AFAIK,cat
是一个外部命令,它在执行时分叉一个新进程,就像sh -c
执行脚本一样。话虽如此,我希望cat
使用它的命令环境,因为它被其他外部命令使用,例如
f=test.txt sh -c 'cat "$f"'
这不应该显示文件的内容吗
f=test.txt cat $f
?
注意:我不是在问什么是 someVariable=someValue command
.我问为什么第一个示例使用其命令变量而不是第二个示例。变量扩展在第二个示例中发生的方式也应该在第一个示例中发生。
答案1
概括
该命令f=test.txt sh -c 'cat "$f"'
会产生输出,因为变量赋值f=test.txt
发生在(单引号)命令参数的扩展之前'cat "$f"'
。单引号可防止在cat "$f"
执行子命令之前发生扩展。
该命令f=test.txt cat $f
确实不是f=test.txt
因为发生变量赋值而产生输出后(未引用的)命令参数的扩展$f
。
为什么f=test.txt cat $f
不产生任何输出
首先,我将尝试解释为什么该命令f=test.txt cat $f
没有产生任何输出,尽管您期望它会产生任何输出。这里对于所谓的评估顺序可能有些轻微的混淆简单的命令。
您可能假设命令序言中的变量赋值(即赋值f=test.txt
)发生在命令主体中的变量扩展(即$f
in的扩展cat $f
)之前。但事实并非如此。为了验证这一点,我们可以参考页面简单的命令扩展在 Bash 手册中或关于简单命令的小节在里面POSIX规范。这两篇参考文献都包含以下段落:
“简单命令”是一系列可选的变量分配和重定向,可以任意顺序,可选地后跟单词和重定向,并由控制运算符终止。
当需要执行给定的简单命令时(即,当任何条件结构,例如 AND-OR 列表或案件语句没有绕过简单命令),以下扩展、赋值和重定向都应从命令文本的开头到结尾执行:
根据以下规则被识别为变量赋值或重定向的单词Shell 语法规则保存以供步骤 3 和 4 中的处理。
非变量赋值或重定向的词应扩展。如果扩展后仍有任何字段,则第一个字段应被视为命令名称,其余字段是命令的参数。
重定向应按中所述执行重定向。
每个变量赋值都应在赋值之前进行扩展,以进行波形符扩展、参数扩展、命令替换、算术扩展和引号删除。
请注意,步骤 2 是命令中发生变量扩展的地方,但步骤 1 告诉我们变量赋值将保存到步骤 3 和 4。因此,表达式在赋值发生之前cat $f
扩展为(不带参数)。这解释了为什么您没有得到任何输出。cat
f=test.txt
有关此主题的进一步讨论,请参阅以下帖子:
为什么f=test.txt sh -c 'cat "$f"'
做产生输出
接下来我将尝试解释为什么该命令f=test.txt sh -c 'cat "$f"'
做产生输出。为此,我们要看看shell 执行的常规操作的完整列表:
shell 从文件中读取输入(参见嘘),来自 -c 选项或来自系统()和波彭()POSIX.1-2008 系统接口卷中定义的函数。如果 shell 命令文件的第一行以字符“#!”开头,则结果未指定。
shell 将输入分解为标记:单词和运算符;看令牌识别。
shell 对每个命令的不同部分(分别)执行各种扩展,从而产生被视为命令和参数的路径名和字段列表;看词表达式。
shell 执行重定向(参见重定向) 并从参数列表中删除重定向运算符及其操作数。
shell 执行一个函数(参见功能 定义 命令),内置(参见特殊的内置实用程序)、可执行文件或脚本,将参数名称指定为编号为 1 到 n 的位置参数,并将命令名称(或者在脚本内的函数的情况下,指定脚本的名称)作为位置参数编号为 0(参见命令搜索和执行)。
shell 可选择等待命令完成并收集退出状态(请参阅命令的退出状态)。
因此,您可以在此处看到调用函数/内置/可执行文件/脚本(此列表中的第 6 步)后解析简单命令。因此f=test.txt
发生分配前程序执行sh -c 'cat "$f"'
。因为参数是单引号的,所以只会被解析后命令执行。因此子命令扩展为cat "test.txt"
.
答案2
shell 命令上的变量分配仅影响传递给该命令的环境,而不影响执行 shell 的环境。
您可以更直接地看到这一点
$ f=1
$ f=2 echo $f
1
$
在设置为 的echo
环境中运行,但分配在(正在构建的)命令的环境中运行,而不是在用于构建命令的 shell 环境中运行。f
2
只有不带命令的赋值才会影响正在运行的 shell 环境,并且 shell 环境用于构造命令。
答案3
因为在您的第二个示例中,$f
是由您的 shell 扩展的,而不是由cat
.假设f
在您的 shell 环境中没有进行其他设置,$f
则扩展为空字符串,cat
然后从 stdin 读取,就像没有给定参数一样。
在你的第一个例子中,$f
位于单引号内,因此它不会被原始 shell 扩展,但是是通过实例扩展sh
看到cat "$f"
和已f
在其环境中设置。