简体   繁体   English

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

[英]bash: Wait for process substitution subshell to finish

How can bash wait for the subshell used in process substitution to finish in the following construct? bash如何在下面的构造中等待进程替换中使用的子shell完成? (This is of course simplified from the real for loop and subshell which I am using, but it illustrates the intent well.) (当然,这与我使用的for循环和subshel​​l进行了简化,但是很好地说明了意图。)

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

Prints: 印刷品:

Finished
Subshell: 1
Subshell: 2
Subshell: 3

Instead of: 代替:

Subshell: 1
Subshell: 2
Subshell: 3
Finished

How can I make bash wait for those subshells to complete? 如何让bash等待这些子shell完成?

UPDATE 更新

The reason for using process substitution is that I'm wanting to use file descriptors to control what is printed to the screen and what is sent to the process. 使用进程替换的原因是我想使用文件描述符来控制打印到屏幕的内容以及发送到流程的内容。 Here is a fuller version of what I'm doing: 这是我正在做的完整版本:

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"

Prints: 印刷品:

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

Processing each may take much longer than the transfer. 处理每个文件可能要比传输花费更长的时间。 The file transfers should be sequential since bandwidth is the limiting factor. 文件传输应该是顺序的,因为带宽是限制因素。 I would like to start processing each file after it is received without waiting for all of them to transfer. 我想在收到每个文件后开始处理,而不必等待所有文件都传输。 The processing can be done in parallel, but only a with a limited number of instances (due to limited memory/CPU). 该处理可以并行完成,但只能使用有限数量的实例(由于有限的内存/ CPU)进行。 So if the fifth file just finished transferring but only the second file has finished processing, the third and fourth files should complete processing before the fifth file is processed. 因此,如果第五个文件刚刚完成传输,而只有第二个文件已完成处理,则第三和第四个文件应在第五个文件处理之前完成处理。 Meanwhile the sixth file should start transferring. 同时,第六个文件应开始传输。

you could have the subshell create a file that the main shell waits for. 您可以让子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"

Bash 4.4 lets you collect the PID of a process substitution with $! Bash 4.4允许您使用$!收集流程替换的PID $! , so you can actually use wait , just as you would for a background process: ,因此您实际上可以像使用后台进程一样使用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"

Beyond that, consider flock -style locking -- though beware of races: 除此之外,考虑使用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"

That said, given your actual use case, what you want should presumably look more like: 就是说,考虑到您的实际用例,您想要的应该看起来更像是:

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

You can use bash coproc to hold a read-able filedescriptor to be closed when all process' children die: 您可以使用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"

If this is to be run on a system where there is an attacker you should not use a temp file name that can be guessed. 如果要在存在攻击者的系统上运行此文件,则不应使用可以猜测的临时文件名。 So based on @Barmar's solution here is one that avoids that: 因此,基于@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"

I think you are making it more complicated than it needs to be. 我认为您正在使它变得比所需的更加复杂。 Something like this works because the internal bash executions are a subprocess of the main process, the wait causes the process to wait until everything is finished before printing. 之所以这样,是因为内部bash执行是主流程的子流程,因此等待会导致流程等待,直到所有内容都完成后再进行打印。

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

Unix and derivatives (Linux) have the ability to wait for child (sub) processes but not grandchild processes such as occurred in your original. Unix和派生类(Linux)能够等待子(子)进程,但不能等待子进程(例如原始进程中的子进程)。 Some would consider the polling solution where you go back and check for completion to be vulgar since it does not use this mechanism. 有些人会认为您回溯到该轮询解决方案并检查完成情况的方法很粗俗,因为它不使用此机制。

The solution where the xargs PID was captured was not vulgar, just too complicated. 捕获xargs PID的解决方案并不庸俗,只是太复杂了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM