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

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

Я придумал настройку, которая, как я думаю, это сделает, но она не работает:

#!/bin/bash

echo "Launching a background process that may take hours to finish.."
myprog &
pid=$!
retval=
##At this time pid should hold the process id of myprog
echo "pid=${pid}"

{
    ##check if the process is still running
    psl=$(ps -f -p ${pid} | grep -E "\bmyprog\b")
    killit=
    while [[ ! -z ${psl} ]]
    do
        ##if a file named "kill_flag" is detected, kill the process
        if [[ -e "kill_flag" ]]
        then
            killit=YES
            break
        fi
        #check every 3 seconds
        sleep 3
        psl=$(ps -f -p ${pid} | grep -E "\bmyprog\b")
    done

    ##killit not set, normal exit, read from fd5
    if [[ -z ${killit} ]]
    then
        read <&5 retval
  else
    ##kill here, the wait will return and the sub process ends
    kill ${pid}
  fi

} 5< <( wait ${pid} > /dev/null 2>&1; echo $? )

echo "retval=$retval"

При первом запуске все кажется в порядке, я могу убить процесс с помощью touch kill_flag, в противном случае он ждет, пока myprog нормально завершит работу. Но потом я заметил, что всегда получаю -1 в retval. myprog возвращает 0, что подтверждается нормальным запуском. Дальнейшее расследование показало, что echo $?часть " " была выполнена сразу после запуска скрипта, а не после завершения команды wait. Мне интересно, что здесь происходит. Я новичок в bash.

решение1

waitможет работать только с потомками текущего процесса оболочки. Подоболочка, которая интерпретирует код внутри, <(...)не может ждать родственный процесс.

Ожидание должно быть выполнено тем же процессом оболочки, который запустил pid. Вместо zshэтого bash(здесь предполагается, что нет других запущенных фоновых заданий):

cmd & pid=$!
while (($#jobstates)) {
  [[ -e killfile ]] && kill $pid
  sleep 3
}
wait $pid; echo $?

решение2

Придумал работоспособную версию:

#!/bin/bash
export retval=
##At this time pid should hold the process id of myprog
{
    ##This is the subshell that launched and monitoring myprog
    subsh=$!

    ##Since myprog is probably the only child process of this subsh, it should be pretty safe
    pid=$(ps -f --ppid ${subsh} | grep -E "\bmyprog\b" | gawk '{print $2}' )
    ##check if the process is still running
    psl=$(ps -f -p ${pid} | grep -E "\bmyprog\b")
    killit=
    while [[ ! -z ${psl} ]]
    do
        ##if a file named "kill_flag" is detected, kill the process
        if [[ -e "kill_flag" ]]
        then
            killit=YES
            break
        fi
        #check every 3 seconds
        sleep 3
        psl=$(ps -f -p ${pid} | grep -E "\bmyprog\b")
    done

    ##killit not set, normal exit, read from fd5
    if [[ -z ${killit} ]]
    then
        read <&5 retval
  else
    ##kill here, the wait will return and the sub process ends
    kill ${pid}
  fi

} 5< <( myprog >>logfile 2>&1; echo $? )

echo "retval=$retval"

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

Связанный контент