关于Unix:如何将文件读入shell中的变量?

How to read a file into a variable in shell?

我想读取一个文件并将其保存在变量中,但我需要保留变量,而不仅仅是打印出文件。我该怎么做?我写了这个脚本,但它不是我需要的:

1
2
3
4
5
6
7
#!/bin/sh
while read LINE  
do  
  echo $LINE  
done <$1  
echo 11111-----------  
echo $LINE

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

1
2
aaaa
11111-----

但这只是把文件打印到屏幕上,我想把它保存到一个变量中!有简单的方法吗?


在跨平台中,您使用的最小公分母sh

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

bashzsh中,在不调用cat的情况下将整个文件读入变量:

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

bashzsh中调用cat来屏蔽文件将被认为是对cat的无用使用。

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

参见:bash hacker's wiki-命令替换-专业。


如果要将整个文件读取到变量中:

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

如果要逐行阅读:

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


两个重要的陷阱

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

  • 从命令扩展中删除尾随换行符
  • 去除nul字符
  • 从命令扩展中删除尾随换行符

    这是一个问题:

    1
    value="$(cat config.txt)"

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

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

    1
    2
    3
    S="$(printf"a
    ")"
    printf"$S" | od -tx1

    输出:

    1
    2
    0000000 61
    0000001

    这打破了从文件中读取的简单方法:

    1
    2
    3
    4
    5
    6
    7
    FILE="$(mktemp)"
    printf"a

    ">"$FILE"
    S="$(<"$FILE")"
    printf"$S" | od -tx1
    rm"$FILE"

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

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

    输出:

    1
    2
    0000000 61 0a 0a
    0000003

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

    去除nul字符

    在变量中存储nul字符没有明智的bash方法。

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

    例子:

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

    输出:

    1
    2
    3
    4
    5
    0000000 61 00 62
    0000003

    0000000 61 62
    0000002

    哈,我们的屁股不见了!

    解决办法:

    • ASCII编码。见下文。

    • 使用bash扩展$""字面值:

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

      只适用于文本,因此不适用于从文件中读取。

    解决陷阱

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

    1
    2
    3
    4
    5
    6
    FILE="$(mktemp)"
    printf"a\0
    ">"$FILE"
    S="$(uuencode -m"$FILE" /dev/stdout)"
    uudecode -o /dev/stdout <(printf"$S") | od -tx1
    rm"$FILE"

    输出:

    1
    2
    0000000 61 00 0a
    0000003

    uuencode和udecode是posix 7,但默认情况下不在Ubuntu 12.04中(sharutils包)。我看不到用于bash进程<()替换扩展的POSIX7替代方法,除非写入另一个文件…

    当然,这是缓慢和不方便的,所以我想真正的答案是:如果输入文件可能包含nul字符,不要使用bash。


    这对我很有用:
    v=$(cat )
    echo $v


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

    我的方法现在使用readprintfbuiltin的-v标志,以便将stdin的内容直接读取到变量中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 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}"$'
    '
       done
       _contents="${_contents}${_line}" # capture any content after the last newline
       printf -v"$1" '%s'"$_contents"
    }

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

    示例用法:

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/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

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

    1
    2
    chmod +x line.sh
    ./line.sh