问题不在于cat
,也不在for
循环本身;这是在使用反引号。当您编写以下任一内容时:
for i in `cat file`
或更好):
for i in $(cat file)
or (in ksh
, zsh
or bash
¹):
for i in $(<file)
shell 执行命令并将输出捕获为字符串,删除尾随换行符(以及 bash 中的所有 NUL),以分隔字符处的单词$IFS
并且(zsh 除外)执行globbing aka 文件名生成 aka 路径名扩展关于结果的话。如果你想将行输入到$i
,你要么必须摆弄IFS
或使用while
环形。这while
如果存在处理的文件很大的危险,则循环更好;它不必一次将整个文件读入内存,并且不执行通配符,也不会跳过空行,这与使用的版本不同$(...)
.
IFS='
'
set -o noglob # disable globbing
for i in $(<file)
do printf '%s\n' "$i"
done
周围的报价"$i"
通常是个好主意。在此背景下,通过修改$IFS
,并且禁用通配符,这实际上并不重要,但即使如此,好习惯仍然是好习惯。printf
比echo
, as echo
对于包含以下内容的输入行将不输出任何内容或空行-n
, -nene
, -eee
或取决于echo
实现和/或环境 mangle 反斜杠。这在以下脚本中很重要:
old="$IFS"
IFS='
'
set -o noglob
for i in $(<file)
do
(
IFS="$old"
set +o noglob
printf '%s\n' "$i"
)
done
当数据文件包含表格或多个空格时(两者都在默认值$IFS
) 或通配符或前导尾随空格
$ cat file
abc 123
foo
-Enee
/e* /b*
$
Output:
$ sh bq.sh
abc 123
foo
-Enee
/e* /b*
$
With echo
并且没有双引号:
$ cat bq.sh
old="$IFS"
IFS='
'
set -o noglob
for i in $(<file)
do
(
IFS="$old"
set +o noglob
echo $i
)
done
$ sh bq.sh
abc 123
foo
/etc /bin /boot
$
For the while read
循环,语法应该是:
while IFS= read -r line
do
printf '%s\n' "$line"
done < file
- without
-r
, read
会破坏反斜杠
- without
IFS=
, read
将删除前导和尾随空格以及制表符(假设默认值为$IFS
).
-
printf
应该使用而不是echo
, and $line
引用的原因与上述相同。
¹ Though in bash it's much less of an optimisation as bash still forks a child process to perform the expansion.