Bash - 等待子进程,循环类型之间的区别

Bash - waiting on child processes, differences between loop types

下面的脚本包含两个将进程分叉到后台的循环。它们的行为不同,因为 for 循环产生预期的结果(等待 10 个子进程完成)但是 while 循环不产生相同的结果。根据 pstree 的输出,显然 while 循环不会导致子进程成为当前 shell 的子进程。我怀疑这是管道的问题?

$ /tmp/wtf    
1499921102 - for loop - start
wtf(2797)─┬─pstree(2810)
          ├─sleep(2800)
          ├─sleep(2801)
          ├─sleep(2802)
          ├─sleep(2803)
          ├─sleep(2804)
          ├─sleep(2805)
          ├─sleep(2806)
          ├─sleep(2807)
          ├─sleep(2808)
          └─sleep(2809)
1499921102 - for loop - waiting
1499921112 - for loop - done
1499921112 - while loop - start
wtf(2797)───pstree(2826)
1499921112 - while loop - waiting
1499921112 - while loop - done


$ cat /tmp/wtf 
#!/bin/bash

set -m
set -e 
set -u

printf "%s - for loop - start\n" "$(date +%s)"
for i in `seq 1 10`
do
  sleep 10 &
done
pstree -pl $$
printf "%s - for loop - waiting\n" "$(date +%s)"
wait
printf "%s - for loop - done\n" "$(date +%s)"

printf "%s - while loop - start\n" "$(date +%s)"
seq 1 10 | while read i
do
  sleep 10 &
done 
pstree -pl $$ 
printf "%s - while loop - waiting\n" "$(date +%s)" 
wait
printf "%s - while loop - done\n" "$(date +%s)"

如何让当前进程等待非直接子进程?尝试将进程 ID 传递到 wait 未成功:

wait $(seq 1 10 | while read i
do
  sleep 10 &
  echo $!
done | tr '\n' ' ')

/tmp/wtf: line 22: wait: pid 2866 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2867 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2868 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2869 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2870 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2871 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2872 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2873 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2874 is not a child of this shell
/tmp/wtf: line 22: wait: pid 2875 is not a child of this shell

一些实验...

这个基于 while 的版本似乎有效:

seq 1 10 | while read i; do sleep 10 &  echo $!; done | xargs sh -c wait 

这也有效:

sh -c wait $(seq 1 10 | while read i; do sleep 10 &  echo $!; done)

备注:

  • tr不用了
  • 由于 wait 是一个 shell 内置命令,xargs 无法看到它 sh -c.
  • 显然 sh -c 是必要的(即使没有 xargs),所以这是一个 内置 问题...

在做了更多研究后,我证实了我的怀疑,管道内部的循环创建了一个子shell。我了解到 wait 只会等待当前 shell 的 children,所以我能想到的最干净的解决方案是确保后台任务和等待在同一个子shell。我通过将循环和 wait 包装在管道内的一组 curly-braces 中来实现这一点。

printf "%s - while loop - start\n" "$(date +%s)"
seq 1 10 | {
  while read i
  do
    sleep 10 &
  done 
  pstree -pl $$ 
  printf "%s - while loop - waiting\n" "$(date +%s)" 
  wait
}
printf "%s - while loop - done\n" "$(date +%s)"