【问题标题】:bash: Wait for process substitution subshell to finishbash:等待进程替换子shell完成
【发布时间】:2018-07-18 20:28:07
【问题描述】:

bash 如何在以下构造中等待进程替换中使用的子shell 完成? (这当然是从我使用的真正的 for 循环和 subshel​​l 中简化的,但它很好地说明了意图。)

for i in {1..3}; do
    echo "$i"
done > >(xargs -n1 bash -c 'sleep 1; echo "Subshell: $0"')
echo "Finished"

打印:

Finished
Subshell: 1
Subshell: 2
Subshell: 3

代替:

Subshell: 1
Subshell: 2
Subshell: 3
Finished

如何让 bash 等待这些子 shell 完成?

更新

使用进程替换的原因是我想使用文件描述符来控制打印到屏幕的内容以及发送到进程的内容。这是我正在做的更完整的版本:

for myFile in file1 file2 file3; do
    echo "Downloading $myFile"     # Should print to terminal
    scp -q $user@$host:$myFile ./  # Might take a long time
    echo "$myFile" >&3             # Should go to process substitution
done 3> >(xargs -n1 bash -c 'sleep 1; echo "Processing: $0"')
echo "Finished"

打印:

Downloading file1
Downloading file2
Downloading file3
Finished
Processing: file1
Processing: file2
Processing: file3

处理每一个可能需要比传输更长的时间。文件传输应该是连续的,因为带宽是限制因素。我想在收到文件后开始处理每个文件,而不是等待所有文件都传输。处理可以并行完成,但实例数量有限(由于内存/CPU 有限)。因此,如果第五个文件刚刚完成传输,但只有第二个文件完成了处理,则第三个和第四个文件应该在第五个文件处理之前完成处理。同时第六个文件应该开始传输。

【问题讨论】:

  • 我认为没有办法。为什么要使用流程替换而不仅仅是管道?
  • 可能的,但仅限于非常新版本的 bash。我们在这里针对的是哪个版本?
  • 我使用的是 bash 4.1
  • 4.1 不会削减它。
  • 我将更新帖子以了解流程替换背后的原因,尽管我可能最终只使用管道。

标签: bash xargs process-substitution


【解决方案1】:

Bash 4.4 允许您使用$! 收集进程替换的 PID,因此您实际上可以使用wait,就像您对后台进程一样:

case $BASH_VERSION in ''|[123].*|4.[0123])
  echo "ERROR: Bash 4.4 required" >&2; exit 1;;
esac

# open the process substitution
exec {ps_out_fd}> >(xargs -n1 bash -c 'sleep 1; echo "Subshell: $0"'); ps_out_pid=$!

for i in {1..3}; do
  echo "$i"
done >&$ps_out_fd

# close the process substitution
exec {ps_out_fd}>&-

# ...and wait for it to exit.
wait "$ps_out_pid"

除此之外,考虑flock 风格的锁定——尽管要小心竞争:

for i in {1..3}; do
  echo "$i"
done > >(flock -x my.lock xargs -n1 bash -c 'sleep 1; echo "Subshell: $0"')

# this is only safe if the "for" loop can't exit without the process substitution reading
# something (and thus signalling that it successfully started up)

flock -x my.lock echo "Lock grabbed; the subshell has finished"

也就是说,考虑到您的实际用例,您想要的应该看起来更像:

download() {
  for arg; do
    scp -q $user@$host:$myFile ./ || (( retval |= $? ))
  done
  exit "$retval"
}
export -f download

printf '%s\0' file1 file2 file3 |
  xargs -0 -P2 -n1 bash -c 'download "$@"' _

【讨论】:

  • 也许这是由于 bash 版本的差异,但如果我等待在进程替换中创建的子进程(使子进程报告 $$,而不是使用 $!),我得到“pid 12345 不是这个外壳的孩子”。测试:: <(echo "$$" > childpid; sleep 10; echo childdone >&2); sleep 1; wait "$(cat childpid)"。版本:4.3.48(1)-发布
  • @init_js,是的,进程替换 PID 必须是 4.4 版才能成为waitable。
【解决方案2】:

你可以让子shell创建一个主shell等待的文件。

tempfile=/tmp/finished.$$
for i in {1..3}; do
    echo "$i"
done > >(xargs -n1 bash -c 'sleep 1; echo "Subshell: $0"'; touch $tempfile)
while ! test -f $tempfile; do sleep 1; done
rm $tempfile
echo "Finished"

【讨论】:

  • 这可以与循环结构一起使用吗?如果循环的每次迭代都触及同一个文件,那么第二次和第三次迭代将触及同一个文件,并且主脚本将在第一次迭代完成后停止等待。
  • @RustyLemur,touch 仅在xargs 退出后运行,这意味着所有迭代都已完成。
  • 哦,我明白了。这很方便!
【解决方案3】:

您可以使用 bash coproc 保存一个可读的文件描述符,以便在所有进程的子进程死亡时关闭:

coproc read                  # previously: `coproc cat`, see comments
for i in {1..3}; do
    echo "$i"
done > >(xargs -n1 bash -c 'sleep 1; echo "Subshell: $0"')
exec {COPROC[1]}>&-          # close my writing side
read -u ${COPROC[0]}         # will wait until all potential writers (ie process children) end
echo "Finished"

【讨论】:

  • 在 bash 4.1 中不需要 eval -- exec {var_holding_fd}>&- 将关闭变量 var_holding_fd 中命名的 FD。
  • coproc cat中的cat有什么意义?那里可以使用任何字符串吗?
  • Afaik 不幸的是,不可能创建一个完美用于此目的的“裸”管道(如pipe(2) syscall)。我发现最接近(虽然简单)的是coproc cat(因为cat 将在其阅读端关闭时退出),另请参阅superuser.com/questions/184307/bash-create-anonymous-fifo——嗯,说coproc read 也可以工作(同时避免执行一个外部命令):)
【解决方案4】:

如果要在存在攻击者的系统上运行,则不应使用可以猜到的临时文件名。因此,基于@Barmar 的解决方案,这里可以避免这种情况:

tempfile="`tempfile`"
for i in {1..3}; do
    echo "$i"
done > >(xargs -n1 bash -c 'sleep 1; echo "Subshell: $0"'; rm "$tempfile")
while test -f "$tempfile"; do sleep 1; done
echo "Finished"

【讨论】:

    【解决方案5】:

    我认为你让它变得比它需要的更复杂。这样的事情之所以有效,是因为内部 bash 执行是主进程的子进程,等待会导致进程等到所有内容都完成后再打印。

    for i in {1..3}
    do
        bash -c "sleep 1; echo Subshell: $i"  &
    done
    wait
    echo "Finished"
    

    Unix 和衍生产品 (Linux) 能够等待子(子)进程,但不能等待原始进程中发生的孙子进程。有些人会认为您返回并检查完成情况的轮询解决方案是粗俗的,因为它不使用此机制。

    xargs PID被抓的方案并不俗,就是太复杂了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-07
      • 1970-01-01
      • 2013-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多