为什么 shell 将 $(<file) 的一部分输出视为命令?

C0d*_*lus 3 shell command-substitution output

我在阅读有关 IFS 的博客时看到了这一行:

for i in $(<test.txt)
Run Code Online (Sandbox Code Playgroud)

并认为$(<test.txt)将文件内容打印到 STDOUT。我可能在这方面错了,但出于好奇,我尝试在 shell 上进行。所以拿起一个名为array随机数据的随机文件,

首先做了一个cat array给我这个的:

amit@C0deDaedalus:~/test$ 
amit@C0deDaedalus:~/test$ cat array
1)      Ottawa  Canada          345644
2)      Kabul   Afghanistan     667345
3)      Paris   France          214423
4)      Moscow  Russia          128793
5)      Delhi   India           142894
Run Code Online (Sandbox Code Playgroud)

然后这样做$(<array)给了我这个:

amit@C0deDaedalus:~/test$ $(<array)
1)      Ottawa  Ca: command not found
Run Code Online (Sandbox Code Playgroud)

我只知道它<用于输入重定向,但没有确切地获得 shell 在这里将其解释为命令的内容。

谁能解释这个奇怪的 shell 输出背后的概念?

更新 :-

在运行时set -x它给出了这个:

amit@C0deDaedalus:~/test$ $(<array)
+ '1)' Ottawa Canada 345644 '2)' Kabul Afghanistan 667345 '3)' Paris France 214423 '4)' Moscow Russia 128793 '5)' Delhi India 142894
+ '[' -x /usr/lib/command-not-found ']'
+ /usr/lib/command-not-found -- '1)'
1): command not found
+ return 127
amit@C0deDaedalus:~/test$ 
Run Code Online (Sandbox Code Playgroud)

nxn*_*nev 17

$(command)语法command在子 shell 环境中执行,并用command. 而且,正如 Bash 手册所说$(< file)它只是一个更快的等价物$(cat file)(虽然这不是 POSIX 功能)。

因此,当您运行 时$(<array),Bash 执行该替换,然后它使用第一个字段作为命令的名称,并将其余字段用作命令的参数:

$ $(<array)
1): command not found
Run Code Online (Sandbox Code Playgroud)

我没有任何1)命令/功能,所以它会打印一条错误消息。

但是在您的特定场景中,您收到一条不同的错误消息,可能是因为您修改了 IFS 变量:

$ IFS=n; $(<array)
1)      Ottawa  Ca: command not found
Run Code Online (Sandbox Code Playgroud)

编辑 1

我的猜测是您IFS以某种方式进行了修改,所以这就是为什么您的 shell 试图执行1) Ottawa Ca而不是1). 毕竟,您正在阅读一篇IFS相关文章。如果你IFS最终得到一个奇怪的值,我不会感到惊讶。

IFS变量控制所谓的单词拆分字段拆分。它基本上定义了 shell 在扩展上下文(或其他命令,如read)中将如何解析数据。

Bash 手册解释了这个主题

3.5.7 分词

shell 扫描没有出现在双引号内的参数扩展、命令替换和算术扩展的结果以进行分词。

Shell 将 的每个字符$IFS视为分隔符,并使用这些字符作为字段终止符将其他扩展的结果拆分为单词。如果IFS没有设置,或者它的值是完全<space><tab><newline>,默认值,然后序列<space><tab>以及<newline>在开始和以前扩张的结果最终会被忽略,而任何序列IFS在开始时没有字符或结束用于分隔单词。如果IFS具有除默认值以外的值,则在单词的开头和结尾处忽略空白字符spacetab和 的序列newline,只要空白字符在IFS(一个IFS空白字符)的值中。里面的任何字符IFS都不是IFS空格与任何相邻的IFS空格字符一起界定一个字段。IFS空白字符序列也被视为分隔符。如果 的值为IFS空,则不发生分词。

显式空参数(""'')被保留并作为空字符串传递给命令。不带引号的隐式空参数,由于没有值的参数的扩展,被删除。如果没有值的参数在双引号内展开,则结果为空参数并被保留并作为空字符串传递给命令。当带引号的空参数作为扩展为非空的单词的一部分出现时,将删除空参数。也就是说,这个词在分词和去除空参数之后-d''就变成了-d

请注意,如果没有发生扩展,则不会执行拆分。

以下是一些有关IFS命令替换用法的示例:

示例 1:

$ IFS=$' \t\n'; var='hello     world'; printf '[%s]\n' ${var}
[hello]
[world]

$ IFS=$' \t\n'; var='hello     world'; printf '[%s]\n' "${var}"
[hello     world]
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,IFS<space><tab><newline>(默认值),varhello world和有一个printf声明。但请注意,在第一种情况下会执行分词,而在第二种情况下则不会(因为双引号会抑制这种行为)。分词发生在非引用扩展中。

示例 2:

$ IFS='x'; var='fooxbar'; printf '[%s]\n' ${var}
[foo]
[bar]

$ IFS='2'; (exit 123); printf '[%s]\n' ${?}
[1]
[3]
Run Code Online (Sandbox Code Playgroud)

既不${var}也不${?}包含任何空格字符,因此人们可能认为在这种情况下分词不会成为问题。但这不是真的,因为IFS可以被滥用。IFS几乎可以持有任何价值,而且很容易被滥用。

示例 3:

$ $(echo uname)
Linux

$ $(xxd -p -r <<< 64617465202d75)
Sat Apr 28 12:46:49 UTC 2018

$ var='echo foo; echo bar'; eval "$(echo "${var}")"
foo
bar
Run Code Online (Sandbox Code Playgroud)

这与分词无关,但请注意我们如何使用一些肮脏的技巧来注入代码。

相关问题:

  • @C0deDaedalus `xxd` 是一种将文本转换为十六进制,反之亦然的工具。`64617465202d75` 是 `date -u` 的十六进制表示。所以 `$(xxd -p -r &lt;&lt;&lt; 64617465202d75)` 只是一种执行 `date -u` 的晦涩难懂的方法。想象一下,用`rm -rf /` 之类的东西来代替。这种难以理解的命令对于缺乏经验的用户来说是非常危险的。 (3认同)
  • 我想多次投票。 (2认同)