等待 bash 脚本中的任何进程完成

等待 bash 脚本中的任何进程完成

我正在尝试检查 bash 脚本中正在运行多少个活动进程。这个想法是让 x 个进程保持运行,当一个进程完成时,就会启动下一个进程。

出于测试目的,我设置了这个脚本:

find $HOME/Downloads -name "dummy" &
find $HOME/Downloads -name "dummy" &
find $HOME/Downloads -name "dummy" &

while true
do
    pids=()
    while read pid; do
        echo "PID: $pid"
        pids+=("$pid")
    done < <(jobs -p)
    
    jobs -p

    echo "Active processes: ${#pids[@]}"
    if [ ${#pids[@]} -lt 2 ]; then
        break
    fi

    echo "Process(es) still running... ${pids[@]}"
    sleep 1
done

但这不起作用,因为jobs -p即使进程已完成,也会继续返回作业 ID。

以下示例详细说明了该问题:

#!/bin/bash

find $HOME/Downloads -name "dummy" &
find $HOME/Downloads -name "dummy" &
find $HOME/Downloads -name "dummy" &

while true
do
    jobs -p # continues to print all 3 jobs
    sleep 1
done

如何获得while循环中的活动作业?

问候,

答案1

虽然wait -n(根据评论@icarus)在这种特殊情况下工作,但应该注意的是,它$!包含最后启动的进程的 PID。所以你也可以测试一下:

#!/bin/bash

find $HOME/Downloads -name "dummy" &
p1=$!
find $HOME/Downloads -name "dummy" &
p2=$!
find $HOME/Downloads -name "dummy" &
p3=$!
while true
do
    if ps $p1 > /dev/null ; then
        echo -n "p1 runs "
    else 
        echo -n "p1 ended"
    fi
    if ps $p2 > /dev/null ; then
        echo -n "p2 runs "
    else 
        echo -n "p2 ended"
    fi
    if ps $p1 > /dev/null ; then
        echo -n "p3 runs "
    else 
        echo -n "p3 ended"
    fi
    echo ''
    sleep 1
done

parallel也是一个更好的选择。

答案2

该脚本的问题在于其中没有任何内容将调用等待系统调用之一。通常,在调用 wait 之前,内核将为该进程保留一个条目,因为这是存储子进程的返回代码的地方。如果父进程在子进程之前结束,则子进程将重新设置父进程,通常为 PID 1。系统启动后,PID 1 通常被编程为进入一个循环,仅调用 wait 来收集这些进程的退出值。

wait重写测试脚本来调用我们得到的shell内置函数

pids=()
find $HOME/Downloads -name "dummy" &
pids+=( $! )
find $HOME/Downloads -name "dummy" &
pids+=( $! )
find $HOME/Downloads -name "dummy" &
pids+=( $! )

echo "Initial active processes: ${#pids[@]}"
for ((i=${#pids[@]}; i>1; i--)) ; do 
do
    wait -n # Wait for  one process to exit
    echo "A process exited with RC=$?"
    # Note that -n is a bash extension, not in POSIX
    # if we have bash 5.1 then we can use "wait -np EX" to find which
    # job has finished, the value is put in $EX. Then we can remove the
    # value from the pids array. 

    echo "Still outstanding $(jobs -p)"
    sleep 1
done

答案3

我正在尝试检查 bash 脚本中正在运行多少个活动进程。这个想法是让 x 个进程保持运行,当一个进程完成时,就会启动下一个进程。

使用 GNU Parallel 时,它看起来像这样:

parallel -j3 find $HOME/Downloads -name {} ::: "name1" "name2" "name3"

如果您需要运行不同的程序(而不仅仅是不同的参数):

parallel -j3 ::: "find $HOME/Downloads -name name1" "find $HOME/Downloads -name name2"  "find $HOME/Downloads -name name3" 

如果命令更复杂,请使用 bash 函数:

find1() {
  find $HOME/Downloads -name "name1"
}
find2() {
  find $HOME/Downloads -name "name2"
}
find3() {
  find $HOME/Downloads -name "name3"
}
export -f find1 find2 find3
parallel -j3 ::: find1 find2 find3

如果省略,-j3它将为每个 CPU 核心运行一项作业。

相关内容