为什么不在 for 循环中使用反引号

mrc*_*_kr 6 bash shell-script

前段时间,我发布了一些关于脚本的问题的答案。有人指出我不应该使用以下命令:

for x in $(cat file); do something; done 
Run Code Online (Sandbox Code Playgroud)

但取而代之的是:

while read f; do something; done < file
Run Code Online (Sandbox Code Playgroud)

无用的 Cat文章假设可以解释整个问题,但唯一的解释是:

反引号是完全危险的,除非您知道反引号的结果将小于或等于您的 shell 可以接受的命令行长度。(实际上,这是内核限制。limits.h 中的常量 ARG_MAX 应该告诉您自己的系统可以占用多少。POSIX 要求 ARG_MAX 至少为 4,096 字节。)

如果我正确理解了这一点,如果我在命令中使用非常大的文件的输出(它应该超过limits.h 文件中定义的ARG_MAX),bash(?) 应该会崩溃。所以我用命令检查了 ARG_MAX:

> grep ARG_MAX /usr/src/kernels/$(uname -r)/include/uapi/linux/limits.h
#define ARG_MAX       131072    /* # bytes of args + environ for exec() */
Run Code Online (Sandbox Code Playgroud)

然后我创建了包含没有空格的文本的文件:

> ls -l
-rw-r--r--. 1 root root 100000000 Aug 21 15:37 in_file
Run Code Online (Sandbox Code Playgroud)

然后我运行:

for i in $(cat in_file); do echo $i; done
Run Code Online (Sandbox Code Playgroud)

没有什么可怕的事情发生。

那么我应该怎么做来检查整个“不要使用带有循环的猫”的事情是否/如何危险?

Sté*_*las 4

这取决于file要包含的内容。如果它意味着包含 IFS 分隔的 shell 全局列表,例如(假设默认值为$IFS):

/var/log/*.log /var/adm/*~
/some/dir/*.txt
Run Code Online (Sandbox Code Playgroud)

那么这for i in $(cat file)就是要走的路。因为这就是未引用的内容的作用$(cat file):在输出上应用 split+glob 运算符cat file:在删除其尾随换行符的因此,它将循环遍历这些 glob 扩展所产生的每个文件名(除非 glob 与任何文件不匹配,否则会将 glob 保留在那里但未扩展)。

如果您想循环 的每个分隔行file,您可以这样做:

while IFS= read -r line <&3; do
{
  something with "$line"
} 3<&-
done 3< file
Run Code Online (Sandbox Code Playgroud)

通过for循环,您可以使用以下命令循环遍历每个非空行:

IFS='
' # 仅在换行符上分割(实际上是换行符序列和
  # 忽略前导和尾随,因为换行符是
  # IFS 空白字符)
set -o noglob # 禁用globsplit+glob 运算符的
对于 $(cat 文件) 中的行;做
   带有“$line”的东西
完毕

然而:

while read line; do
  something with "$line"
done < file
Run Code Online (Sandbox Code Playgroud)

没什么意义。这是以一种非常复杂的方式读取 的内容file,其中 的字符$IFS和反斜杠被特殊处理。

在任何情况下,您引用的文本所指的 ARG_MAX 限制是在execve()系统调用上(关于参数和环境变量的累积大小),因此仅适用于正在使用可能的文件系统上的命令执行的情况应用于命令替换的 split+glob 运算符的非常长的扩展(该文本在多个帐户上具有误导性和错误性)。

例如,它适用于:

cat -- $(cat file) # with shell implementations where cat is not builtin
Run Code Online (Sandbox Code Playgroud)

但不在:

for i in $(cat file)
Run Code Online (Sandbox Code Playgroud)

不涉及execve()系统调用的地方。

比较:

bash-4.4$ echo '/*/*/*/*' > file
bash-4.4$ true $(cat file)
bash-4.4$ n=0; for f in $(cat file); do ((n++)); done; echo "$n"
523696
bash-4.4$ /bin/true $(cat file)
bash: /bin/true: Argument list too long
Run Code Online (Sandbox Code Playgroud)

bash使用的true内置命令或循环可以for,但执行时不行/bin/true。请注意,file只有 9 个字节大,但由于shell 正在扩展 glob,因此扩展$(cat file)了几兆字节。/*/*/*/*

更多阅读: