问题不在于
cat
,也不在于
for
循环本身;而是在于使用反引号。当你写下以下任何一种形式时:
for i in `cat file`
或者(更好):
for i in $(cat file)
或者(在 ksh、zsh 或 bash 中):
for i in $(<file)
shell执行命令并将输出捕获为字符串,删除尾随的换行符(以及使用bash删除所有的NUL),在字符$IFS
处分隔单词,并在结果单词上执行globbing,也称为文件名生成或路径扩展。如果您想要将输入行传递给$i
,您需要调整IFS
或使用while
循环。如果处理的文件可能很大,while
循环更好;它不需要一次性将整个文件读入内存,也不执行文件名生成,并且不跳过空行,而不像使用$(...)
的版本。
IFS='
'
set -o noglob
for i in $(<file)
do printf '%s\n' "$i"
done
在大多数情况下,使用引号将"$i"
括起来是一个很好的做法。在这个上下文中,通过修改了$IFS
并禁用了通配符展开,实际上并不是必要的,但养成良好的习惯总是好的。相比于echo
,printf
更好,因为echo
对于包含-n
、-nene
、-eee
或者根据echo
的实现和/或环境处理反斜杠的输入行,可能输出空白行或空白内容。这在以下脚本中是重要的:
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*
$
输出:
$ sh bq.sh
abc 123
foo
-Enee
/e* /b*
$
使用
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
$
对于 while read
循环,语法应该是:
while IFS= read -r line
do
printf '%s\n' "$line"
done < file
- 没有
-r
,read
会破坏反斜杠。
- 没有
IFS=
,read
会删除前导和尾随的空格和制表符(假设默认值为$IFS
)。
- 应该使用
printf
而不是echo
,并且对$line
进行引用,原因同上。
虽然在bash中这种优化的效果要小得多,因为bash仍然会fork一个子进程来执行扩展。
bash
语言)$' \t\n'
;也就是说,它由空格、制表符和换行符组成。这可能会改变您的分析结果。当您说“在逗号处断开”时,我相信您指的是在逗号后面的空格处断开,这与 IFS 包含空格(以及制表符和换行符)是一致的。 - Jonathan Leffler