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

2voto

Eduardo Lucio Points 51

Et pourquoi pas... =D

GET_STDERR=""
GET_STDOUT=""
get_stderr_stdout() {
    GET_STDERR=""
    GET_STDOUT=""
    unset t_std t_err
    eval "$( (eval $1) 2> >(t_err=$(cat); typeset -p t_err) > >(t_std=$(cat); typeset -p t_std) )"
    GET_STDERR=$t_err
    GET_STDOUT=$t_std
}

get_stderr_stdout "command"
echo "$GET_STDERR"
echo "$GET_STDOUT"

2voto

Warbo Points 502

Une solution, qui est un peu compliquée mais peut-être plus intuitive que certaines des suggestions de cette page, est de baliser les flux de sortie, de les fusionner, et de les diviser ensuite en fonction des balises. Par exemple, nous pourrions marquer stdout avec un préfixe "STDOUT" :

function someCmd {
    echo "I am stdout"
    echo "I am stderr" 1>&2
}

ALL=$({ someCmd | sed -e 's/^/STDOUT/g'; } 2>&1)
OUT=$(echo "$ALL" | grep    "^STDOUT" | sed -e 's/^STDOUT//g')
ERR=$(echo "$ALL" | grep -v "^STDOUT")

```

Si vous savez que stdout et/ou stderr sont d'une forme restreinte, vous pouvez proposer une balise qui n'entre pas en conflit avec leur contenu autorisé.

1voto

calestyo Points 41

AVERTISSEMENT : PAS (encore ?) DE FONCTIONNEMENT !

La méthode suivante semble être une piste possible pour le faire fonctionner sans créer de fichiers temporaires et aussi sur POSIX sh seulement ; elle nécessite cependant base64 et à cause de l'encodage/décodage, elle peut ne pas être très efficace et utiliser aussi une "plus grande" mémoire.

  • Même dans le cas simple, cela échouerait déjà, lorsque la dernière ligne stderr n'a pas de nouvelle ligne. Cela peut être corrigé, au moins dans certains cas, en remplaçant exe par "{ exe ; echo >&2 ; }", c'est-à-dire en ajoutant une nouvelle ligne.
  • Le principal problème est cependant que tout semble racé. Essayez d'utiliser un exe comme :

    exe() { cat /usr/share/hunspell/de_DE.dic cat /usr/share/hunspell/en_GB.dic >&2 }

et vous verrez que, par exemple, des parties de la ligne encodée en base64 se trouvent en haut du fichier, d'autres à la fin, et le contenu non décodé du stderr au milieu.

Eh bien, même si l'idée ci-dessous ne peut pas être mise en œuvre (ce que je suppose), elle peut servir d'anti-exemple pour les personnes qui pourraient croire à tort qu'elle pourrait être mise en œuvre de cette manière.

Idée (ou anti-exemple) :

#!/bin/sh

exe()
{
        echo out1
        echo err1 >&2
        echo out2
        echo out3
        echo err2 >&2
        echo out4
        echo err3 >&2
        echo -n err4 >&2
}

r="$(  { exe  |  base64 -w 0 ; }  2>&1 )"

echo RAW
printf '%s' "$r"
echo RAW

o="$( printf '%s' "$r" | tail -n 1 | base64 -d )"
e="$( printf '%s' "$r" | head -n -1  )"
unset r    

echo
echo OUT
printf '%s' "$o"
echo OUT
echo
echo ERR
printf '%s' "$e"
echo ERR

donne (avec le correctif stderr-newline) :

$ ./ggg 
RAW
err1
err2
err3
err4

b3V0MQpvdXQyCm91dDMKb3V0NAo=RAW

OUT
out1
out2
out3
out4OUT

ERR
err1
err2
err3
err4ERR

(Au moins sur dash et bash de Debian)

1voto

Andy Points 1607

Voici une variante de la solution de @madmurphy qui devrait fonctionner pour des flux stdout/stderr de taille arbitraire, maintenir la valeur de retour de la sortie, et gérer les nuls dans le flux (en les convertissant en nouvelles lignes).

function buffer_plus_null()
{
  local buf
  IFS= read -r -d '' buf || :
  echo -n "${buf}"
  printf '\0'
}

{
    IFS= time read -r -d '' CAPTURED_STDOUT;
    IFS= time read -r -d '' CAPTURED_STDERR;
    (IFS= read -r -d '' CAPTURED_EXIT; exit "${CAPTURED_EXIT}");
} < <((({ { some_command ; echo "${?}" 1>&3; } | tr '\0' '\n' | buffer_plus_null; } 2>&1 1>&4 | tr '\0' '\n' | buffer_plus_null 1>&4 ) 3>&1 | xargs printf '%s\0' 1>&4) 4>&1 )

Cons :

  • En read Les commandes sont la partie la plus coûteuse de l'opération. Par exemple : find /proc sur un ordinateur exécutant 500 processus, prend 20 secondes (alors que la commande ne prenait que 0,5 seconde). Il faut 10 secondes pour lire la première fois, et 10 secondes de plus pour lire la deuxième fois, ce qui double le temps total.

Explication du tampon

La solution originale était un argument pour printf pour mettre en mémoire tampon le flux, mais comme le code de sortie doit venir en dernier, une solution consiste à mettre en mémoire tampon à la fois stdout et stderr. J'ai essayé xargs -0 printf mais on commençait rapidement à se heurter aux "limites de longueur maximale des arguments". J'ai donc décidé que la solution était d'écrire une fonction tampon rapide :

  1. Utilisez read pour stocker le flux dans une variable
  2. Ce site read se terminera lorsque le flux se terminera, ou lorsqu'un null sera reçu. Comme nous avons déjà supprimé les nullités, il se termine lorsque le flux est fermé, et renvoie un résultat différent de zéro. Puisque c'est un comportement attendu, nous ajoutons || : signifiant "ou vrai" afin que la ligne soit toujours évaluée à vrai (0)
  3. Maintenant que je sais que le flux est terminé, je peux commencer à le renvoyer en écho.
  4. echo -n "${buf}" est une commande intégrée et n'est donc pas limitée par la longueur maximale des arguments.
  5. Enfin, ajoutez un séparateur nul à la fin.

0voto

Hamy Points 5700

Si la commande 1) n'a pas d'effets secondaires liés à l'état et 2) est peu coûteuse en termes de calcul, la solution la plus simple consiste à l'exécuter deux fois. J'ai principalement utilisé cela pour le code qui s'exécute pendant la séquence de démarrage lorsque vous ne savez pas encore si le disque va fonctionner. Dans mon cas, il s'agissait d'un petit some_command Il n'y a donc pas de perte de performance en cas de double exécution, et la commande n'a pas d'effets secondaires.

Le principal avantage est qu'il est propre et facile à lire. Les solutions proposées ici sont assez intelligentes, mais je détesterais être celui qui doit maintenir un script contenant les solutions les plus compliquées. Je recommanderais la simple approche run-it-twice si votre scénario fonctionne avec cela, car elle est beaucoup plus propre et plus facile à maintenir.

Exemple :

output=$(getopt -o '' -l test: -- "$@")
errout=$(getopt -o '' -l test: -- "$@" 2>&1 >/dev/null)
if [[ -n "$errout" ]]; then
        echo "Option Error: $errout"
fi

Encore une fois, cela n'est acceptable que parce que le getopt n'a pas d'effets secondaires. Je sais que les performances sont sûres parce que mon code parent l'appelle moins de 100 fois pendant tout le programme, et l'utilisateur ne remarquera jamais 100 appels à getopt contre 200 appels à getopt.

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