web-dev-qa-db-fra.com

Est-ce un bug dans bash? `retour` ne quitte pas la fonction si appelé d'un tuyau

J'ai eu des problèmes étranges avec Bash dernièrement. En essayant de simplifier mon script, j'ai proposé ce petit morceau de code:

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

return aurait dû sortir de la fonction sans impression $?, ne devrait-il pas? Eh bien, alors j'ai vérifié si je peux revenir d'un tuyau seul:

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

La même chose se produit sans une boucle while:

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

Y a-t-il quelque chose qui me manque ici? Une recherche Google n'a rien apporté à ce sujet! Ma version Bash est 4.2.37 (1) -Release sur Debian Wheezy.

16
Teresa e Junior

Associé: https://stackoverflow.com/a/7804208/49379

Ce n'est pas un bogue que vous ne pouvez pas quitter un script ou votre retour d'une fonction par exit ou return dans les sous-espaces. Ils sont exécutés dans un autre processus et n'affectant pas le processus principal.

En outre, je suppose que vous voyez des comportements sans papiers de bash sur (probablement) des spécifications non définies. Dans une fonction, aucune erreur n'est affirmée pour return au niveau supérieur des commandes SUBSHELL et cela se comporte comme exit.

IMHO C'est un bug Bash pour le comportement incohérent de return selon que l'instruction principale est en fonction ou non.

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

Sortir:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23
10
yaegashi

La documentation de POSIX, tilisation return en dehors de la fonction ou du script source est indéterminée . Donc, cela dépend de votre coquille à gérer.

Systemv Shell fera signaler une erreur, _ ksh, return en dehors de la fonction ou du script source se comporter comme exit. La plupart des autres coquilles de Posix et Schily's Osh se comportent également comme ça:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

ksh et zsh _ N'a pas sortie car la dernière partie du tuyau dans ces coquilles a été exécutée dans une coque de courant au lieu de sous-espaces. La déclaration de retour a affecté l'environnement de shell actuel qui a appelé la fonction, car la fonction retour immédiatement sans rien imprimer.

En session interactive, bash _ ne signalez que l'erreur mais n'a pas terminé la coquille, schily's osh a rapporté l'erreur et terminé la coquille:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

(zsh _ en session interactive et sortie est terminal Ne pas terminer, bash, yash et schily's osh signalé l'erreur mais n'a pas terminé la coquille)

6
cuonglm

Je pense que vous avez obtenu le comportement attendu, à bash, chaque commande dans un pipeline est exécutée dans un sous-groupe. Vous pouvez vous convaincre en essayant de modifier une variable globale de votre fonction:

foo(){ x=42; : | x=3; echo "x==$x";}

Au fait, le retour fonctionne mais cela revient du sous-groupe. Encore une fois, vous pouvez vérifier cela:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

Va émettre ce qui suit:

1
This should not be printed.

Donc déclaration de retour correctement quitté le sous-groupe

.

4
herbert

La réponse plus générale est que Bash et d'autres coquilles mettent normalement tous les éléments d'un pipeline dans des processus distincts. Ceci est raisonnable lorsque la ligne de commande est

programme1programme2programme3

étant donné que les programmes sont normalement exécutés dans des processus distincts de toute façon (sauf si vous dites exec program). Mais cela peut être une surprise pour

commander1commander2commander3

où certaines ou toutes les commandes sont des commandes intégrées. Les exemples triviaux incluent:

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

Un exemple légèrement plus réaliste est

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

où l'ensemble while ... _ ... do ... done boucle est placée dans une sous-processus, de sorte que ses modifications apportées à t ne sont pas visibles à la coque principale après la boucle se termine. Et c'est exactement ce que vous faites - la tuyauterie dans une boucle while, ce qui oblige la boucle à exécuter en tant que sous-(-n-shell, puis essayant de revenir du sous-groupe.

1
Scott