我正在尝试编写一个.sh文件,同时运行许多程序

我试过了

prog1 
prog2

但是它会运行prog1,然后等待prog1结束,然后启动prog2……

那么如何并行运行呢?


当前回答

如何:

prog1 & prog2 && fg

这将:

prog1开始。 将其发送到后台,但继续打印其输出。 启动prog2,并将其放在前台,因此可以使用ctrl-c关闭它。 当你关闭prog2时,你将返回到prog1的前台,所以你也可以用ctrl-c关闭它。

其他回答

你可以使用wait:

some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2

它将后台程序pid分配给变量($!是最后启动进程的PID),然后wait命令等待它们。这很好,因为如果您终止了脚本,它也会终止进程!

xargs -P <n>允许并行运行<n个>命令。

虽然-P是一个非标准选项,但是GNU (Linux)和macOS/BSD实现都支持它。

示例如下:

最多同时运行3个命令, 只有在先前启动的进程终止时才启动附加命令。

time xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF

输出如下所示:

1   # output from 1st command 
4   # output from *last* command, which started as soon as the count dropped below 3
2   # output from 2nd command
3   # output from 3rd command

real    0m3.012s
user    0m0.011s
sys 0m0.008s

计时显示这些命令是并行运行的(最后一个命令仅在最初3个命令中的第一个命令终止后启动,但执行得非常快)。

xargs命令本身在所有命令完成之前不会返回,但是您可以在后台执行它,使用控制操作符&终止它,然后使用内置的wait等待整个xargs命令完成。

{
  xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF
} &

# Script execution continues here while `xargs` is running 
# in the background.
echo "Waiting for commands to finish..."

# Wait for `xargs` to finish, via special variable $!, which contains
# the PID of the most recently started background process.
wait $!

注意:

BSD/macOS xargs要求您显式地指定并行运行的命令的数量,而GNU xargs允许您指定-P 0以并行运行尽可能多的命令。 并行运行的进程的输出在生成时到达,因此它将不可预测地交错。 正如Ole的回答中所提到的(大多数平台都不是标准的),GNU并行在每个进程的基础上方便地序列化(分组)输出,并提供了许多更高级的特性。

如果您有一个GUI终端,您可以为希望并行运行的每个进程生成一个新的选项卡终端实例。

这样做的好处是,每个程序都在自己的选项卡中运行,它可以独立于其他运行的程序进行交互和管理。

例如,在Ubuntu 20.04上:

gnome-terminal --tab -- bash -c 'prog1'
gnome-terminal --tab -- bash -c 'prog2'

要按顺序运行某些程序或其他命令,可以添加;

gnome-terminal --tab -- bash -c 'prog1_1; prog1_2'
gnome-terminal --tab -- bash -c 'prog2'

我发现对于某些程序,终端在启动之前就关闭了。对于这些程序,我将终端命令与;等待或;睡眠1

gnome-terminal --tab -- bash -c 'prog1; wait'

对于Mac OS,你必须为你正在使用的终端找到一个等效的命令——我没有在Mac OS上测试,因为我没有Mac。

并行运行多个程序:

prog1 &
prog2 &

如果你需要脚本等待程序完成,你可以添加:

wait

在您希望脚本等待它们的地方。

由于某些原因,我不能使用等待,我想出了这个解决方案:

# create a hashmap of the tasks name -> its command
declare -A tasks=(
  ["Sleep 3 seconds"]="sleep 3"
  ["Check network"]="ping imdb.com"
  ["List dir"]="ls -la"
)

# execute each task in the background, redirecting their output to a custom file descriptor
fd=10
for task in "${!tasks[@]}"; do
    script="${tasks[${task}]}"
    eval "exec $fd< <(${script} 2>&1 || (echo $task failed with exit code \${?}! && touch tasks_failed))"
    ((fd+=1))
done

# print the outputs of the tasks and wait for them to finish
fd=10
for task in "${!tasks[@]}"; do
    cat <&$fd
    ((fd+=1))
done

# determine the exit status
#   by checking whether the file "tasks_failed" has been created
if [ -e tasks_failed ]; then
    echo "Task(s) failed!"
    exit 1
else
    echo "All tasks finished without an error!"
    exit 0
fi