等待脚本中的 bash 后台作业完成

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1131484/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 18:20:02  来源:igfitidea点击:

Wait for bash background jobs in script to be finished

bashscriptingjob-control

提问by mark

To maximize CPU usage (I run things on a Debian Lenny in EC2) I have a simple script to launch jobs in parallel:

为了最大限度地提高 CPU 使用率(我在 EC2 中的 Debian Lenny 上运行),我有一个简单的脚本来并行启动作业:

#!/bin/bash

for i in apache-200901*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200902*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200903*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200904*.log; do echo "Processing $i ..."; do_something_important; done &
...

I'm quite satisfied with this working solution, however I couldn't figure out how to write further code which only executed once all of the loops have been completed.

我对这个工作解决方案非常满意,但是我不知道如何编写更多代码,这些代码仅在所有循环完成后才执行。

Is there a way to get control of this?

有没有办法控制这个?

回答by eduffy

There's a bashbuiltin command for that.

有一个bash内置命令。

wait [n ...]
      Wait for each specified process and return its termination  sta‐
      tus.   Each  n  may be a process ID or a job specification; if a
      job spec is given, all processes  in  that  job's  pipeline  are
      waited  for.  If n is not given, all currently active child pro‐
      cesses are waited for, and the return  status  is  zero.   If  n
      specifies  a  non-existent  process or job, the return status is
      127.  Otherwise, the return status is the  exit  status  of  the
      last process or job waited for.

回答by Ole Tange

Using GNU Parallel will make your script even shorter and possibly more efficient:

使用 GNU Parallel 将使您的脚本更短,并且可能更高效:

parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: apache-*.log

This will run one job per CPU core and continue to do that until all files are processed.

这将为每个 CPU 内核运行一项作业,并继续执行此操作,直到处理完所有文件。

Your solution will basically split the jobs into groups before running. Here 32 jobs in 4 groups:

您的解决方案基本上会在运行之前将作业分成几组。这里有 4 个组中的 32 个工作:

Simple scheduling

简单的调度

GNU Parallel instead spawns a new process when one finishes - keeping the CPUs active and thus saving time:

GNU Parallel 会在完成后生成一个新进程 - 保持 CPU 处于活动状态,从而节省时间:

GNU Parallel scheduling

GNU 并行调度

To learn more:

了解更多:

回答by Olivier Lalonde

I had to do this recently and ended up with the following solution:

我最近不得不这样做,最终得到了以下解决方案:

while true; do
  wait -n || {
    code="$?"
    ([[ $code = "127" ]] && exit 0 || exit "$code")
    break
  }
done;

Here's how it works:

这是它的工作原理:

wait -nexits as soon as one of the (potentially many) background jobs exits. It always evaluates to true and the loop goes on until:

wait -n一旦(可能很多)后台作业之一退出,它就会退出。它总是评估为真,循环继续直到:

  1. Exit code 127: the last background job successfully exited. In that case, we ignore the exit code and exit the sub-shell with code 0.
  2. Any of the background job failed. We just exit the sub-shell with that exit code.
  1. 退出代码127:上一个后台作业成功退出。在这种情况下,我们忽略退出代码并使用代码 0 退出子 shell。
  2. 任何后台作业失败。我们只是用那个退出代码退出子 shell。

With set -e, this will guarantee that the script will terminate early and pass through the exit code of any failed background job.

使用set -e,这将保证脚本将提前终止并通过任何失败的后台作业的退出代码。

回答by Radu

This is my crude solution:

这是我的粗略解决方案:

function run_task {
        cmd=
        output=
        concurency=
        if [ -f ${output}.done ]; then
                # experiment already run
                echo "Command already run: $cmd. Found output $output"
                return
        fi
        count=`jobs -p | wc -l`
        echo "New active task #$count:  $cmd > $output"
        $cmd > $output && touch $output.done &
        stop=$(($count >= $concurency))
        while [ $stop -eq 1 ]; do
                echo "Waiting for $count worker threads..."
                sleep 1
                count=`jobs -p | wc -l`
                stop=$(($count > $concurency))
        done
}

The idea is to use "jobs" to see how many children are active in the background and wait till this number drops (a child exits). Once a child exists, the next task can be started.

这个想法是使用“工作”来查看有多少孩子在后台活跃,然后等到这个数字下降(孩子退出)。一旦孩子存在,就可以开始下一个任务。

As you can see, there is also a bit of extra logic to avoid running the same experiments/commands multiple times. It does the job for me.. However, this logic could be either skipped or further improved (e.g., check for file creation timestamps, input parameters, etc.).

如您所见,还有一些额外的逻辑可以避免多次运行相同的实验/命令。它为我完成了这项工作。然而,这个逻辑可以被跳过或进一步改进(例如,检查文件创建时间戳、输入参数等)。