it-swarm.com.de

Holen Sie sich den Exit-Code eines Hintergrundprozesses

Ich habe einen Befehl, den CMD von meinem Haupt-Bourne-Shell-Skript aufgerufen hat, das für immer dauert.

Ich möchte das Skript wie folgt ändern:

  1. Führen Sie den Befehl CMD parallel als Hintergrundprozess aus ($ CMD &).
  2. Verfügen Sie im Hauptskript über eine Schleife, um den erzeugten Befehl alle paar Sekunden zu überwachen. Die Schleife gibt auch einige Meldungen an stdout aus, die den Fortschritt des Skripts anzeigen.
  3. Beenden Sie die Schleife, wenn der erzeugte Befehl beendet wird.
  4. Erfassen und melden Sie den Beendigungscode des erzeugten Prozesses.

Kann mir jemand Hinweise geben, um dies zu erreichen?

102
bob

1: In Bash enthält $! die PID des letzten Hintergrundprozesses, der ausgeführt wurde. Das wird Ihnen sowieso sagen, welcher Prozess überwacht werden soll.

4: wait <n> wartet, bis der Prozess mit der ID abgeschlossen ist (er wird blockiert, bis der Prozess abgeschlossen ist. Möglicherweise möchten Sie dies nicht aufrufen, bis Sie sicher sind, dass der Prozess abgeschlossen ist). Nachdem wait zurückgegeben wurde, wird der Beendigungscode des Prozesses in der Variablen $? zurückgegeben.

2, 3: ps oder ps | grep " $! " kann Ihnen sagen, ob der Prozess noch läuft. Es liegt an Ihnen, wie Sie die Ausgabe verstehen und entscheiden, wie nahe sie am Ende ist. (ps | grep ist nicht idiotensicher. Wenn Sie Zeit haben, können Sie auf zuverlässigere Weise feststellen, ob der Prozess noch läuft.).

Hier ist ein Skelett-Skript:

# 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
104
mob

So habe ich es gelöst, als ich ein ähnliches Bedürfnis hatte:

# 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
40
Bjorn
#/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 $?
7
Abu Aqil

Wie ich sehe, verwenden fast alle Antworten externe Dienstprogramme (meistens ps), um den Status des Hintergrundprozesses abzufragen. Es gibt eine mehr Unixesh-Lösung, die das SIGCHLD-Signal erfasst. Im Signalhandler muss geprüft werden, welcher Kindprozess gestoppt wurde. Dies kann durch kill -0 <PID> eingebaut (universell) oder durch Überprüfen des Vorhandenseins des /proc/<PID>-Verzeichnisses (Linux-spezifisch) oder durch Verwendung der eingebauten jobs ( bash specific. jobs -l meldet auch die PID. In diesem Fall das 3. Feld der Ausgabe kann angehalten werden | Ausführen | Fertig | Beenden.).

Hier ist mein Beispiel.

Der gestartete Prozess heißt loop.sh. Es akzeptiert -x oder eine Zahl als Argument. Für -x gibt es Exits mit Exitcode 1. Für eine Zahl wartet sie auf * 5 Sekunden. Alle 5 Sekunden gibt es die PID aus.

Der Launcher-Prozess heißt 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

Weitere Informationen finden Sie unter: Das Starten eines Prozesses vom Bash-Skript ist fehlgeschlagen

7
TrueY

Die PID eines im Hintergrund befindlichen untergeordneten Prozesses wird in $ gespeichert! . Sie können die Pids aller untergeordneten Prozesse in einem Array speichern, z. PIDS [] .

wait [-n] [jobspec or pid …]

Warten Sie, bis der unter jeder Prozess-ID pid oder Jobspezifikation jobspec angegebene untergeordnete Prozess beendet ist, und geben Sie den Beendigungsstatus des letzten Befehls zurück, auf den der Befehl gewartet hat. Wenn eine Jobspezifikation angegeben wird, wird auf alle Prozesse im Job gewartet. Wenn keine Argumente angegeben werden, wird auf alle derzeit aktiven untergeordneten Prozesse gewartet und der Rückgabestatus ist Null. Wenn die Option -n angegeben wird, wartet wait, bis ein Job beendet wird, und gibt seinen Beendigungsstatus zurück. Wenn weder jobspec noch pid einen aktiven untergeordneten Prozess der Shell angeben, ist der Rückgabestatus 127.

