2009-10-15 4 views
86

У меня есть команда CMD, вызываемая из моего основного сценария оболочки bourne, который берет навсегда.shell - получить код выхода фонового процесса

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

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

Может ли кто-нибудь дать мне указатели для этого?

+1

... а победитель? – TrueY

ответ

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

Вот трюк, чтобы избежать «grep -v». Вы можете ограничить поиск в начале строки: 'grep '^' $ pid' Плюс, вы можете сделать' ps p $ pid -o pid = ', так или иначе. Кроме того, 'tail -f' не закончится, пока вы его не убьете, поэтому я не думаю, что это очень хороший способ продемонстрировать это (по крайней мере, не указывая на это). Вы можете перенаправить вывод своей команды 'ps' в'/dev/null' или она будет отображаться на экране на каждой итерации. Ваш 'exit' заставляет' wait' пропускаться - вероятно, это будет 'break'. Но не являются ли 'while' /' ps' и 'wait' избыточными? –

+5

Почему все забывают о 'kill -0 $ pid'? Он фактически не посылает никакого сигнала, а только проверяет, что процесс жив, используя встроенную оболочку вместо внешних процессов. – ephemient

+1

Потому что вы можете убить только свой процесс: 'bash: kill: (1) - Операция не разрешена' –

89

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

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

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

Вот скелет сценария:

# 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 
+10

'ps -p $ my_pid -o pid =' ни 'grep' не требуется. –

+1

@ Деннис Уильямсон 'ps' имеет много вкусов. Ваш звонок не работает для меня, но 'ps -p $ my_pid' делает. Ваша большая точка, что 'grep' не нужна, является правильной. – mob

+0

Хммм .. на самом деле я не могу найти хороший способ избежать grep на Cygwin. 'ps -p $ pid' всегда имеет статус выхода 0, существует ли $ pid или нет. Я мог бы сказать что-то вроде 'while ['ps -p $ pid | wc -l '\> 1] ', но это вряд ли является улучшением ... – mob

4

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

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

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

$ 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 
0

Это может быть выходящим за рамками вашего вопроса, однако, если вы беспокоитесь о продолжительности времени выполняющихся процессов для, вы можете быть заинтересованы в проверке состояния запущенных фоновых процессов после интервала времени.Это достаточно легко проверить, какой ребенок ИДП по-прежнему работает с использованием pgrep -P $$, однако я придумал следующее решение, чтобы проверить состояние выхода из тех PIDs, которые уже истекли:

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 на строковую переменную, а не на массив, чтобы упростить ситуацию, если хотите.

1

Другим решением является мониторинг процессов через файловую систему 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 
.... 

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

+0

Не работает в bash? 'Синтаксическая ошибка:« else »неожиданно (ожидается« сделано »)' – benjaoming

6

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

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

Запущенный процесс называется 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 

Для более подробного объяснения см: Starting a process from bash script failed

+0

Поскольку мы говорим о Bash, цикл for мог бы быть записан как: 'для i в $ {! Pids [@]};' с использованием расширения параметров. – PlasmaBinturong

31

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

# 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 
+0

Мне нравится этот подход. –

+0

Спасибо! Мне кажется, это самый простой подход. –

+0

Очень хороший способ решить проблему! –

0

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

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

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

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

PS .: Кстати, я закодированное мышление в Баше

2

ИДПА из дочернего процесса в фоновом режиме сохраняются в $!. Вы можете сохранить все дочерние процессы дочерних процессов в массив, например. PIDS [].

wait [-n] [jobspec or pid …] 

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

Использование ждать команды, которую вы можете ждать все дочерние процессы отделки, между тем вы можете получить статус выхода каждого ребенок процессов и магазин статус в СТАТУС []. Затем вы можете сделать что-то в зависимости от статуса.

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

#!/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's 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 
+0

Я пробовал и доказал, что он работает хорошо. Вы можете прочитать мое объяснение в коде. –

+0

Прочитайте «[Как написать хороший ответ?] (Https://stackoverflow.com/help/how-to-answer)», где вы найдете следующую информацию: ** ... попробуйте упомянуть любой ограничения, допущения или упрощения в вашем ответе. Сокращение допустимо, но более полные объяснения лучше. ** Таким образом, вы отвечаете приемлемо, но у вас есть гораздо лучшие шансы получить upvotes, если вы можете уточнить проблему и свое решение. :-) –

1

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

long_running.sh & 
pid=$! 

# Wait on a background job completion. Query status every 10 minutes. 
declare -i elapsed=0 
# `ps -p ${pid}` works on macOS and CentOS. On both OSes `ps ${pid}` works as well. 
while ps -p ${pid} >/dev/null; do 
    sleep 1 
    if ((++elapsed % 600 == 0)); then 
    echo "Waiting for the completion of the main script. $((elapsed/60))m and counting ..." 
    fi 
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} 
Смежные вопросы