it-swarm.cn

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

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

85
Peter.O

陷阱是

IFS=; while read..

在循环外为整个Shell环境设置IFS

while IFS= read

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

while IFS= read xxx; ... done

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

blabalbla
 ooooooo

而之后

IFS=; read xxx; ... done

IFSstays重新定义:现在echo "blabalbla $IFS ooooooo"版画

blabalbla  ooooooo

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


这个问题的第二部分 已合并到这里 ,所以我从这里删除了相关的答案。

86
rozcietrzewiacz

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

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导致换行符被忽略,而反斜杠-anything则忽略第一个反斜杠。为避免反斜杠受到特殊对待,我们使用read -r

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

更好,我们按预期有两行。这两行几乎包含所需的内容:helloworld之间的双空格已保留,因为它在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是内置变量,因此该变量实际上永远不会在外部进程的环境中结束。不过,只要read正在执行¹,我们就会在其中分配$IFS的值。请注意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未指定该值,实际上取决于Shell。

48

除了(已经弄清)while IFS='' readIFS=''; while readwhile IFS=''; read习惯用法(每个命令vs脚本/ Shell范围IFS变量之间的IFS范围界定差异之外)范围),带回家的教训是您失去了领先优势  如果IFS变量设置为(包含a)空格,则输入行的尾部空格。

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

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

另请参阅: 重击,使用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"
)
3
jon

受到 Yuzem的回答 的启发

如果您想将IFS设置为实际字符,这对我有用

iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
  echo "$line"
done
1
Steven Penny