103 votes

Capturer stdout et stderr dans des variables différentes

Est-il possible de stocker ou de capturer stdout et stderr en différentes variables sans utiliser de fichier temporaire ? Pour l'instant, je fais ceci pour obtenir stdout en out et stderr dans err en cours d'exécution some_command mais je mais j'aimerais éviter le fichier temporaire.

error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< $error_file)
rm $error_file

14voto

ormaaj Points 3287

Jonathan a la réponse . Pour référence, il s'agit de l'astuce ksh93. (nécessite une version non ancienne).

function out {
    echo stdout
    echo stderr >&2
}

x=${ { y=$(out); } 2>&1; }
typeset -p x y # Show the values

produit

x=stderr
y=stdout

En ${ cmds;} est juste une substitution de commande qui ne crée pas de sous-shell. Les commandes sont exécutées dans l'environnement actuel du shell. L'espace au début est important ( { est un mot réservé).

Le stderr du groupe de commandes interne est redirigé vers le stdout (afin qu'il s'applique à la substitution interne). Ensuite, le stdout de out est affecté à y et le stderr redirigé est capturé par x sans la perte habituelle de y au sous-shell d'une substitution de commande.

Ce n'est pas possible dans d'autres shells, car toutes les constructions qui capturent la sortie nécessitent de placer le producteur dans un sous-shell, qui dans ce cas, inclurait l'affectation.

mettre à jour : Maintenant aussi supporté par mksh.

10voto

Jacques Gaudin Points 4478

C'est un diagramme montrant comment la méthode de @madmurphy solution très soignée travaux.

Diagram of @madmurhy's solution

Et une version en retrait de la phrase :

catch() {
  {
      IFS=$'\n' read -r -d '' "$out_var";
      IFS=$'\n' read -r -d '' "$err_var";
      (IFS=$'\n' read -r -d '' _ERRNO_; return ${_ERRNO_});
  }\
  < <(
    (printf '\0%s\0%d\0' \
      "$(
        (
          (
            (
              { ${3}; echo "${?}" 1>&3-; } | tr -d '\0' 1>&4-
            ) 4>&2- 2>&1- | tr -d '\0' 1>&4-
          ) 3>&1- | exit "$(cat)"
        ) 4>&1-
      )" "${?}" 1>&2
    ) 2>&1
  )
}

5voto

mncl Points 141

N'ayant pas aimé l'eval, voici une solution qui utilise des astuces de redirection pour capturer la sortie du programme dans une variable, puis analyse cette variable pour en extraire les différents composants. Le drapeau -w définit la taille des morceaux et influence l'ordre des messages std-out/err dans le format intermédiaire. 1 donne une résolution potentiellement élevée au prix d'une surcharge.

