Shell - получить код выхода фонового процесса

У меня есть команда CMD, вызываемая из моей основной оболочки bourne script, которая выполняется навсегда.

Я хочу изменить script следующим образом:

  • Запустите команду CMD параллельно в качестве фонового процесса ($ CMD &).
  • В главном script есть цикл для отслеживания порожденной команды каждые несколько секунд. Цикл также перекликается с некоторыми сообщениями в stdout, указывающими ход работы script.
  • Выход из цикла при завершении команды.
  • Захватите и сообщите код выхода созданного процесса.

Может ли кто-нибудь дать мне указатели, чтобы выполнить это?

Ответ 1

1: В bash, $! хранится PID последнего фонового процесса, который был выполнен. В любом случае, это скажет вам, какой процесс контролировать.

4: wait <n> ожидает завершения процесса с идентификатором (он будет блокироваться до тех пор, пока процесс не завершится, поэтому вы не захотите называть это, пока не убедитесь, что процесс завершен). После возврата wait код возврата процесса возвращается в переменной $?

2, 3: ps или ps | grep " $! " могут рассказать вам, продолжает ли процесс. Вам решать, как понимать результат и решать, насколько близко он заканчивается. (ps | grep не является идиот-доказательством.Если у вас есть время, вы можете найти более надежный способ узнать, продолжает ли процесс).

Здесь скелет 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
my_status=$?
echo The exit status of the process was $my_status

Ответ 2

Вот как я решил это, когда у меня была такая же потребность:

# 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 finnish, will take max 14s
for p in $pids; do
        if wait $p; then
                echo "Process $p success"
        else
                echo "Process $p fail"
        fi
done

Ответ 3

#/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 $?

Ответ 4

Как я вижу, почти все ответы используют внешние утилиты (в основном ps) для опроса состояния фонового процесса. Существует еще одно решение для решения симуляций, захватывающее сигнал SIGCHLD. В обработчике сигнала необходимо проверить, какой дочерний процесс был остановлен. Это можно сделать с помощью kill -0 <PID> встроенного (универсального) или проверки существования каталога /proc/<PID> (для Linux) или с помощью встроенного jobs (. jobs -l также сообщает pid. В этом случае 3-е поле вывода может быть Stopped | Running | Done | Exit.).

Вот мой пример.

Запущенный процесс называется loop.sh. Он принимает -x или число в качестве аргумента. Для -x выходы с кодом выхода 1. Для числа он ждет num * 5 секунд. Через каждые 5 секунд выводится PID.

Процесс запуска запускается 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

Подробнее: Запуск процесса из bash script не удалось

Ответ 5

Я бы немного изменил ваш подход. Вместо того чтобы проверять каждые несколько секунд, если команда все еще жива и сообщается о сообщении, у вас есть другой процесс, который каждые несколько секунд сообщает, что команда все еще работает, а затем убивает этот процесс, когда команда завершается. Например:

#!/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

Ответ 6

Простой пример, аналогичный приведенным выше решениям. Это не требует мониторинга выхода любого процесса. Следующий пример использует хвост для вывода результатов.

$ 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

Используйте хвост для выполнения выходного процесса и завершите работу, когда процесс завершен.

$ 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

Ответ 7

pid фонового дочернего процесса хранится в $!. Вы можете хранить все дочерние процессы дочерних процессов в массиве, например. СОИД [].

wait [-n] [jobspec or pid …]

Подождите, пока дочерний процесс, указанный каждым идентификатором процесса или заданием спецификации задания, не выйдет и не вернет статус завершения последней команды, ожидающей. Если задана спецификация задания, все процессы в задании ждут. Если аргументы не заданы, ожидаются все текущие дочерние процессы, а статус возврата равен нулю. Если включена опция -n, ожидание ожидает завершения любого задания и возврата его статуса выхода. Если ни jobspec, ни pid не задают активный дочерний процесс оболочки, статус возврата равен 127.

Используйте команду wait, вы можете дождаться завершения всех дочерних процессов, между тем вы можете получить статус завершения каждого дочернего процесса и сохранить статус в STATUS []. Затем вы можете сделать что-то в зависимости от состояния.

Я пробовал следующий код, и он работает хорошо.

#!/bin/bash

# start 3 child processes concurrently, and store each pid into 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 exit code into 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 "failed"
  else
    echo "finish"
  fi
  ((i+=1))
done

Ответ 8

Другое решение - отслеживать процессы через файловую систему proc (безопаснее, чем ps/grep combo); при запуске процесса у него есть соответствующая папка в /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
....

Теперь вы можете использовать переменную $exit_status, как вам нравится.

Ответ 9

Это может выходить за рамки вашего вопроса, однако, если вас беспокоит длительность процессов, вы можете быть заинтересованы в проверке состояния запуска фоновых процессов через промежуток времени. Достаточно легко проверить, какие дочерние 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

который выводит:

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

Примечание. Вы можете изменить $pids на строковую переменную, а не на массив, чтобы упростить вещи, если хотите.

Ответ 10

С помощью этого метода ваш script не должен ждать фоновый процесс, вам нужно будет только следить за временным файлом для статуса выхода.

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

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

теперь ваш script может делать что-нибудь еще, пока вам просто нужно следить за содержимым retFile (он может также содержать любую другую информацию, которую вы хотите, как время выхода).

PS.: btw, я закодированное мышление в bash

Ответ 11

У команды была такая же потребность в удаленном SSH-исполнении script, который был отключен после 25 минут бездействия. Вот решение, в котором цикл контроля проверяет фоновый процесс каждую секунду, но печатает только каждые 10 минут, чтобы отключить таймаут бездействия.

command &
pid=$!

# Wait on a background job completion. Print to stdout every 10 minutes to suppress inactivity timeouts.
declare -i elapsed=0
while ps -p ${pid} >/dev/null; do
  sleep 1
  (( ++elapsed % 600 == 0 )) && echo "Waiting for the completion of the script. ${elapsed}s and counting ..."
done

# Return the exit code of the terminated background process. This works in Bash 4.4 despite what Bash docs say:
# "If neither jobspec nor pid specifies an active child process of the shell, the return status is 127."
wait ${pid}