État de sortie bash utilisé avec PIPE

10

J'essaie de comprendre comment l'état de sortie est communiqué lorsqu'un tuyau est utilisé. Supposons que j'utilise whichpour localiser un programme inexistant:

which lss
echo $?
1

Depuis l' whichéchec de la localisation, lssj'ai obtenu un statut de sortie de 1. C'est très bien. Cependant, lorsque j'essaie ce qui suit:

which lss | echo $?
0

Cela indique que la dernière commande exécutée s'est terminée normalement. La seule façon dont je peux comprendre cela est que peut-être PIPE produit également un statut de sortie. Est-ce la bonne façon de le comprendre?

sshekhar1980
la source
2
Tous les composants d'un pipeline s'exécutent en même temps . Ainsi, dans which lss | echo $?, le echoest démarré avant whichsa sortie; le shell générant la ligne de commande pour echon'a aucun moyen de savoir quel sera le statut de sortie de whichplus tard.
Charles Duffy
Ils ne courent pas en même temps. Le but de la pipe est de rediriger la sortie d'une commande dans l'autre. La dernière commande exécutée par le shell (c'est-à-dire la dernière de votre instruction) sera celle qui retourne un code retour.
Tim S.20
3
@TimS. Mais ils s'exécutent en même temps, c'est ainsi que vous pouvez obtenir les premiers bits de sortie avant la fin de la première commande s'il s'agit d'une commande très longue.
Random832
Je pense que je pensais au début de l'exécution - il y a un chevauchement certain, mais les processus ne démarrent pas ensemble. Je vois ce que tu veux dire, mon erreur. (Le tube redirige la sortie, pas l'exécution ...) J'ai bâclé mes commentaires, mais j'espère que vous savez de quoi je parle. :)
Tim S.20
oui mais le début de l'exécution n'a pas d'importance, ce qui est important c'est la fin; la dernière commande démarre avant la fin de la première
user371366

Réponses:

14

L'état de sortie du pipeline est l'état de sortie de la dernière commande du pipeline (sauf si l'option shell pipefailest définie dans les shells qui le prennent en charge, auquel cas l'état de sortie sera celui de la dernière commande du pipeline qui se termine avec un statut non nul).

Il n'est pas possible de sortir l'état de sortie d'un pipeline à partir d' un pipeline, car il n'y a aucun moyen de savoir quelle serait cette valeur jusqu'à ce que le pipeline ait réellement terminé son exécution.

Le pipeline

which lss | echo $?

est absurde car echone lit pas son entrée standard (les pipelines sont utilisés pour passer des données entre la sortie d'une commande à l'entrée de la suivante). Le echon'imprimerait pas non plus l'état de sortie du pipeline, mais l'état de sortie de la commande s'exécutait immédiatement avant le pipeline.

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

Ceci est un meilleur exemple:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

Cela signifie qu'un pipeline peut également être utilisé avec if:

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi
Kusalananda
la source
10

L'état de sortie d'un tuyau est l'état de sortie de la commande de droite. Le statut de sortie de la commande de gauche est ignoré.

(Notez que which lss | echo $?cela ne s'affiche pas. Vous devez exécuter which lss | true; echo $?pour afficher cela. Dans which lss | echo $?, echo $?indique l'état de la dernière commande avant ce pipeline.)

La raison pour laquelle les shells se comportent de cette façon est qu'il existe un scénario assez courant où une erreur dans la partie gauche doit être ignorée. Si le côté droit sort (ou ferme plus généralement son entrée standard) pendant que le côté gauche écrit toujours, alors le côté gauche reçoit un signal SIGPIPE. Dans ce cas, il n'y a généralement rien de mal: le côté droit ne se soucie pas des données; si le rôle du côté gauche est uniquement de produire ces données, il est normal que cela s'arrête.

Cependant, si le côté gauche meurt pour une raison autre que SIGPIPE, ou si le travail du côté gauche n'était pas uniquement de produire des données sur la sortie standard, une erreur dans le côté gauche est une véritable erreur qui doivent être signalés.

En clair, la seule solution est d'utiliser un tube nommé.

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

Dans ksh, bash et zsh, vous pouvez demander au shell d'effectuer une sortie de pipeline avec un statut différent de zéro si un composant du pipeline se termine avec un statut différent de zéro. Vous devez définir l' pipefailoption:

  • ksh: set -o pipefail
  • frapper: shopt -s pipefail
  • zsh: setopt pipefail(ou setopt pipe_fail)

Dans mksh, bash et zsh, vous pouvez obtenir l'état de chaque composant du pipeline à l'aide de la variable PIPESTATUS(bash, mksh) ou pipestatus(zsh), qui est un tableau contenant l'état de toutes les commandes du dernier pipeline (une généralisation de $?).

Gilles 'SO- arrête d'être méchant'
la source
Merci Gilles, je n'étais pas au courant de ces complexités impliquées. Cela a vraiment clarifié cela pour moi.
sshekhar1980
4

Oui, PIPE produit un état de sortie; si vous voulez le code de sortie de la commande avant le PIPE, vous pouvez utiliser$PIPESTATUS

Selon ce Q&A de débordement de pile , pour imprimer l'état de sortie d'une commande lorsque vous utilisez un tuyau, vous pouvez faire:

your_command | echo $PIPESTATUS

Ou définissez pipefail avec la commande set -o pipefail(pour le désactiver, faites set +o pipefail), et utilisez $?plutôt que $PIPESTATUS.

your_command | echo $?

Ces commandes ne fonctionnent qu'après les avoir exécutées au moins une fois; cela signifie PIPESTATUSque cela ne fonctionne que lorsque vous l'exécutez après la commande avec le canal, comme ceci:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Vous obtiendrez 10 pour ${PIPESTATUS[0]}et 1 pour ${PIPESTATUS[1]}. Voir ce Q&R U&L pour plus d'informations.

jeremy.Snidaro
la source
2
Bien qu'il PIPESTATUScontienne les états de sortie des commandes du dernier pipeline, votre premier exemple n'a pas plus de sens que celui somecmd | echo $?que Kusalananda a traité dans sa réponse. false; true | echo $PIPESTATUSimprime 1pour l'état de sortie de false.
ilkkachu
Upvote pour PIPESTATUS et pipefiail, mais les |exhoest vraiment déroutant
Eckes
0

Le shell évalue la ligne de commande avant l'exécution du pipeline. La $?valeur de la dernière commande exécutée avant le pipeline l'est également. Qu'il soit echolui-même démarré avant ou après whichest sans importance.

Maintenant, si votre question concernait spécifiquement la propagation du statut de sortie, vous pouvez étudier le comportement par défaut comme ceci:

% false | true
% echo $?
0

% true | false
% echo $?
1

Comme vous pouvez le voir, l'état de sortie d'un pipeline est l'état de sortie de sa dernière commande.

alexis
la source