#######                                                                                                                                                                                                                          
# runs "$@" and outputs both stdout and stderr on stdin, both in a prefixed format allowing both std in and out to be separately stored in variables later.                                                                  
# limitations: Bash does not allow null to be returned from subshells, limiting the usefullness of applying this function to commands with null in the output.                                                                   
# example:                                                                                                                                                                                                                       
#  var=$(keepBoth ls . notHere)                                                                                                                                                                                                  
#  echo ls had the exit code "$(extractOne r "$var")"                                                                                                                                                                            
#  echo ls had the stdErr of "$(extractOne e "$var")"                                                                                                                                                                            
#  echo ls had the stdOut of "$(extractOne o "$var")"                                                                                                                                                                            
keepBoth() {                                                                                                                                                                                                                     
  (                                                                                                                                                                                                                              
    prefix(){                                                                                                                                                                                                                    
      ( set -o pipefail                                                                                                                                                                                                          
        base64 -w 1 - | (                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
          while read c                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
          do echo -E "$1" "$c"                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
          done                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
        )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
      )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    ( (                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
        "$@" | prefix o >&3                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
        echo  ${PIPESTATUS[0]} | prefix r >&3                                                                                                                                                                                                                                                                                                                                                                                                                                                           
      ) 2>&1 | prefix e >&1                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
    ) 3>&1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
  )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

extractOne() { # extract                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
  echo "$2" | grep "^$1" | cut --delimiter=' ' --fields=2 | base64 --decode -                                                                                                                                                                                                                                                                                                                                                                                                                           
}

4voto

Tino Points 529

Pour le bénéfice du lecteur voici une solution utilisant tempfile s.

La question n'était pas d'utiliser tempfile s. Cependant, cela peut être dû à la pollution indésirable de l'eau. /tmp/ avec tempfile au cas où le shell meurt. En cas de kill -9 un peu de trap 'rm "$tmpfile1" "$tmpfile2"' 0 ne fait pas feu.

Si vous êtes dans une situation où vous pouvez utiliser tempfile mais je veux ne laissez jamais de débris derrière vous voici une recette.

Encore une fois, il est appelé catch() (comme mon autre réponse ) et possède la même syntaxe d'appel :

catch stdout stderr command args..

# Wrappers to avoid polluting the current shell's environment with variables

: catch_read returncode FD variable
catch_read()
{
eval "$3=\"\`cat <&$2\`\"";
# You can use read instead to skip some fork()s.
# However read stops at the first NUL byte,
# also does no \n removal and needs bash 3 or above:
#IFS='' read -ru$2 -d '' "$3";
return $1;
}
: catch_1 tempfile variable comand args..
catch_1()
{
{
rm -f "$1";
"${@:3}" 66<&-;
catch_read $? 66 "$2";
} 2>&1 >"$1" 66<"$1";
}

: catch stdout stderr command args..
catch()
{
catch_1 "`tempfile`" "${2:-stderr}" catch_1 "`tempfile`" "${1:-stdout}" "${@:3}";
}

Ce qu'il fait :

  • Il crée deux tempfile pour stdout y stderr . Cependant, il les supprime presque immédiatement, de sorte qu'ils ne sont présents que pendant un temps très court.

  • catch_1() attrape stdout (FD 1) dans une variable et déplace stderr a stdout de sorte que le prochain ("gauche") catch_1 peut attraper ça.

  • Traitement dans catch se fait de droite à gauche, donc la gauche catch_1 est exécuté en dernier et attrape stderr .

Le pire qui puisse arriver, c'est que certains fichiers temporaires s'affichent sur /tmp/ mais ils sont toujours vides dans ce cas. (Ils sont supprimés avant d'être remplis). En général, cela ne devrait pas être un problème, car sous Linux, tmpfs supporte environ 128K fichiers par Go de mémoire principale.

  • La commande donnée peut accéder à toutes les variables locales du shell et les modifier. Vous pouvez donc appeler une fonction shell qui a des effets secondaires !

  • Cela ne fait que bifurquer deux fois pour le tempfile appeler.

Bugs :

  • Manque une bonne gestion des erreurs en cas tempfile échoue.

  • Cela fait l'habituel \n le retrait de la coquille. Voir le commentaire dans catch_read() .

  • Vous ne pouvez pas utiliser le descripteur de fichier 66 pour envoyer des données à votre commande. Si vous en avez besoin, utilisez un autre descripteur pour la redirection, par exemple 42 (Notez que les coquilles très anciennes n'offrent que des FD jusqu'à 9).

  • Il ne peut pas gérer les octets NUL ( $'\0' ) en stdout y stderr . (NUL est simplement ignoré. Pour le read variante tout ce qui se trouve derrière un NUL est ignoré).

FYI :

  • Unix nous permet d'accéder aux fichiers supprimés, à condition que vous conserviez une référence à ces fichiers (par exemple, une poignée de fichier ouverte). De cette façon, nous pouvons les ouvrir puis les supprimer.

2voto

Jonathan Leffler Points 299946

En bref, je pense que la réponse est "non". La capture $( ... ) ne capture que la sortie standard dans la variable ; il n'y a pas de moyen de capturer l'erreur standard dans une variable séparée. Donc, ce que vous avez est à peu près aussi propre que possible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X