Mit dem Befehl wait können Sie warten, bis alle untergeordneten Prozesse abgeschlossen sind. In der Zwischenzeit können Sie den Beendigungsstatus aller untergeordneten Prozesse über $ abrufen. und den Status in STATUS [] speichern. Dann können Sie je nach Status etwas tun.

Ich habe die folgenden 2 Lösungen ausprobiert und sie laufen gut. solution01 ist prägnanter, während solution02 etwas kompliziert ist.

lösung01

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

lösung02

#!/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
5
Terry wang

Ich würde deine Einstellung etwas ändern. Anstatt alle paar Sekunden zu überprüfen, ob der Befehl noch aktiv ist, und eine Meldung zu melden, müssen Sie einen anderen Prozess melden, der alle paar Sekunden meldet, dass der Befehl noch ausgeführt wird, und den Prozess abbrechen, wenn der Befehl abgeschlossen ist. Zum Beispiel:

 #!/bin/sh 

 cmd () {sleep 5; Ausfahrt 24; } 

 cmd & # Führen Sie den lang laufenden Prozess aus 
 pid = $! # Die pid aufzeichnen 

 # Spawn einen Prozess, der ständig meldet, dass der Befehl noch ausgeführt wird 
 Während das Echo "$ (date): $ pid noch läuft"; schlafe 1; done & 
 echoer = $! 

 # Legen Sie einen Trap fest, um den Reporter abzubrechen, wenn der Prozess abgeschlossen ist 
 trap 'kill $ echoer' 0 

 # Warten Sie, bis der Prozess abgeschlossen ist 
 wenn warten $ pid; dann
 echo "cmd war erfolgreich" 
 sonst 
 echo "cmd FAILED !! (zurückgegebenes $?)" 
 fi 
4
William Pursell

Unser Team hatte das gleiche Bedürfnis mit einem Remote-SSH-ausgeführten Skript, das nach 25 Minuten Inaktivität ausfiel. Hier ist eine Lösung, bei der die Überwachungsschleife den Hintergrundprozess jede Sekunde überprüft, aber nur alle 10 Minuten gedruckt wird, um ein Inaktivitätszeitlimit zu unterdrücken.

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}
3
DKroot

Ein einfaches Beispiel, ähnlich den obigen Lösungen. Dies erfordert keine Überwachung der Prozessausgabe. Das nächste Beispiel verwendet tail, um der Ausgabe zu folgen.

$ 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

Verwenden Sie tail, um die Prozessausgabe zu verfolgen und zu beenden, wenn der Prozess abgeschlossen ist.

$ 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
2
Darren Weber

Eine andere Lösung besteht darin, Prozesse über das proc-Dateisystem zu überwachen (sicherer als die Kombination ps/grep). Wenn Sie einen Prozess starten, befindet sich in/proc/$ pid ein entsprechender Ordner. Die Lösung könnte also sein

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

Jetzt können Sie die Variable $ exit_status nach Belieben verwenden.

Bei dieser Methode muss Ihr Skript nicht auf den Hintergrundprozess warten. Sie müssen lediglich eine temporäre Datei auf den Exitstatus überwachen.

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

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

jetzt kann Ihr Skript alles andere tun, während Sie einfach den Inhalt von retFile überwachen müssen (es kann auch andere Informationen enthalten, die Sie wünschen, z. B. die Beendigungszeit).

PS .: Übrigens, ich habe in bash das Denken codiert

0
Aquarius Power

Dies kann über Ihre Frage hinausgehen. Wenn Sie jedoch besorgt sind, wie lange die Prozesse ausgeführt werden, können Sie nach einer bestimmten Zeit den Status der laufenden Hintergrundprozesse überprüfen. Es ist leicht genug zu prüfen, welche untergeordneten PIDs noch mit pgrep -P $$ ausgeführt werden. Allerdings habe ich die folgende Lösung gefunden, um den Exit-Status der bereits abgelaufenen PIDs zu überprüfen:

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

welche Ausgänge:

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

Hinweis: Sie können $pids in eine String-Variable und nicht in ein Array ändern, um die Dinge zu vereinfachen, wenn Sie möchten. 

0
errant.info