如何将文件读入Shell中的变量?


488

我想读取文件并将其保存在变量中,但是我需要保留变量,而不仅仅是打印文件。我怎样才能做到这一点?我已经编写了此脚本,但它并不是我所需要的:

#!/bin/sh
while read LINE  
do  
  echo $LINE  
done <$1  
echo 11111-----------  
echo $LINE  

在我的脚本中,我可以将文件名作为参数,因此,例如,如果文件包含“ aaaa”,它将打印出以下内容:

aaaa
11111-----

但这只是将文件打印到屏幕上,我想将其保存到变量中!是否有捷径可寻?


1
它似乎是纯文本。如果它是二进制文件,则由于或将导致输出不完整(大小小于实际文件),因此将需要cat$(<someFile)
水瓶座力量

Answers:


1052

在跨平台的最低公分母中,sh您可以使用:

#!/bin/sh
value=`cat config.txt`
echo "$value"

bash或中zsh,无需调用即可将整个文件读入变量cat

#!/bin/bash
value=$(<config.txt)
echo "$value"

调用catbashzsh以啜文件会被认为是猫无用的使用

请注意,不必引用命令替换来保留换行符。

请参阅:Bash Hacker的Wiki-命令替代-Specialties


4
好吧,但它是猛烈的,不是嘘。它可能并不适合所有情况。
moala 2013年

14
会不会value="`cat config.txt`"value="$(<config.txt)"的情况下,更安全的是config.txt中包含空格?
Martin von Wittich 2014年

13
请注意,cat按如上所述使用并不总是被视为对的无用cat。例如,< invalid-file 2>/dev/null将导致错误消息无法路由到/dev/null,而cat invalid-file 2>/dev/null确实被正确路由到/dev/null
Dejay Clayton

16
对于像我这样的新shell脚本编写者,请注意,cat版本使用反斜线,而不是单引号!希望这可以节省一个半小时的时间,而我花了我半个小时才弄清楚。
ericksonla

7
对于像我这样的新拳手:请注意,这value=$(<config.txt)很好,但是value = $(<config.txt)很糟糕。当心那些空间。
ArtHare

88

如果要将整个文件读入一个变量:

#!/bin/bash
value=`cat sources.xml`
echo $value

如果要逐行阅读:

while read line; do    
    echo $line    
done < file.txt

2
@brain:如果文件是Config.cpp并包含反斜杠,该怎么办;双引号和引号?
user2284570

2
您应该在中将变量双引号echo "$value"。否则,外壳程序将对该值执行空格标记化和通配符扩展。
Tripleee '16

3
@ user2284570使用read -r而不是仅read-总是使用,除非您特别需要暗示的古怪行为。
Tripleee '16

74

两个重要的陷阱

到目前为止,其他答案都忽略了这些答案:

  1. 从命令扩展中删除尾随换行符
  2. NUL字符删除

从命令扩展中删除尾随换行符

这是一个问题:

value="$(cat config.txt)"

类型解决方案,但不适用于read基础解决方案。

命令扩展删除尾随的换行符:

S="$(printf "a\n")"
printf "$S" | od -tx1

输出:

0000000 61
0000001

这打破了从文件读取的幼稚方法:

FILE="$(mktemp)"
printf "a\n\n" > "$FILE"
S="$(<"$FILE")"
printf "$S" | od -tx1
rm "$FILE"

POSIX解决方法:在命令扩展中附加一个额外的字符,并在以后将其删除:

S="$(cat $FILE; printf a)"
S="${S%a}"
printf "$S" | od -tx1

输出:

0000000 61 0a 0a
0000003

几乎是POSIX的解决方法:ASCII编码。见下文。

NUL字符删除

没有理智的Bash方法将NUL字符存储在变量中

这会影响扩展和read解决方案,我不知道有什么好的解决方法。

例:

printf "a\0b" | od -tx1
S="$(printf "a\0b")"
printf "$S" | od -tx1

