Get exit code of a background process

前端 未结 12 1732
别跟我提以往
别跟我提以往 2020-11-22 17:14

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

I want to modify the script as follows:

  1. Run the command CMD in parallel
相关标签:
12条回答
  • 2020-11-22 17:57

    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

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

    0 讨论(0)
  • 2020-11-22 17:59

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

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

    $ 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 讨论(0)
  • 2020-11-22 18:01
    #/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 $?
    
    0 讨论(0)
  • 2020-11-22 18:01

    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:

    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 $pids to a string variable rather than array to simplify things if you like.

    0 讨论(0)
  • 2020-11-22 18:02

    The pid of a backgrounded child process is stored in $!. You can store all child processes' pids into an array, e.g. 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.

    Use wait command 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.

    I have tried the following 2 solutions and they run well. solution01 is more concise, while solution02 is a little complicated.

    solution01

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

    #!/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
    
    0 讨论(0)
  • 2020-11-22 18:02

    Our team had the same need with a remote SSH-executed script which was timing out after 25 minutes of inactivity. Here is a solution with the monitoring loop checking the background process every second, but printing only every 10 minutes to suppress an inactivity timeout.

    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}
    
    0 讨论(0)
提交回复
热议问题