为什么如此频繁地使用“while IFS= read”,而不是“IFS=;”在阅读时..`?

为什么如此频繁地使用“while IFS= read”,而不是“IFS=;”在阅读时..`?

看来正常的做法会将 IFS 的设置放在 while 循环之外,以便不为每次迭代重复设置它......这是否只是习惯性的“猴子看,猴子做”风格,就像这只猴子一样,直到我读男人读,或者我在这里错过了一些微妙的(或明显明显的)陷阱?

答案1

陷阱是

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'


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

答案2

让我们看一个示例,其中包含一些精心设计的输入文本:

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环境变量(为空值)IFSread(为空值) 。这是一般情况下的一个例子简单的命令语法:变量赋值的(可能为空)序列,后跟命令名称及其参数(此外,您可以在任何时候引入重定向)。由于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是最简单的方法(而且,方便地,也是最短的方法)。

1并且,如果read被捕获信号中断,可能是在陷阱执行时 — 这不是由 POSIX 指定的,而是取决于实践中的 shell。

答案3

除了(已经澄清的) 和 习语之间的作用域差异(每个命令与脚本/shell 范围的变量作用域)之外,最IFS重要的教训是您失去了领先的while IFS='' readIFS=''; while readwhile IFS=''; readIFS如果 IFS 变量设置为(包含)空格,则输入行的尾随空格。

如果正在处理文件路径,这可能会产生非常严重的后果。

因此,将 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"
)

答案4

灵感来自尤泽姆的回答

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

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

相关内容