输出:

0000000 61 00 62
0000003

0000000 61 62
0000002

哈,我们的NUL不见了!

解决方法:

  • ASCII编码。见下文。

  • 使用bash扩展$""文字:

    S=$"a\0b"
    printf "$S" | od -tx1

    仅适用于文字,因此对从文件读取无用。

陷阱的解决方法

将文件的uuencode base64编码版本存储在变量中,并在每次使用前进行解码:

FILE="$(mktemp)"
printf "a\0\n" > "$FILE"
S="$(uuencode -m "$FILE" /dev/stdout)"
uudecode -o /dev/stdout <(printf "$S") | od -tx1
rm "$FILE"

输出:

0000000 61 00 0a
0000003

uuencode和udecode是POSIX 7,但默认情况下不在Ubuntu 12.04中(sharutils程序包)... <()除写入另一个文件外,我看不到bash进程替换扩展的POSIX 7替代品...

当然,这很慢且不方便,所以我想真正的答案是:如果输入文件中可能包含NUL字符,请不要使用Bash。


2
仅感谢这个为我工作的人,因为我需要换行。
Jason Livesay 2014年

1
@CiroSantilli:如果FILE是Config.cpp并且包含反斜杠,那该怎么办?双引号和引号?
user2284570 2014年

@ user2284570我不知道,但是很容易找到:S="$(printf "\\\'\"")"; echo $S。输出:\'"。如此奏效=)
Ciro Santilli郝海东冠状病六四事件法轮功

@CiroSantilli:在5511行上?您确定没有自动化的方法吗?
user2284570 2014年

@ user2284570我不明白,哪里有5511行?陷阱来自$()扩展,我的示例表明$()扩展适用于\'"
Ciro Santilli郝海东冠状病六四事件法轮功


2

正如Ciro Santilli指出的那样,使用命令替换将删除尾随的换行符。他们添加尾随字符的解决方法很棒,但是使用了一段时间后,我决定我需要一个根本不使用命令替换的解决方案。

我的方法现在readprintf内置-v标记一起使用,以便将stdin的内容直接读入变量。

# Reads stdin into a variable, accounting for trailing newlines. Avoids needing a subshell or
# command substitution.
read_input() {
  # Use unusual variable names to avoid colliding with a variable name
  # the user might pass in (notably "contents")
  : "${1:?Must provide a variable to read into}"
  if [[ "$1" == '_line' || "$1" == '_contents' ]]; then
    echo "Cannot store contents to $1, use a different name." >&2
    return 1
  fi

  local _line _contents
   while read -r _line; do
     _contents="${_contents}${_line}"$'\n'
   done
   _contents="${_contents}${_line}" # capture any content after the last newline
   printf -v "$1" '%s' "$_contents"
}

这支持带或不带尾随换行符的输入。

用法示例:

$ read_input file_contents < /tmp/file
# $file_contents now contains the contents of /tmp/file

大!我只是想知道,为什么不使用类似_contents="${_contents}${_line}\n "保留换行符的方法呢?
Eenoku

1
您在询问$'\n'吗?这是必要的,否则您将附加文字\ n字符。您的代码块的末尾也有一个额外的空间,不确定这是否是有意的,但是会在每行后面缩进一个额外的空格。
dimo414

好,谢谢您的解释!
Eenoku

-3

您可以通过for循环一次访问1行

#!/bin/bash -eu

#This script prints contents of /etc/passwd line by line

FILENAME='/etc/passwd'
I=0
for LN in $(cat $FILENAME)
do
    echo "Line number $((I++)) -->  $LN"
done

将整个内容复制到File(例如line.sh);执行

chmod +x line.sh
./line.sh

您的for循环不会遍历行,而是会遍历单词。在的情况下/etc/passwd,每行仅包含一个单词。但是,其他文件可能每行包含多个单词。
mpb
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.