Récupérer une variable d'un sous shell en bash

Daniel Caillibaud ml at lairdutemps.org
Mon Dec 7 11:59:26 CET 2015


Le 04/12/15 à 18:21, Marc Chantreux <khatar at phear.org> a écrit :

MC> hello,
MC> 
MC> > comment remonter mon code de sortie au shell parent ?
MC> 
MC> excellente formulation! du coup la lecture d'un document expliquant le
MC> fonctionnement des processus unix te donnera la réponse: tu ne peux
MC> simplement pas. 
MC> 
MC> Il faut donc utiliser un socket unix ou internet, un fichier ou
MC> n'importe quel autre mecanisme qui permettent aux process de s'échanger
MC> de l'info. 

Bon ben pas la peine que je cherche davantage ;-)

MC> > Si oui, je cherche un moyen de récupérer le code de sortie d'une commande que je fais
MC> > tourner en arrière plan.  
MC> 
MC> comment peux-tu avoir un code de retour d'un truc qui tourne a moins de lire dans le
MC> futur ? 

J'attends simplement que le futur devienne maintenant ;-)


MC> > J'ai essayé 
MC> >   $commande && exit $? &  
MC> 
MC> oula ...  reprennons:
MC> 
MC>     commande;
MC>     exit $?
MC> 
MC> et 
MC> 
MC>     commande;
MC> 
MC> font la meme chose (parceque le shell renvoie la valeur de la
MC> derniere expression evaluée (soit $?)). idem pour les fonctions
MC> 
MC>     foo () { commande; return $?  }
MC> 
MC> et 
MC> 
MC>     foo () { commande }

Pour une fonction c'est évident, j'espérais bêtement que mon exit sortirait du script
principal, alors qu'il ne sort que du bg (ce qu'il aurait fait de toute façon).

Parce que dans le script principal, 
  commande;
  # la suite
n'est pas la même chose que 
  commande; exit $?
  # la suite
car dans le 2e cas la suite n'est jamais exécutée.

(c'est évident, juste pour expliquer ma tentative de exit $? qui parait vraiment stupide)

MC> mais donc... que renvoie une expression qui passe une commande
MC> en bg ? 
MC> 
MC>     foo () { return 5 }
MC>     foo
MC>     perl -E'sleep 1; exit 10'& 
MC>     echo $?  # 0 donc $? veut juste dire que & a marché
MC>     foo
MC>     wait
MC>     echo $?  # 0 ... donc $? veut dire que wait a bien fonctionné
MC> 
MC> dans zsh tu as le $pipe_status mais j'ai jamais utilisé.
MC> 
MC> donc le plus simple a mon avis est de passer par des fichiers
MC> 
MC> pstatus=$( mktemp -d ~/process-status-$(date +%F_%X_%s_XXXX ))
MC> { montruc --avec --plein de parametres
MC>     <<< $? > $pstatus/montruc } &
MC> 
MC> du coup tu peux voir si le process est fini dans ton script
MC> 
MC> test -f $pstatus/montruc && echo "job done" 
MC> 
MC> et comme c'est dans des fichiers, tu peux facilement surveille l'état
MC> d'avancement des jobs meme apres avoir quitté ton script.

Ok, c'est clair, merci Marc.


Je m'étais contenté d'un ok|KO, avec

trap "exit $OK" RTMIN
trap "exit $KO" RTMAX
$commande && kill -RTMIN $$ || kill -RTMAX $$ &

mais je viens d'ajouter le vrai code de sortie en passant par un fichier, voici le script complet 
(qui veut mettre en pause un process trop gourmand en I/O, que ionice ne suffit pas à calmer et 
qui exlose mon load), si y'en a que ça intéressent. 
Les critiques sont bienvenues, je viens d'ajouter (en plus du bon code d'erreur) la partie de reprise
pour rsync que je n'ai pas encore testée (pour cp et rm ça marche bien)


#!/bin/bash
# wrapper pour lancer une commande et la mettre en pause si le load monte trop

