bash:等待进程替换子shell完成

时间:2018-07-18 20:28:07

标签: bash xargs process-substitution

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)进行。因此,如果第五个文件刚刚完成传输,而只有第二个文件已完成处理,则第三和第四个文件应在第五个文件处理之前完成处理。同时,第六个文件应开始传输。

5 个答案:

答案 0 :(得分:3)

您可以让子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"

答案 1 :(得分:2)

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 "$@"' _

答案 2 :(得分:2)

您可以使用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"

答案 3 :(得分:1)

如果要在存在攻击者的系统上运行此文件,则不应使用可以猜测的临时文件名。因此,基于@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"

答案 4 :(得分:0)

我认为您正在使它变得比所需的更加复杂。之所以这样,是因为内部bash执行是主流程的子流程,因此等待会导致该流程等待,直到一切都完成后再打印。

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

Unix和派生类(Linux)可以等待子(子)进程,但不能等待子进程(例如原始进程)。有些人会认为您回溯到该轮询解决方案并检查完成情况是粗俗的,因为它不使用此机制。

捕获xargs PID的解决方案不是很粗俗,只是太复杂了。