在Bash中,何时将IFS设置为换行符?

7

我认为将IFS设置为$'\n'会帮助我将整个文件读入数组,示例如下:

IFS=$'\n' read -r -a array < file

然而,上述命令只将文件的第一行读入数组的第一个元素中,其他内容并未被读取。

即使使用以下命令,也只能将文件的第一行读入数组中:

string=$'one\ntwo\nthree'
IFS=$'\n' read -r -a array <<< "$string"

我在这个网站上看到其他帖子,提到使用mapfile -tread循环将文件读入数组。现在我的问题是:我什么时候需要使用IFS=$'\n'呢?

1
相关:IFS=$'\n'的确切含义是什么? - codeforester
2个回答

16
你对于什么是IFS有些困惑。IFS是bash用来执行单词拆分的内部字段分隔符,将行拆分为单词进行扩展。默认值为[ \t\n](空格、制表符、换行符)。 通过重新赋值IFS=$'\n',您将删除' \t'并告诉Bash仅在换行符上拆分单词(您的想法是正确的)。这样可以使带有空格的某些行被读入一个单独的数组元素中而不需要引用。但是,在您的 read -r -a array < file 实现中存在问题。-a 会将行内的单词指定为顺序数组索引。但是,您已经告诉Bash仅在换行符处断开(即整个行)。由于您只调用了一次read,因此仅填充了一个数组索引。您可以选择:
while IFS=$'\n' read -r line; do
    array+=( $line )
done < "$filename"

(如果您只是引用了"$line",则可以不更改IFS
或者,您可以使用IFS=$'\n',然后执行:
IFS=$'\n'
array=( $(<filename) )

或者,最后你也可以使用IFSreadarray
readarray array <filename

试用它们,如果您有疑问,请告诉我。


1
“array=$( $(<filename) )” 应该改为 “array=( $(<filename) )” 吧? - Benjamin W.
1
非常感谢你们,Benjamin和David。你们的回答对我帮助很大。 - learningbee
3
很高兴能为您提供帮助。通过这两个回答,您现在对IFS应该有了很好的了解。理解"拆分单词"和"扩展"这些术语以及bash如何应用它们可能需要更长时间 -- 这很正常,随着时间的推移,这些概念会逐渐清晰。不要忘记查看man bash,所有内容都在那里,只是需要一点时间来熟悉如何阅读文档。当您可以通过简单的man bash/searchterm找到所需信息时,您就已经掌握了shell的精髓。祝您在脚本编写中好运 :) - David C. Rankin

6
你的第二次尝试几乎成功,但是你需要告诉 read 它不仅应该读取到换行符(默认行为),而且也可以例如读取到空字符串。
$ IFS=$'\n' read -a arr -d '' <<< $'a b c\nd e f\ng h i'
$ declare -p arr
declare -a arr='([0]="a b c" [1]="d e f" [2]="g h i")'

但正如你所指出的,如果你有mapfile/readarray,那么这是前进的方法(需要Bash 4.0或更高版本):

$ mapfile -t arr <<< $'a b c\nd e f\ng h i'
$ declare -p arr
declare -a arr='([0]="a b c" [1]="d e f" [2]="g h i")'

-t选项会从每个元素中删除换行符。

至于何时使用IFS=$'\n'

  • 如上所示,如果你想将文件读入数组中,每行一个元素,且你的Bash版本早于4.0,并且不想使用循环。
  • 有些人提倡使用没有空格的IFS以避免意外的单词拆分副作用;但我认为正确的方法是理解单词拆分并确保按需使用适当的引号避免它。
  • 例如bash-completion中的cd脚本,我曾看到IFS=$'\n'在标签补全脚本中使用:该脚本处理路径并将冒号替换为换行符,然后使用该IFS进行拆分。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接