J'ai une commande CMD appelée depuis mon script shell bourne principal qui prend une éternité.

Je souhaite modifier le script comme suit:

  1. Exécutez la commande CMD en parallèle en tant que processus d'arrière-plan ($ CMD &).
  2. Dans le script principal, ayez une boucle pour surveiller la commande générée toutes les quelques secondes. La boucle renvoie également certains messages à stdout indiquant la progression du script.
  3. Quittez la boucle lorsque la commande générée se termine.
  4. Capturez et signalez le code de sortie du processus généré.

Quelqu'un peut-il me donner des conseils pour accomplir cela?

111
user1116725 3 nov. 2019 à 19:16

9 réponses

Cela peut aller au-delà de votre question, mais si vous êtes préoccupé par la durée des processus, vous pouvez être intéressé par la vérification de l'état des processus en arrière-plan après un intervalle de temps. Il est assez facile de vérifier quels PID enfants sont toujours en cours d'exécution à l'aide de pgrep -P $$, mais j'ai trouvé la solution suivante pour vérifier l'état de sortie de ces PID qui ont déjà expiré:

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

qui génère:

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

Remarque: Vous pouvez remplacer $pids par une variable de chaîne plutôt que par un tableau pour simplifier les choses si vous le souhaitez.

0
errant.info 2 mai 2013 à 04:01

Avec cette méthode, votre script n'a pas à attendre le processus d'arrière-plan, vous n'aurez qu'à surveiller un fichier temporaire pour l'état de sortie.

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

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

maintenant, votre script peut faire autre chose alors que vous n'avez qu'à surveiller le contenu de retFile (il peut également contenir toute autre information que vous souhaitez comme l'heure de sortie).

PS .: btw, j'ai codé la pensée en bash

0
Aquarius Power 26 févr. 2016 à 23:16
#/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
funroll 20 janv. 2015 à 17:32

Voici comment je l'ai résolu quand j'avais un besoin similaire:

# 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
44
Bjorn 9 avril 2015 à 09:53

Notre équipe avait le même besoin avec un script exécuté à distance par SSH qui expirait après 25 minutes d'inactivité. Voici une solution avec la boucle de surveillance vérifiant le processus d'arrière-plan toutes les secondes, mais n'imprimant que toutes les 10 minutes pour supprimer un délai d'inactivité.

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 22 févr. 2018 à 13:41

Une autre solution consiste à surveiller les processus via le système de fichiers proc (plus sûr que le combo ps / grep); lorsque vous démarrez un processus, il a un dossier correspondant dans / proc / $ pid, donc la solution pourrait être

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

Vous pouvez maintenant utiliser la variable $ exit_status comme vous le souhaitez.

1
Искрен Хаджинедев 12 juin 2013 à 09:01

Un exemple simple, similaire aux solutions ci-dessus. Cela ne nécessite la surveillance d'aucune sortie de processus. L'exemple suivant utilise tail pour suivre la sortie.

$ 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

Utilisez tail pour suivre la sortie du processus et quitter lorsque le processus est terminé.

$ 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 2 avril 2013 à 18:55

Je changerais légèrement votre approche. Plutôt que de vérifier toutes les quelques secondes si la commande est toujours vivante et de signaler un message, ayez un autre processus qui signale toutes les quelques secondes que la commande est toujours en cours d'exécution, puis tuez ce processus lorsque la commande se termine. Par exemple:

#!/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
5
William Pursell 15 févr. 2012 à 14:25

1: En bash, $! contient le PID du dernier processus d'arrière-plan qui a été exécuté. De toute façon, cela vous dira quel processus surveiller.

4: wait <n> attend que le processus avec le PID <n> soit terminé (il se bloquera jusqu'à la fin du processus, donc vous ne voudrez peut-être pas l'appeler tant que vous n'êtes pas sûr que le processus est terminé), puis retourne le code de sortie du processus terminé.

2, 3: ps ou ps | grep " $! " peuvent vous dire si le processus est toujours en cours. C'est à vous de comprendre la sortie et de décider de sa fin. (ps | grep n'est pas à l'abri des idiots. Si vous avez le temps, vous pouvez trouver un moyen plus robuste de dire si le processus est toujours en cours).

Voici un script squelette:

# 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
# The variable $? always holds the exit code of the last command to finish.
# Here it holds the exit code of $my_pid, since wait exits with that code. 
my_status=$?
echo The exit status of the process was $my_status
109
ntc2 5 mars 2019 à 17:42