Linux 获取后台进程的退出代码

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1570262/
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-08-03 17:46:51  来源:igfitidea点击:

Get exit code of a background process

linuxshellunixprocess

提问by bob

I have a command CMD called from my main bourne shell script that takes forever.

我有一个从我的主 bourne shell 脚本调用的命令 CMD,它需要永远。

I want to modify the script as follows:

我想修改脚本如下:

  1. Run the command CMD in parallel as a background process ($CMD &).
  2. In the main script, have a loop to monitor the spawned command every few seconds. The loop also echoes some messages to stdout indicating progress of the script.
  3. Exit the loop when the spawned command terminates.
  4. Capture and report the exit code of the spawned process.
  1. 并行运行命令 CMD 作为后台进程 ($CMD &)。
  2. 在主脚本中,每隔几秒钟就有一个循环来监视生成的命令。该循环还会向 stdout 回显一些消息,指示脚本的进度。
  3. 当生成的命令终止时退出循环。
  4. 捕获并报告衍生进程的退出代码。

Can someone give me pointers to accomplish this?

有人可以给我指示来完成这个吗?

回答by Abu Aqil

#/bin/bash

#pgm to monitor
tail -f /var/log/messages >> /tmp/log&
# background cmd pid
pid=$!
# loop to monitor running background cmd
while :
do
    ps ax | grep $pid | grep -v grep
    ret=$?
    if test "$ret" != "0"
    then
        echo "Monitored pid ended"
        break
    fi
    sleep 5

done

wait $pid
echo $?

回答by mob

1: In bash, $!holds the PID of the last background process that was executed. That will tell you what process to monitor, anyway.

1:在 bash 中,$!保存最后执行的后台进程的 PID。无论如何,这将告诉您要监视的进程。

4: wait <n>waits until the process with PID <n>is complete (it will block until the process completes, so you might not want to call this until you are sure the process is done), and then returns the exit code of the completed process.

4:wait <n>等待PID的进程<n>完成(它会阻塞直到进程完成,所以你可能不想调用这个直到你确定进程完成),然后返回已完成进程的退出代码。

