同时进行变量赋值和扩展

4 bash

为什么

for i in {1..5}; do x="${i}" echo "$x"; done
Run Code Online (Sandbox Code Playgroud)

不输出

1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

?

这样做的正确方法是什么?

(经过测试 for i in {1..5}; do x=$(i) echo "$x"; done

-bash: i: 命令未找到

和别的)

roa*_*ima 10

回答你的问题

为什么不for i in {1..5}; do x="${i}" echo "$x"; done输出1, 2, 3, 4, 5

原因与在执行期间评估操作的顺序有关。看看这个命令

x="${i}" echo "$x"
Run Code Online (Sandbox Code Playgroud)

这是做什么的

  1. 用它们的值替换变量
  2. 为变量分配一个临时值 x
  3. 执行命令

所以你得到

  1. x=1 echo ""(或x=2 echo ""等)
  2. 在此命令的持续时间内,x设置为值1
  3. 执行命令: echo ""

很可能你的意思是这个命令是两条指令:给 赋值x,输出它的值。但是在 shell 语法中,您编写的代码是完全合法的代码,因此 shell 毫无异议地执行了它。

  • @roaima:你的解释并不完全正确。步骤2中的关键点是变量x被放置在要执行的命令的执行环境(即子进程)中,因此对父进程不可见。这可以通过将示例更改为“for i in {1..5};”来说明。执行 x="${i}" printenv x; done`,输出预期的 1,2,...,5。 (2认同)
  • @roaima:这不是一个反例;相对的。如果我们在反例中将 `echo $x` 替换为 `printenv x`,我们会再次得到 1,2,3....。命令前面的 x=... **确实** 设置后续命令的执行环境,但是“$x”当然仍然在当前环境中计算,因此不会“看到” x 的变化。 (2认同)
  • 嗯,难道我们的意思是一样的,只是表达方式不同吗?让我明确一下我的观点:在 sh/bash/ksh/zsh 中,“X=UV”形式的语句(其中 V 是外部程序)会生成一个子进程,V 在其中启动。该子进程继承父进程的环境(即当前 shell,不同之处在于变量 X 设置为值 U。这意味着 X 成为子进程中的环境变量(并且只有那时)。如果我改为 ` X=U; V`,变量X是在当前进程中设置的,并不一定出现在V的环境中。 (2认同)

iga*_*gal 6

您要求回答两个问题:

  1. 您要求解释为什么您当前的代码没有产生预期的输出。

  2. 您要求以正确的方式编写代码,以便它确实产生预期的输出。

查看您的代码,我可以看到两种可能的解释,说明您为何以这种方式编写代码:

  1. 对于 for-loop语法可能会有一些轻微的混淆。

  2. 所谓的simple command 中的求值顺序可能会有些混乱。

for 循环语法

在第一种情况下,我会说您在变量赋值后缺少分号。如果你想在一行上写你的 for 循环,那么你需要在循环体中的每个命令后面放一个分号。试试这个:

for i in {1..5}; do x="${i}"; echo "$x"; done
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用多行语法编写 for 循环,用换行符代替分号:

for i in {1..5}
do
x="${i}"
echo "${x}"
done
Run Code Online (Sandbox Code Playgroud)

您还可以混合匹配分号和换行符,例如:

for i in {1..5}; do
x="${i}"; echo "${x}"
done
Run Code Online (Sandbox Code Playgroud)

简单命令的评估

在第二种情况下,我会说您可能已经假设命令序言中的变量赋值(即x="$i"赋值)发生在命令主体中的变量扩展(即${x}in的扩展echo "${x}")之前。但事实并非如此。为了验证这一点,我们可以参考在页面上简单的命令扩展在Bash的手册或在简单的命令款POSIX规范。这两个参考文献都包含以下段落:

“简单命令”是一系列可选的变量赋值和重定向,以任何顺序,可选地跟随单词和重定向,由控制运算符终止。

当需要执行给定的简单命令时(即当任何条件结构如 AND-OR 列表或case语句没有绕过简单命令时),以下扩展、赋值和重定向都应从命令文本的开头到结尾:

  1. 根据Shell 语法规则被识别为变量赋值或重定向的单词将被保存以在步骤 3 和 4 中进行处理。

  2. 不是变量赋值或重定向的词应该被扩展。如果任何字段在它们的扩展之后仍然存在,则第一个字段应被视为命令名称,其余字段是命令的参数。

  3. 应中所描述来执行重定向的重定向

  4. 在分配值之前,每个变量分配都应针对波浪号扩展、参数扩展、命令替换、算术扩展和引号删除进行扩展。

请注意,第 2 步是命令中变量扩展发生的地方,但第 1 步告诉我们变量赋值会一直保存到第 3 步和第 4 步。因此表达式在赋值发生之前echo "${x}"被扩展到。这解释了为什么你得到空输出。echo ""x="${i}"

有关此主题的进一步讨论,请参阅以下帖子: