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

50voto

TheConstructor Points 2012

Ok, c'est devenu un peu moche, mais voici une solution :

unset t_std t_err
eval "$( (echo std; echo err >&2) \
        2> >(readarray -t t_err; typeset -p t_err) \
         > >(readarray -t t_std; typeset -p t_std) )"

donde (echo std; echo err >&2) doit être remplacé par la commande réelle. Sortie de stdout est enregistré dans le tableau $t_std ligne par ligne en omettant les retours à la ligne (l'option -t ) et stderr en $t_err .

Si vous n'aimez pas les tableaux, vous pouvez faire ce qui suit

unset t_std t_err
eval "$( (echo std; echo err >&2 ) \
        2> >(t_err=$(cat); typeset -p t_err) \
         > >(t_std=$(cat); typeset -p t_std) )"

qui imite à peu près le comportement de var=$(cmd) sauf pour la valeur de $? ce qui nous amène à la dernière modification :

unset t_std t_err t_ret
eval "$( (echo std; echo err >&2; exit 2 ) \
        2> >(t_err=$(cat); typeset -p t_err) \
         > >(t_std=$(cat); typeset -p t_std); t_ret=$?; typeset -p t_ret )"

Aquí $? est conservé dans $t_ret

Testé sur Debian wheezy en utilisant GNU bash , Version 4.2.37(1)-release (i486-pc-linux-gnu) .

46voto

madmurphy Points 101

Je pense qu'avant de dire "vous ne pouvez pas" faire quelque chose, les gens devraient au moins essayer de le faire de leurs propres mains

Solution simple et propre, sans utiliser eval ou tout ce qui est exotique

1. Une version minimale

{
    IFS=$'\n' read -r -d '' CAPTURED_STDERR;
    IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\0' "$(some_command)" 1>&2) 2>&1)

Exige : printf , read

2. Un test simple

Un script factice pour la production de stdout y stderr : useless.sh

#!/bin/bash
#
# useless.sh
#

echo "This is stderr" 1>&2
echo "This is stdout" 

Le script réel qui va capturer stdout y stderr : capture.sh

#!/bin/bash
#
# capture.sh
#

{
    IFS=$'\n' read -r -d '' CAPTURED_STDERR;
    IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\0' "$(./useless.sh)" 1>&2) 2>&1)

echo 'Here is the captured stdout:'
echo "${CAPTURED_STDOUT}"
echo

echo 'And here is the captured stderr:'
echo "${CAPTURED_STDERR}"
echo

Sortie de capture.sh

Here is the captured stdout:
This is stdout

And here is the captured stderr:
This is stderr

3. Comment cela fonctionne

La commande

(printf '\0%s\0' "$(some_command)" 1>&2) 2>&1

envoie la sortie standard de some_command a printf '\0%s\0' créant ainsi la chaîne de caractères \0${stdout}\n\0 (où \0 es un NUL octet et \n est un caractère de nouvelle ligne) ; la chaîne \0${stdout}\n\0 est ensuite redirigé vers l'erreur standard, où l'erreur standard de some_command était déjà présente, composant ainsi la chaîne ${stderr}\n\0${stdout}\n\0 qui est ensuite redirigé vers la sortie standard.

Ensuite, la commande

IFS=$'\n' read -r -d '' CAPTURED_STDERR;

commence à lire la chaîne de caractères ${stderr}\n\0${stdout}\n\0 jusqu'au premier NUL et enregistre le contenu dans ${CAPTURED_STDERR} . Ensuite, la commande

IFS=$'\n' read -r -d '' CAPTURED_STDOUT;

continue à lire la même chaîne jusqu'à la prochaine NUL et enregistre le contenu dans ${CAPTURED_STDOUT} .

4. Le rendre incassable

La solution ci-dessus repose sur un NUL octet pour le délimiteur entre stderr y stdout par conséquent, il ne fonctionnera pas si pour une raison quelconque stderr contient d'autres NUL octets.

Bien que cela ne devrait jamais se produire, il est possible de rendre le script complètement incassable en supprimant tous les possibles NUL octets de stdout y stderr avant de passer les deux sorties à read (désinfection) - NUL Les octets se perdent de toute façon, car il n'est pas possible de les stocker dans des variables shell :

{
    IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
    IFS=$'\n' read -r -d '' CAPTURED_STDERR;
} < <((printf '\0%s\0' "$((some_command | tr -d '\0') 3>&1- 1>&2- 2>&3- | tr -d '\0')" 1>&2) 2>&1)

Exige : printf , read , tr

EDIT

J'ai supprimé un autre exemple de propagation de l'état de sortie au shell courant, car, comme Andy a fait remarquer dans les commentaires, il n'était pas aussi "incassable" qu'il était censé l'être (puisqu'il n'utilisait pas de système de contrôle de la qualité de l'air). printf pour mettre en mémoire tampon l'un des flux). Pour mémoire, je colle le code problématique ici :

Préservation de l'état de sortie (toujours insécable)

La variante suivante propage également l'état de sortie de l'utilisateur. some_command au shell actuel :

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

Exige : printf , read , tr , xargs

Andy a ensuite soumis la "modification suggérée" suivante pour capturer le code de sortie :

Solution simple et propre permettant d'économiser la valeur de sortie

Nous pouvons ajouter à la fin de stderr un troisième élément d'information, un autre NUL plus le exit le statut de la commande. Il sera affiché après stderr mais avant stdout

{
  IFS= read -r -d '' CAPTURED_STDERR;
  IFS= read -r -d '' CAPTURED_EXIT;
  IFS= read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\n\0' "$(some_command; printf '\0%d' "${?}" 1>&2)" 1>&2) 2>&1)

Sa solution semble fonctionner, mais présente le problème mineur que l'état de sortie devrait être placé comme le dernier fragment de la chaîne, de sorte que nous puissions lancer exit "${CAPTURED_EXIT}" entre parenthèses et ne pas polluer la portée globale, comme j'avais essayé de le faire dans l'exemple supprimé. L'autre problème est que, comme la sortie de son fichier interne printf est immédiatement ajouté à la stderr de some_command nous ne pouvons plus assainir les possibles NUL octets en stderr car parmi ceux-ci, il y a aussi notre NUL délimiteur.

5. Préservation de l'état de sortie - le schéma directeur (sans assainissement)

Après avoir réfléchi un peu à l'approche ultime, j'ai trouvé une solution qui utilise printf pour le cache les deux stdout et le code de sortie comme deux arguments différents, afin qu'ils n'interfèrent jamais.

La première chose que j'ai faite a été d'esquisser un moyen de communiquer l'état de sortie au troisième argument de la fonction printf et il s'agissait d'une opération très facile à réaliser dans sa forme la plus simple (c'est-à-dire sans désinfection).

{
    IFS=$'\n' read -r -d '' CAPTURED_STDERR;
    IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
    (IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(some_command)" "${?}" 1>&2) 2>&1)

Exige : exit , printf , read

6. Préservation de l'état de sortie avec sanitization - incassable (réécrit)

Les choses se gâtent cependant lorsque nous essayons d'introduire la désinfection. Lancement de tr pour assainir les flux écrase en fait notre état de sortie précédent, donc apparemment la seule solution est de rediriger ce dernier vers un descripteur séparé avant qu'il ne soit perdu, de le garder là jusqu'à ce que tr fait son travail deux fois, puis le redirige à sa place.

Après quelques redirections assez acrobatiques entre les descripteurs de fichiers, voici ce que j'ai obtenu.

Le code ci-dessous est une réécriture de l'exemple que j'ai supprimé. Il nettoie également les éventuels NUL octets dans les flux, de sorte que read peut toujours fonctionner correctement.

{
    IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
    IFS=$'\n' read -r -d '' CAPTURED_STDERR;
    (IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(((({ some_command; 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)

Exige : exit , printf , read , tr

Cette solution est vraiment robuste. Le code de sortie est toujours gardé séparé dans un descripteur différent jusqu'à ce qu'il atteigne printf directement comme un argument distinct.

7. La solution ultime - une fonction d'usage général avec statut de sortie

Nous pouvons également transformer le code ci-dessus en une fonction d'usage général.

# SYNTAX:
#   catch STDOUT_VARIABLE STDERR_VARIABLE COMMAND
catch() {
    {
        IFS=$'\n' read -r -d '' "${1}";
        IFS=$'\n' read -r -d '' "${2}";
        (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)
}

Exige : cat , exit , printf , read , tr

Avec le catch nous pouvons lancer le snippet suivant,

catch MY_STDOUT MY_STDERR './useless.sh'

echo "The \`./useless.sh\` program exited with code ${?}"
echo

echo 'Here is the captured stdout:'
echo "${MY_STDOUT}"
echo

echo 'And here is the captured stderr:'
echo "${MY_STDERR}"
echo

et obtenir le résultat suivant :

The `./useless.sh` program exited with code 0

Here is the captured stdout:
This is stderr 1
This is stderr 2

And here is the captured stderr:
This is stdout 1
This is stdout 2

8. Ce qui se passe dans les derniers exemples

Voici une schématisation rapide :

  1. some_command est lancé : on a alors some_command 's stdout sur le descripteur 1, some_command 's stderr sur le descripteur 2 et some_command Le code de sortie de l'utilisateur est redirigé vers le descripteur 3.
  2. stdout est acheminé vers tr (assainissement)
  3. stderr est échangé avec stdout (en utilisant temporairement le descripteur 4) et transmis en pipeline à tr (assainissement)
  4. le code de sortie (descripteur 3) est échangé avec stderr (maintenant descripteur 1) et transmis à exit $(cat)
  5. stderr (maintenant le descripteur 3) est redirigé vers le descripteur 1, fin développée comme second argument de printf
  6. le code de sortie de exit $(cat) est capturé par le troisième argument de printf
  7. la sortie de printf est redirigée vers le descripteur 2, où stdout était déjà présent
  8. la concaténation de stdout et la sortie de printf est acheminé vers read

9. La version #1 conforme à POSIX (cassable)

Substitutions de processus (le < <() ) ne sont pas conformes à la norme POSIX (bien qu'ils de facto sont). Dans un shell qui ne prend pas en charge l'option < <() syntaxe, la seule façon d'arriver au même résultat est de passer par la <<EOF … EOF syntaxe. Malheureusement, cela ne nous permet pas d'utiliser NUL comme délimiteurs, parce que ceux-ci sont automatiquement supprimés avant d'atteindre read . Nous devons utiliser un autre délimiteur. Le choix naturel se porte sur le CTRL+Z (caractère ASCII n° 26). Voici un cassable (les sorties ne doivent jamais contenir le CTRL+Z sinon ils seront mélangés).

_CTRL_Z_=$'\cZ'

{
    IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" CAPTURED_STDERR;
    IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" CAPTURED_STDOUT;
    (IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" _ERRNO_; exit ${_ERRNO_});
} <<EOF
$((printf "${_CTRL_Z_}%s${_CTRL_Z_}%d${_CTRL_Z_}" "$(some_command)" "${?}" 1>&2) 2>&1)
EOF

Exige : exit , printf , read

10. La version #2 conforme à POSIX (incassable, mais pas aussi bonne que la version non-POSIX)

Et voici sa version incassable, directement en forme de fonction (si l'un ou l'autre stdout o stderr contiennent CTRL+Z caractères, le flux sera tronqué, mais ne sera jamais échangé avec un autre descripteur).

_CTRL_Z_=$'\cZ'

# SYNTAX:
#     catch_posix STDOUT_VARIABLE STDERR_VARIABLE COMMAND
catch_posix() {
    {
        IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" "${1}";
        IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" "${2}";
        (IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" _ERRNO_; return ${_ERRNO_});
    } <<EOF
$((printf "${_CTRL_Z_}%s${_CTRL_Z_}%d${_CTRL_Z_}" "$(((({ ${3}; echo "${?}" 1>&3-; } | cut -z -d"${_CTRL_Z_}" -f1 | tr -d '\0' 1>&4-) 4>&2- 2>&1- | cut -z -d"${_CTRL_Z_}" -f1 | tr -d '\0' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
EOF
}

Exige : cat , cut , exit , printf , read , tr

32voto

Tino Points 529

Ceci permet de capturer stdout et stderr dans des variables différentes. Si vous voulez seulement attraper stderr en laissant stdout tel quel, il existe une solution meilleure et plus courte .

A somme tout en haut pour le bénéfice du lecteur, voici une

Facile à réutiliser bash Solution

Cette version utilise des sous-shells et fonctionne sans tempfile s. (Pour un tempfile version qui fonctionne sans sous-shells, voir mon autre réponse .)

: catch STDOUT STDERR cmd args..
catch()
{
eval "$({
__2="$(
  { __1="$("${@:3}")"; } 2>&1;
  ret=$?;
  printf '%q=%q\n' "$1" "$__1" >&2;
  exit $ret
  )";
ret="$?";
printf '%s=%q\n' "$2" "$__2" >&2;
printf '( exit %q )' "$ret" >&2;
} 2>&1 )";
}

Exemple d'utilisation :

dummy()
{
echo "$3" >&2
echo "$2" >&1
return "$1"
}

catch stdout stderr dummy 3 $'\ndiffcult\n data \n\n\n' $'\nother\n difficult \n  data  \n\n'

printf 'ret=%q\n' "$?"
printf 'stdout=%q\n' "$stdout"
printf 'stderr=%q\n' "$stderr"

cette impression

ret=3
stdout=$'\ndiffcult\n data '
stderr=$'\nother\n difficult \n  data  '

On peut donc l'utiliser sans y penser plus profondément. Il suffit de mettre catch VAR1 VAR2 devant tout command args.. et vous avez terminé.

Quelques if cmd args..; then deviendra if catch VAR1 VAR2 cmd args..; then . Vraiment rien de complexe.

Addendum : Utilisation en " mode strict "

catch fonctionne pour moi de manière identique en mode strict. Le seul problème est que l'exemple ci-dessus renvoie le code d'erreur 3, qui, en mode strict, appelle le piège ERR. Par conséquent, si vous exécutez une commande sous set -e qui est censé renvoyer des codes d'erreur arbitraires (pas seulement 0), vous devez saisir le code de retour dans une variable telle que && ret=$? || ret=$? comme indiqué ci-dessous :

dummy()
{
echo "$3" >&2
echo "$2" >&1
return "$1"
}

catch stdout stderr dummy 3 $'\ndifficult\n data \n\n\n' $'\nother\n difficult \n  data  \n\n' && ret=$? || ret=$?

printf 'ret=%q\n' "$ret"
printf 'stdout=%q\n' "$stdout"
printf 'stderr=%q\n' "$stderr"

Discussion

Q : Comment cela fonctionne-t-il ?

Il ne fait que regrouper les idées des autres réponses dans une fonction, de sorte qu'elle puisse être facilement réutilisée.

catch() utilise essentiellement eval pour définir les deux variables. Ceci est similaire à https://stackoverflow.com/a/18086548

Considérons un appel de catch out err dummy 1 2a 3b :

  • sautons le eval "$({ et le __2="$( pour l'instant. J'y reviendrai plus tard.

  • __1="$("$("${@:3}")"; } 2>&1; exécute dummy 1 2a 3b et stocke son stdout en __1 pour une utilisation ultérieure. Donc __1 devient 2a . Il redirige également stderr de dummy a stdout de sorte que la prise extérieure puisse rassembler stdout

  • ret=$?; attrape le code de sortie, qui est 1

  • printf '%q=%q\n' "$1" "$__1" >&2; alors les sorties out=2a a stderr . stderr est utilisé ici, car l'actuel stdout a déjà repris le rôle de stderr de la dummy commandement.

  • exit $ret puis transmet le code de sortie ( 1 ) à l'étape suivante.

Maintenant, à l'extérieur __2="$( ... )" :

  • Cela attrape stdout de ce qui précède, qui est le stderr de la dummy appel, dans la variable __2 . (Nous pourrions réutiliser __1 ici, mais j'ai utilisé __2 pour que ce soit moins confus). Donc __2 devient 3b

  • ret="$?"; attrape le code de retour (retourné) 1 (de dummy ) à nouveau

  • printf '%s=%q\n' "$2" "$__2" >&2; alors les sorties err=3a a stderr . stderr est utilisé à nouveau, car il a déjà été utilisé pour sortir l'autre variable out=2a .

  • printf '( exit %q )' "$ret" >&2; puis édite le code pour définir la valeur de retour appropriée. Je n'ai pas trouvé de meilleur moyen, car l'assigner à une variable nécessite un nom de variable, qui ne peut donc pas être utilisé comme premier ou second argument de la commande catch .

Veuillez noter que, par souci d'optimisation, nous aurions pu écrire ces 2 printf comme un seul comme printf '%s=%q\n( exit %q ) "$__2" "$ret"` aussi bien.

Alors, qu'est-ce qu'on a jusqu'à présent ?

Nous avons écrit ce qui suit sur stderr :

out=2a
err=3b
( exit 1 )

donde out vient de $1 , 2a vient de stdout de dummy , err vient de $2 , 3b vient de stderr de dummy et le 1 provient du code de retour de dummy .

Veuillez noter que %q au format de printf s'occupe des guillemets, de telle sorte que l'interpréteur de commandes voit les arguments (simples) appropriés lorsqu'il s'agit de eval . 2a y 3b sont si simples, qu'elles sont copiées littéralement.

Maintenant, à l'extérieur eval "$({ ... } 2>&1 )"; :

Ceci exécute tout ce qui précède, ce qui donne les 2 variables et le fichier exit l'attrape (donc le 2>&1 ) et l'analyse dans le shell courant en utilisant eval .

De cette façon, les deux variables sont définies et le code de retour également.

Q : Il utilise eval ce qui est mauvais. Alors, c'est sûr ?

  • Tant que printf %q n'a pas de bogues, il devrait être sûr. Mais il faut toujours être très prudent, il suffit de penser à ShellShock.

Q : Les insectes ?

  • Aucun bogue évident n'est connu, sauf les suivants :

    • La capture d'une sortie importante nécessite beaucoup de mémoire et de CPU, car tout est placé dans des variables et doit être ré-analysé par le shell. Il faut donc l'utiliser à bon escient.

    • Comme d'habitude $(echo $'\n\n\n\n') avale toutes les lignes d'alimentation et pas seulement le dernier. Il s'agit d'une exigence POSIX. Si vous avez besoin de récupérer les LFs sans les endommager, ajoutez simplement un caractère de queue à la sortie et supprimez-le ensuite comme dans la recette suivante (regardez le caractère de queue x qui permet de lire un lien logiciel pointant vers un fichier qui se termine sur un $'\n' ) :

          target="$(readlink -e "$file")x"
          target="${target%x}"
    • Les variables shell ne peuvent pas porter l'octet NUL ( $'\0' ). Ils sont tout simplement ignorés s'ils se produisent dans stdout o stderr .

  • La commande donnée s'exécute dans un sous-shell. Elle n'a donc pas accès à $PPID Il ne peut pas non plus modifier les variables du shell. Vous pouvez catch une fonction de l'interpréteur de commandes, même les buildins, mais ceux-ci ne pourront pas modifier les variables de l'interpréteur de commandes (comme tout ce qui s'exécute dans l'interpréteur de commandes). $( .. ) ne peut pas le faire). Donc, si vous avez besoin d'exécuter une fonction dans le shell actuel et de récupérer son stderr/stdout, vous devez le faire de la manière habituelle avec tempfile s. (Il existe des moyens de faire cela de telle sorte que l'interruption de l'obus ne laisse normalement pas de débris derrière elle, mais cela est complexe et mérite sa propre réponse).

Q : Version Bash ?

  • Je pense que vous avez besoin de Bash 4 et plus (en raison de printf %q )

Q : Ça a toujours l'air si bizarre.

  • Bien. Une autre réponse ici montre comment cela peut être fait en ksh beaucoup plus proprement. Cependant, je ne suis pas habitué à ksh Je laisse donc à d'autres le soin de créer une recette similaire, facile à réutiliser, pour le ksh .

Q : Pourquoi ne pas utiliser ksh alors ?

  • Parce que c'est un bash solution

Q : Le script peut être amélioré

  • Bien sûr, vous pouvez extraire quelques octets et créer une solution plus petite ou plus incompréhensible. Allez-y ;)

Q : Il y a une faute de frappe. : catch STDOUT STDERR cmd args.. se lit comme suit # catch STDOUT STDERR cmd args..

  • En fait, c'est voulu. : apparaît dans bash -x tandis que les commentaires sont avalés en silence. Ainsi, vous pouvez voir où en est l'analyseur syntaxique si vous avez une coquille dans la définition de la fonction. C'est une vieille astuce de débogage. Mais attention, vous pouvez facilement créer des effets secondaires dans les arguments de la fonction : .

Edit : Ajouté un couple de plus ; pour qu'il soit plus facile de créer une ligne simple à partir de catch() . Et ajouté la section comment ça marche.

19voto

Irfy Points 3864

Techniquement, les named pipes ne sont pas des fichiers temporaires et personne n'en parle ici. Ils ne stockent rien dans le système de fichiers et vous pouvez les supprimer dès que vous les connectez (vous ne les verrez donc jamais) :

#!/bin/bash -e

foo () {
    echo stdout1
    echo stderr1 >&2
    sleep 1
    echo stdout2
    echo stderr2 >&2
}

rm -f stdout stderr
mkfifo stdout stderr
foo >stdout 2>stderr &             # blocks until reader is connected
exec {fdout}<stdout {fderr}<stderr # unblocks `foo &`
rm stdout stderr                   # filesystem objects are no longer needed

stdout=$(cat <&$fdout)
stderr=$(cat <&$fderr)

echo $stdout
echo $stderr

exec {fdout}<&- {fderr}<&- # free file descriptors, optional

Vous pouvez avoir plusieurs processus d'arrière-plan de cette façon et collecter de manière asynchrone leurs sorties et leurs entrées à un moment opportun, etc.

Si vous n'en avez besoin que pour un seul processus, vous pouvez tout aussi bien utiliser des numéros de fd codés en dur comme 3 et 4, au lieu de l'option {fdout}/{fderr} (qui trouve un fd libre pour vous).

15voto

Cette commande définit les valeurs de stdout (stdval) et de stderr (errval) dans le shell en cours d'exécution :

eval "$( execcommand 2> >(setval errval) > >(setval stdval); )"

à condition que cette fonction ait été définie :

function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }

Remplacez execcommand par la commande capturée, que ce soit "ls", "cp", "df", etc.


Tout ceci est basé sur l'idée que nous pourrions convertir toutes les valeurs capturées en une ligne de texte à l'aide de la fonction setval, puis setval est utilisé pour capturer chaque valeur dans cette structure :

execcommand 2> CaptureErr > CaptureOut

Convertir chaque valeur de capture en un appel setval :

execcommand 2> >(setval errval) > >(setval stdval)

Enveloppez tout dans un appel d'exécution et faites-en l'écho :

echo "$( execcommand 2> >(setval errval) > >(setval stdval) )"

Vous obtiendrez les appels de déclaration que chaque setval crée :

declare -- stdval="I'm std"
declare -- errval="I'm err"

Pour exécuter ce code (et obtenir les variables définies), utilisez eval :

eval "$( execcommand 2> >(setval errval) > >(setval stdval) )"

et enfin faire écho à l'ensemble des vars :

echo "std out is : |$stdval| std err is : |$errval|

Il est également possible d'inclure la valeur de retour (sortie).
Un exemple complet de bash script ressemble à ceci :

#!/bin/bash --

# The only function to declare:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }

# a dummy function with some example values:
function dummy { echo "I'm std"; echo "I'm err" >&2; return 34; }

# Running a command to capture all values
#      change execcommand to dummy or any other command to test.
eval "$( dummy 2> >(setval errval) > >(setval stdval); <<<"$?" setval retval; )"

echo "std out is : |$stdval| std err is : |$errval| return val is : |$retval|"

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