# les bornes du load x100 pour arrêter / reprendre
MAX=150
MIN=100
# intervalle de temps en secondes pour la mesure du load et décision d'arrêter / reprendre
PAUSE=10
# durée de la reprise brève pour rsync
RESUME=5
# nb d'intervalle à attendre avant de lancer un resume
NBCYCLES=10

# pour la lisibilité
OK=0
K0=1

# affiche un message horodaté
function log() {
  echo "[$(date '+%F:%T')] $@"
}
# idem sur stderr
function logErr() {
  echo "[$(date '+%F:%T')] $@" >&2
}

# met à jour la variable state et répond ok si vide
function isLost() {
  state=$(ps -o s= $pid)
  [ -z "$state" ] && return $OK || return $KO
}

function exitOk() {
  log "fin ok $commande"
  exit $OK
}
function exitKO() {
  logErr "fin KO $commande"
  exit $(<$statusFile)
}
function end() {
  rm -f $statusFile
}

# pour être prévenu de la sortie on utilise ces signaux passés à $$ (le pid courant, identique en arrière plan)
trap exitOk RTMIN
trap exitKO RTMAX
# et pour nettoyer notre fichier temporaire
trap end EXIT

# on a des alias (symlinks) comme benice_cp pour préfixer les arguments reçu avec la commande cp
[ "$0" == "benice.sh" ] && commande="$@" || commande="${0#*benice_} $@"

# faut au moins /bin et /sbin dans notre PATH, pour les alias
[ "$PATH" == "${PATH/:\/sbin/}" ] && $PATH="$PATH:/sbin"
[ "$PATH" == "${PATH/:\/bin/}" ] && $PATH="$PATH:/bin"

statusFile=$(mktemp /tmp/${0}_XXXX)

# on lance la commande en arrière plan (et on sortira quand elle sera terminée avec son code de retour)
#echo "on lance $commande && kill -RTMIN $$ || kill -RTMAX $$ &"
$commande && kill -RTMIN $$ || (echo $? > $statusFile && kill -RTMAX $$) &

# le pid de la commande qui tourne en arrière plan
# ok, grep -v grep est quick&dirty, un pid=$(ps -o pid= -C "$commande") serait plus propre mais ça marche pas
pid=$(ps auxw|grep " $commande"|grep -v grep|awk '{print $2}')
state=""
isLost # pour init $state
i=0

#echo "pid $pid dans l'état $state avec les process benice :"
#ps auxwf |grep -2 [b]enice

# et on boucle avec une sonde toutes les $PAUSE secondes
while true
do
  load=$(cut -d " " -f 1 /proc/loadavg)
  # valeur entière (le /1) du load x100 
  load100=$(echo "$load * 100 / 1"|bc)
  isLost && sleep 1 && isLost && logErr "$0 tourne toujours alors que le pid $pid ne tourne plus" \
    && exit $KO
  
  # pour savoir si on a mis en pause le process on teste si l'état vaut T (en pause), 
  # sinon il peut valoir R (running) ou D (pour des attentes I/O) ou S (autre attente), man ps pour le détail
  if [ "$state" == "T" ]
  then
    # en pause
    if [ $load100 -lt $MIN ]
    then
      kill -CONT $pid && log "Reprise de la commande '$commande' (load x100 : $load100 état $state)"
    else
      i=$((i+1))
      if [ "$aliasName" == "rsync" && $i -gt $NBCYCLES ]
      then
        # on reprend brièvement pour pas couper la connexion rsync
        log "reprise brève de la commande '$commande' (load x100 : $load100)" \
          && kill -CONT $pid && sleep $RESUME && kill -STOP $pid && i=0
      fi
    fi
  else # D ou R
    [ $load100 -gt $MAX ] && kill -STOP $pid && i=0 \
      && log "Commande '$commande' mise en pause (load x100 : $load100 état $state)"
  fi
  sleep $PAUSE
done

logErr "Sortie anormale de $0"
exit $KO


-- 
Daniel

La pensée vole et les mots vont à pied. Voilà tout le drame de l'écrivain.
Green.


More information about the Shell mailing list