2, 3: psor ps | grep " $! "can tell you whether the process is still running. It is up to you how to understand the output and decide how close it is to finishing. (ps | grepisn't idiot-proof. If you have time you can come up with a more robust way to tell whether the process is still running).

2、3:ps或者ps | grep " $! "可以告诉你进程是否还在运行。如何理解输出并决定距离完成有多近取决于您。(ps | grep不是防白痴。如果你有时间,你可以想出一种更可靠的方法来判断进程是否仍在运行)。

Here's a skeleton script:

这是一个骨架脚本:

# simulate a long process that will have an identifiable exit code
(sleep 15 ; /bin/false) &
my_pid=$!

while   ps | grep " $my_pid "     # might also need  | grep -v grep  here
do
    echo $my_pid is still in the ps output. Must still be running.
    sleep 3
done

echo Oh, it looks like the process is done.
wait $my_pid
# The variable $? always holds the exit code of the last command to finish.
# Here it holds the exit code of $my_pid, since wait exits with that code. 
my_status=$?
echo The exit status of the process was $my_status

回答by William Pursell

I would change your approach slightly. Rather than checking every few seconds if the command is still alive and reporting a message, have another process that reports every few seconds that the command is still running and then kill that process when the command finishes. For example:

我会稍微改变你的方法。与其每隔几秒检查一次命令是否还活着并报告一条消息,不如让另一个进程每隔几秒报告一次命令仍在运行,然后在命令完成时终止该进程。例如:

#!/bin/sh

cmd() { sleep 5; exit 24; }

cmd &   # Run the long running process
pid=$!  # Record the pid

# Spawn a process that coninually reports that the command is still running
while echo "$(date): $pid is still running"; do sleep 1; done &
echoer=$!

# Set a trap to kill the reporter when the process finishes
trap 'kill $echoer' 0

# Wait for the process to finish
if wait $pid; then
    echo "cmd succeeded"
else
    echo "cmd FAILED!! (returned $?)"
fi

回答by Darren Weber

A simple example, similar to the solutions above. This doesn't require monitoring any process output. The next example uses tail to follow output.

一个简单的例子,类似于上面的解决方案。这不需要监视任何过程输出。下一个示例使用 tail 来跟踪输出。

$ echo '#!/bin/bash' > tmp.sh
$ echo 'sleep 30; exit 5' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh &
[1] 7454
$ pid=$!
$ wait $pid
[1]+  Exit 5                  ./tmp.sh
$ echo $?
5

Use tail to follow process output and quit when the process is complete.

使用 tail 跟踪进程输出并在进程完成时退出。

$ echo '#!/bin/bash' > tmp.sh
$ echo 'i=0; while let "$i < 10"; do sleep 5; echo "$i"; let i=$i+1; done; exit 5;' >> tmp.sh
$ chmod +x tmp.sh
$ ./tmp.sh
0
1
2
^C
$ ./tmp.sh > /tmp/tmp.log 2>&1 &
[1] 7673
$ pid=$!
$ tail -f --pid $pid /tmp/tmp.log
0
1
2
3
4
5
6
7
8
9
[1]+  Exit 5                  ./tmp.sh > /tmp/tmp.log 2>&1
$ wait $pid
$ echo $?
5

回答by errant.info

This may be extending beyond your question, however if you're concerned about the length of time processes are running for, you may be interested in checking the status of running background processes after an interval of time. It's easy enough to check which child PIDs are still running using pgrep -P $$, however I came up with the following solution to check the exit status of those PIDs that have already expired:

这可能超出了您的问题,但是如果您担心进程运行的时间长度,您可能有兴趣在一段时间后检查运行后台进程的状态。使用 很容易检查哪些子 PID 仍在运行pgrep -P $$,但是我想出了以下解决方案来检查那些已经过期的 PID 的退出状态:

cmd1() { sleep 5; exit 24; }
cmd2() { sleep 10; exit 0; }

pids=()
cmd1 & pids+=("$!")
cmd2 & pids+=("$!")

lasttimeout=0
for timeout in 2 7 11; do
  echo -n "interval-$timeout: "
  sleep $((timeout-lasttimeout))

  # you can only wait on a pid once
  remainingpids=()
  for pid in ${pids[*]}; do
     if ! ps -p $pid >/dev/null ; then
        wait $pid
        echo -n "pid-$pid:exited($?); "
     else
        echo -n "pid-$pid:running; "
        remainingpids+=("$pid")
     fi
  done
  pids=( ${remainingpids[*]} )

  lasttimeout=$timeout
  echo
done

which outputs:

输出:

interval-2: pid-28083:running; pid-28084:running; 
interval-7: pid-28083:exited(24); pid-28084:running; 
interval-11: pid-28084:exited(0); 

Note: You could change $pidsto a string variable rather than array to simplify things if you like.

注意:$pids如果您愿意,您可以更改为字符串变量而不是数组以简化事情。

回答by Искрен Хаджинедев

Another solution is to monitor processes via the proc filesystem (safer than ps/grep combo); when you start a process it has a corresponding folder in /proc/$pid, so the solution could be

另一种解决方案是通过 proc 文件系统监控进程(比 ps/grep 组合更安全);当您启动一个进程时,它在 /proc/$pid 中有一个相应的文件夹,因此解决方案可能是

#!/bin/bash
....
doSomething &
local pid=$!
while [ -d /proc/$pid ]; do # While directory exists, the process is running
    doSomethingElse
    ....
else # when directory is removed from /proc, process has ended
    wait $pid
    local exit_status=$?
done
....

Now you can use the $exit_status variable however you like.

现在您可以随意使用 $exit_status 变量。

回答by TrueY

As I see almost all answers use external utilities (mostly ps) to poll the state of the background process. There is a more unixesh solution, catching the SIGCHLD signal. In the signal handler it has to be checked which child process was stopped. It can be done by kill -0 <PID>built-in (universal) or checking the existence of /proc/<PID>directory (Linux specific) or using the jobsbuilt-in (bashspecific. jobs -lalso reports the pid. In this case the 3rd field of the output can be Stopped|Running|Done|Exit . ).

正如我所看到的,几乎所有答案都使用外部实用程序(主要是ps)来轮询后台进程的状态。有一个更 unixesh 的解决方案,捕获 SIGCHLD 信号。在信号处理程序中,必须检查哪个子进程被停止。它可以通过kill -0 <PID>内置(通用)或检查/proc/<PID>目录的存在(Linux 特定)或使用jobs内置(bash特定。jobs -l也报告 pid。在这种情况下,输出的第三个字段可以是 Stopped|Running |完成|退出。)。

Here is my example.

这是我的例子。

The launched process is called loop.sh. It accepts -xor a number as an argument. For -xis exits with exit code 1. For a number it waits num*5 seconds. In every 5 seconds it prints its PID.

启动的进程称为loop.sh。它接受-x或 一个数字作为参数。For -xis exits with exit code 1. 对于一个数字,它等待 num*5 秒。每 5 秒打印一次它的 PID。

The launcher process is called launch.sh:

启动程序过程称为launch.sh

#!/bin/bash

handle_chld() {
    local tmp=()
    for((i=0;i<${#pids[@]};++i)); do
        if [ ! -d /proc/${pids[i]} ]; then
            wait ${pids[i]}
            echo "Stopped ${pids[i]}; exit code: $?"
        else tmp+=(${pids[i]})
        fi
    done
    pids=(${tmp[@]})
}

set -o monitor
trap "handle_chld" CHLD

# Start background processes
./loop.sh 3 &
pids+=($!)
./loop.sh 2 &
pids+=($!)
./loop.sh -x &
pids+=($!)

# Wait until all background processes are stopped
while [ ${#pids[@]} -gt 0 ]; do echo "WAITING FOR: ${pids[@]}"; sleep 2; done
echo STOPPED

For more explanation see: Starting a process from bash script failed

有关更多解释,请参阅:从 bash 脚本启动进程失败

回答by Bjorn

This is how I solved it when I had a similar need:

当我有类似需求时,我是这样解决的:

# Some function that takes a long time to process
longprocess() {
        # Sleep up to 14 seconds
        sleep $((RANDOM % 15))
        # Randomly exit with 0 or 1
        exit $((RANDOM % 2))
}

pids=""
# Run five concurrent processes
for i in {1..5}; do
        ( longprocess ) &
        # store PID of process
        pids+=" $!"
done

# Wait for all processes to finish, will take max 14s
# as it waits in order of launch, not order of finishing
for p in $pids; do
        if wait $p; then
                echo "Process $p success"
        else
                echo "Process $p fail"
        fi
done

回答by Aquarius Power

With this method, your script doesnt have to wait for the background process, you will only have to monitor a temporary file for the exit status.

使用这种方法,您的脚本不必等待后台进程,您只需监视一个临时文件的退出状态。

FUNCmyCmd() { sleep 3;return 6; };

export retFile=$(mktemp); 
FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; }; 
FUNCexecAndWait&

now, your script can do anything else while you just have to keep monitoring the contents of retFile (it can also contain any other information you want like the exit time).

现在,您的脚本可以做任何其他事情,而您只需要继续监视 retFile 的内容(它还可以包含您想要的任何其他信息,例如退出时间)。

PS.: btw, I coded thinking in bash

PS.: btw, 我用 bash 编码思考

回答by Terry

The pid of a backgrounded child process is stored in $!. You can store all child processes' pids into an array, e.g. PIDS[].

后台子进程的 pid 存储在$! . 您可以将所有子进程的 pid 存储到一个数组中,例如PIDS[]

wait [-n] [jobspec or pid …]

Wait until the child process specified by each process ID pid or job specification jobspec exits and return the exit status of the last command waited for. If a job spec is given, all processes in the job are waited for. If no arguments are given, all currently active child processes are waited for, and the return status is zero. If the -n option is supplied, wait waits for any job to terminate and returns its exit status. If neither jobspec nor pid specifies an active child process of the shell, the return status is 127.

等到每个进程 ID pid 或作业规范 jobspec 指定的子进程退出并返回上一个等待的命令的退出状态。如果给出了作业规范,则等待作业中的所有进程。如果没有给出参数,则等待所有当前活动的子进程,返回状态为零。如果提供了 -n 选项,则等待任何作业终止并返回其退出状态。如果 jobspec 和 pid 都没有指定 shell 的活动子进程,则返回状态为 127。

Use waitcommand you can wait for all child processes finish, meanwhile you can get exit status of each child processes via $?and store status into STATUS[]. Then you can do something depending by status.

使用wait命令可以等待所有子进程完成,同时可以通过$?获取每个子进程的退出状态并将状态存储到STATUS[] 中。然后你可以根据状态做一些事情。

I have tried the following 2 solutions and they run well. solution01is more concise, while solution02is a little complicated.

我已经尝试了以下 2 种解决方案,它们运行良好。solution01更简洁,而solution02有点复杂。

solution01

解决方案01

#!/bin/bash

# start 3 child processes concurrently, and store each pid into array PIDS[].
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
  ./${app} &
  PIDS+=($!)
done

# wait for all processes to finish, and store each process's exit code into array STATUS[].
for pid in ${PIDS[@]}; do
  echo "pid=${pid}"
  wait ${pid}
  STATUS+=($?)
done

# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
  if [[ ${st} -ne 0 ]]; then
    echo "$i failed"
  else
    echo "$i finish"
  fi
  ((i+=1))
done

solution02

解决方案02

#!/bin/bash

# start 3 child processes concurrently, and store each pid into array PIDS[].
i=0
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
  ./${app} &
  pid=$!
  PIDS[$i]=${pid}
  ((i+=1))
done

# wait for all processes to finish, and store each process's exit code into array STATUS[].
i=0
for pid in ${PIDS[@]}; do
  echo "pid=${pid}"
  wait ${pid}
  STATUS[$i]=$?
  ((i+=1))
done

# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
  if [[ ${st} -ne 0 ]]; then
    echo "$i failed"
  else
    echo "$i finish"
  fi
  ((i+=1))
done