其中man bash
写道:
等待[-fn] [id ...]
等待每个指定的子进程并返回其终止状态。每个id可能是进程ID或作业规范;如果给出了作业规范,则等待该作业管道中的所有进程。如果未给出 id,则等待所有当前活动的子进程,并且返回状态为零。如果提供了 -n 选项,则 wait 等待任何作业终止并返回其退出状态。
典型的例子如
command1 & command2 & command3 & wait
意味着这三个命令并行运行,并且只有在所有命令完成后才会完成下一步。
我的问题在于这两个 bash 脚本的结果:
#!/bin/bash
for i in 1 2 3 ; do
a=$i
echo "$a is $i"
done 2>/dev/null
结果:
1 is 1
2 is 2
3 is 3
和我预期的差不多。现在我假设分配变量是一个漫长的过程,所以我等待它:
#!/bin/bash
for i in 1 2 3 ; do
a=$i & wait
echo "$a is $i"
done 2>/dev/null
结果是:
3 is 1
3 is 2
3 is 3
我很困惑,原因如下:
- 唯一
wait
应该等待完成的过程是分配变量,然后应该运行脚本中的下一步 (echo
)。a=3
应该只发生在循环的最后一次迭代中。 - 据我所知,
for
-loops 在子 shell 中运行,并且wait
仅具有启动它的 shell 的范围。因此它甚至不应该等待for
-loop 完成(因为这是父级)。 - 我从未指定
echo
与其他进程并行运行,因此我没有预料到会出现竞争情况。
那么为什么a
-variable 是在最后一次循环迭代中设置的,而该变量$i
却不是?wait
我误解了命令的哪一部分?这种行为完全超出了我的预期。
GNU bash,版本 5.0.3(1),基于 5.7.0-0.bpo.2-amd64 Linux 内核。
预先取消设置a
会使第二个脚本返回此值
is 1
is 2
is 3
即该变量从未设置,并且是从我之前的运行中拖出的。
答案1
for
循环不在子shell中运行,例如您可以拥有for i in 1 2 3; do a=foo; done; echo $a
并且它会打印foo
,即使该变量在循环之外使用。循环变量的值也是循环最后分配给它的值(如果循环退出,则可能不是最后一个值break
)。
但使用&
做将该命令放入子 shell 中,然后执行操作a=$i &
会使分配仅发生在子 shell 中。当然,您也可以在子 shell 中使用分配的值:
例如:
a=1;
{ a=2; echo "subshell: $a"; } &
echo "main: $a";
wait;
echo "after: $a"
会打印
main: 1
subshell: 2
after: 1
做一些类似{ a=$i; echo "$a"; } &
验证的事情。
让后台命令访问(并修改!)主 shell 中的变量将需要进程之间进行一些同步,这会给 shell 带来复杂性,而收益可能相对较小。
如果你做不过需要它,例如,如果您想在后台运行一个长时间运行的命令,但需要其输出,则必须执行一些操作,例如将输出临时存储在文件中,例如
tmp=$(mktemp -d)
some long process > "$tmp/a" &
another long process > "$tmp/b" &
wait
a=$(< "$tmp/a")
b=$(< "$tmp/b")
...
rm -rf "$tmp"
或者使用 GNU Parallel 之类的东西,它是专门为并行运行命令而构建的。它还具有parset
能够为 shell 变量赋值的命令。
至于为什么你3
从$a
循环的每次迭代中得到,这可能是因为你之前运行过第一个循环,并且它离开a
了3
。将所有后续分配推入子 shell 中后,这就是a
整个循环中主 shell中保留的值。