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).

36
demandé sur Denilson Sá Maia 2012-03-29 10:59:23

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.
38
répondu torek 2013-11-02 08:59:03