为什么经常使用“ while IFS = read”而不是“ IFS =”?在阅读时。


81

似乎通常的做法是将IFS的设置置于while循环之外,以免在每次迭代时都重复设置它。我读过人读的书,还是我在这里缺少一些微妙的(或明显地)陷阱?

Answers:


82

陷阱是

IFS=; while read..

设置IFS为外循环整个shell环境,而

while IFS= read

仅在read调用时重新定义它(Bourne shell中除外)。您可以检查是否像

while IFS= read xxx; ... done

然后在这样的循环之后,echo "blabalbla $IFS ooooooo"打印

blabalbla
 ooooooo

而之后

IFS=; read xxx; ... done

重新定义的IFS 支柱:现在echo "blabalbla $IFS ooooooo"打印

blabalbla  ooooooo

因此,如果您使用第二种形式,则必须记住要重置:IFS=$' \t\n'


该问题的第二部分已在此处合并,因此我从此处删除了相关答案。


好的,似乎是一个潜在的“陷阱”,是忽略了重置外部IFS的操作……但我确实想知道是否还有其他问题……我正在疯狂地进行测试,并且请注意,在while的命令列表中设置IFS的行为与qute有所不同,具体取决于是否跟随冒号。我还不了解这种行为(现在),现在我想知道在此级别上是否涉及一些特殊的考虑…… while IFS=X read不会在分裂X,但是while IFS=X; read会...
Peter.O 2011年

(您的意思是冒号,对吗?)第二个while没有太大意义- 以该分号while 结尾的条件,因此没有实际的循环... read成为单元素循环中的第一个命令... ?那do那..呢?
rozcietrzewiacz 2011年

1
不,等等-对,您可以在while条件中(在之前do)有几个命令。
rozcietrzewiacz 2011年

哦..当然,您可以拥有它们……正如您已经意识到的那样……但是它们似乎不喜欢分号……(而且循环将无限循环循环直到最后一个命令返回非-零退出代码)...我现在想知道陷阱是否完全位于另一个扇区中;了解while命令列表的工作原理,例如。为什么IFS=行得通,但IFS=X没有...(或者也许我对此做了一段时间..需要喝咖啡休息时间:)
Peter.O 2011年

1
$ rozcietrzewiacz ..糟糕...当我移动我的更新时,我没有注意到您的更新(如前一则评论中所述)。它看起来很有趣,而且已经开始有意义了……但即使是一个晚上,像我这样的鸟,已经很晚了……(我刚刚听到早晨的鸟声:)……也就是说,我已经集会了一下,读了你的例子……我想我明白了,实际上我确保您已经掌握了,但是我必须睡觉:) ...这几乎是尤里卡!时刻...谢谢
Peter.O 2011年

45

让我们看一个带有精心设计的输入文本的示例:

text=' hello  world\
foo\bar'

这是两行,第一行以空格开头,以反斜杠结尾。首先,让我们看一下发生的情况,而无需采取任何预防措施read(但printf '%s\n' "$text"要谨慎打印,$text而不会产生扩展风险)。(下面$ ‌是shell提示符。)

$ printf '%s\n' "$text" |
  while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]

read吃了反斜杠:backslash-newline导致换行符被忽略,而backslash-anything则忽略第一个反斜杠。为了避免对反斜杠进行特殊处理,我们使用read -r

$ printf '%s\n' "$text" |
  while read -r line; do printf '%s\n' "[$line]"; done
[hello  world\]
[foo\bar]

更好,我们按预期有两行。这两行几乎包含所需的内容:hello和之间的双精度空格world已保留,因为它位于line变量内。另一方面,最初的空间被吃光了。这是因为read读取的单词与传递给它的变量一样多,除了最后一个变量包含该行的其余部分,但它仍以第一个单词开头,即初始空格被丢弃。

因此,为了按字面意义阅读每一行,我们需要确保没有进行分。为此,我们将IFS变量设置为空值。

$ printf '%s\n' "$text" |
  while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello  world\]
[foo\bar]

请注意我们如何IFS 专门针对read内置时间进行设置。的IFS= read -r line设置环境变量IFS专门为执行(为空值)read。这是一般简单命令语法的一个实例:变量分配的序列(可能为空),后跟命令名称及其参数(同样,您可以在任何时候抛出重定向)。由于read是内置变量,因此该变量永远不会最终出现在外部进程的环境中。但是$IFS,只要read执行¹,值就是我们在其中分配的值。请注意,这read不是一个特殊的内置变量,因此分配仅在其持续时间内有效。

因此,我们注意不要更改IFS可能依赖它的其他指令的值。无论周围的代码IFS最初设置为什么,该代码都将起作用,并且如果循环内的代码依赖,也不会造成任何麻烦IFS

与此代码段对比,该代码段以冒号分隔的路径查找文件。从文件中读取文件名列表,每行一个文件名。

IFS=":"; set -f
while IFS= read -r name; do
  for dir in $PATH; do
    ## At this point, "$IFS" is still ":"
    if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
  done
done <filenames.txt

如果循环为while IFS=; read -r name; do …,则for dir in $PATH不会拆分$PATH为冒号分隔的组件。如果代码为IFS=; while read …,则在循环主体中IFS未设置为会更加明显:

当然,有可能IFS在执行之后恢复的值read。但这需要知道先前的值,这是额外的工作。IFS= read是最简单的方法(也是最简便的方法)。

¹ 而且,如果read被捕获的信号中断,则可能是在执行陷阱时-POSIX并未指定,实际上取决于外壳。


4
谢谢吉尔..一个非常不错的导览..(您是说'set -f'吗?)....现在,对于读者来说,重申已经说过的话,我想强调一下我看错了方向。首先也是最重要的事实是,该构造while IFS= read(。之后没有分号=不是while或of IFSread。的特殊形式。该构造是通用的:即。anyvar=anyvalue anycommand。缺少;after设置anyvar使anyvar local的范围为anycommand.. while--do / done循环与.local的范围无关any_var
Peter.O 2011年

3

除了(和已经阐明的),和习惯用法IFS之间的作用域区别(每个命令vs脚本/ shell范围的变量作用域)之外,总结课是,如果IFS变量,您将丢失输入行的前导尾随空格设置为(包含)空格。while IFS='' readIFS=''; while readwhile IFS=''; readIFS

如果正在处理文件路径,则可能会导致非常严重的后果。

因此,将IFS变量设置为空字符串不是什么坏主意,因为它可以确保不会删除行的开头和结尾空格。

另请参阅:Bash,使用IFS从文件逐行读取

(
shopt -s nullglob
touch '  file with spaces   '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)

+1出色的演示,使用“ rm * file * with * spaces *”
清除

0

受到Yuzem答案的启发

如果您想设置IFS为实际角色,这对我有用

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
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.