Besoin d'explications pour le comportement de la commande Linux bash builtin exec
À Partir de Bash Manuel de Référence j'ai le sujet de exec
bash builtin commande:
Si la commande est fournie, elle remplace le shell sans créer de nouveau processus.
Maintenant, j'ai le script bash
suivant:
#!/bin/bash
exec ls;
echo 123;
exit 0
Ceci exécuté, j'ai eu ceci:
cleanup.sh ex1.bash file.bash file.bash~ output.log
(files from the current directory)
Maintenant, si j'ai ce script:
#!/bin/bash
exec ls | cat
echo 123
exit 0
J'obtiens la sortie suivante:
cleanup.sh
ex1.bash
file.bash
file.bash~
output.log
123
Ma question Est:
Si quand exec
est invoqué Il remplace le shell sans créer un nouveau processus, pourquoi quand on met | cat
, le echo 123
est imprimé, mais sans lui, ce n'est pas le cas. donc, je serais heureux si quelqu'un pouvait expliquer quelle est la logique de ce comportement.
Merci.
Modifier: Après la réponse de @torek, j'ai un comportement encore plus difficile à expliquer:
1.La commande exec ls>out
crée le fichier out
et y met le résultat de la commande ls
;
2.exec ls>out1 ls>out2
crée uniquement les fichiers, mais ne met aucun résultat à l'intérieur. Si la commande fonctionne comme suggéré, je pense que la commande numéro 2 devrait avoir le même résultat que la commande numéro 1(encore plus, je pense qu'elle n'aurait pas dû créer le fichier out2
).
1 réponses
, Dans ce cas particulier, vous avez la exec
dans un pipeline. Afin d'exécuter une série de commandes de pipeline, le shell doit initialement fork, en faisant un sous-shell. (Plus précisément, il doit créer le tuyau, puis la fourche, de sorte que tout ce qui est exécuté "à gauche" du tuyau puisse avoir sa sortie envoyée à tout ce qui est "à droite" du tuyau.)
Pour voir que c'est en fait ce qui se passe, comparez:
{ ls; echo this too; } | cat
Avec:
{ exec ls; echo this too; } | cat
Le premier court ls
sans quitter le sous-shell, de sorte que ce sous-shell est donc toujours là pour exécuter le echo
. Ce dernier s'exécute ls
en laissant le sous-shell, qui n'est donc plus là pour faire le echo
, et this too
n'est pas imprimé.
(l'utilisation d'accolades { cmd1; cmd2; }
supprime normalement l'action de fourche sous-shell que vous obtenez avec des parenthèses (cmd1; cmd2)
, mais dans le cas d'un tuyau, la fourche est "forcée", pour ainsi dire.)
La Redirection du shell actuel ne se produit que s'il n'y a "rien à exécuter", pour ainsi dire, après le mot exec
. Ainsi, par exemple, exec >stdout 4<input 5>>append
modifie le shell actuel, mais exec foo >stdout 4<input 5>>append
essaie d'exécuter la commande foo
. [Note: ce n & apos; est pas strictement exact; voir l & apos; additif.]
Fait intéressant, dans un shell interactif, après l'échec de exec foo >output
car il n'y a pas de commande foo
, le shell reste bloqué, mais stdout reste redirigé vers le fichier output
. (Vous pouvez récupérer avec exec >/dev/tty
. Dans un script, l'échec de exec foo
met fin au script.)
Avec un bout de chapeau à @ Pumbaa80, voici quelque chose d'encore plus illustratif:
#! /bin/bash
shopt -s execfail
exec ls | cat -E
echo this goes to stdout
echo this goes to stderr 1>&2
(note: {[21] } est simplifié par rapport à mon cat -vET
habituel, qui est mon go-to pratique pour "laissez-moi voir les caractères non imprimables d'une manière reconnaissable"). Lorsque ce script est exécuté, la sortie de ls
a cat -E
appliquée (sous Linux, cela rend la fin de ligne visible sous forme de signe$), mais la sortie envoyée à stdout et stderr (sur les deux lignes restantes) estpas redirigée. Modifier les | cat -E
à > out
et, après l'exécution du script, observez le contenu de fichier out
: les deux derniers echo
ne sont pas là.
Maintenant, changez le ls
en foo
(ou une autre commande qui ne sera pas trouvée) et exécutez à nouveau le script. Cette fois, la sortie est:
$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr
Et le fichier out
a maintenant le contenu produit par le premier echo
ligne.
Cela rend ce que exec
"fait vraiment" aussi évident que possible (mais pas plus évident, comme Albert Einstein ne l'a pas dit: -)).
Normalement, lorsque le shell va exécuter une " commande simple" (voir la page de manuel pour la définition précise, mais cela exclut spécifiquement les commandes dans un "pipeline"), il prépare toutes les opérations de redirection d'E/S spécifiées avec <
, >
, et ainsi de suite en ouvrant les fichiers nécessaires. Ensuite, le shell appelle fork
(ou une variante équivalente mais plus efficace comme vfork
ou clone
en fonction du système d'exploitation sous-jacent, de la configuration, etc.), et, dans le processus enfant, réarrange les descripteurs de fichiers ouverts (en utilisant des appels dup2
ou équivalents) pour atteindre le résultat souhaité Dispositions finales: > out
déplace le descripteur ouvert vers fd 1-stdout-tandis que 6> out
déplace le descripteur ouvert vers fd 6.
Si vous spécifiez le mot clé exec
, cependant, le shell supprime l'étape fork
. Il fait toute l'ouverture de fichier et la réorganisation de descripteur de fichier comme d'habitude, mais cette fois, cela affecte toutes les commandes suivantes . Enfin, après avoir fait toutes les redirections, le shell tente de execve()
(dans le sens d'appel système) la commande, s'il y en a une. Si il n'y a pas de commande, ou si l'appel execve()
échoue et le shell est censé continuer à fonctionner (est interactif ou vous avez défini execfail
), les soldats du shell sont activés. Si la execve()
réussit, le shell n'existe plus, ayant été remplacé par la nouvelle commande. Si execfail
n'est pas défini et que le shell n'est pas interactif, le shell se ferme.
(Il y a aussi la complication supplémentaire de la fonction shell command_not_found_handle
: bash's exec
semble supprimer son exécution, en fonction des résultats des tests. Le mot clé exec
dans general fait que le shell ne regarde pas ses propres fonctions, c'est-à-dire que si vous avez une fonction shell f, l'exécution de f
comme une simple commande exécute la fonction shell, tout comme (f)
qui l'exécute dans un sous-shell, mais l'exécution de (exec f)
saute dessus.)
Quant à la raison pour laquelle
ls>out1 ls>out2
crée deux fichiers (avec ou sans exec
), c'est assez simple: le shell ouvre chaque redirection, puis utilise dup2
pour déplacer les descripteurs de fichier. Si vous avez deux redirections >
ordinaires, le shell ouvre les deux, déplace le premier fd 1 (stdout), puis passe la seconde pour fd 1 (stdout nouveau), en clôture de la première dans le processus. Enfin, il fonctionne ls ls
, parce que c'est ce qui reste après avoir enlevé le >out1 >out2
. Tant qu'il n'y a pas de fichier nommé ls
, la commande ls
se plaint à stderr et n'écrit rien dans stdout.