Dans quel ordre le shell exécute-t-il les commandes et redirige-t-il les flux?

31

J'essayais de rediriger les deux stdout et stderr vers un fichier aujourd'hui, et je suis tombé sur ceci:

<command> > file.txt 2>&1

Cela redirige apparemment stderr vers stdout en premier, puis le stdout résultant est redirigé vers file.txt .

Cependant, pourquoi l’ordre <command> 2>&1 > file.txt n’est-il pas? On lirait naturellement ceci (en supposant l'exécution de gauche à droite) la commande exécutée en premier, le stderr étant redirigé vers stdout puis le stdout résultant étant écrit dans file.txt . Mais ce qui précède ne redirige que stderr vers l'écran.

Comment le shell interprète-t-il les deux commandes?

    
posée Train Heartnet 13.12.2016 - 09:20
la source

6 réponses

40

Lorsque vous exécutez <command> 2>&1 > file.txt stderr est redirigé par 2>&1 vers la destination actuelle de stdout, votre terminal. Après cela, stdout est redirigé vers le fichier par > , mais stderr n'est pas redirigé avec ce fichier, il reste donc en sortie de terminal.

Avec <command> > file.txt 2>&1 stdout est d'abord redirigé vers le fichier par > , puis 2>&1 redirige stderr vers l'endroit où va stdout, qui est le fichier.

Cela peut sembler contre-intuitif au début, mais lorsque vous pensez aux redirections de cette manière, et que vous vous rappelez qu'elles sont traitées de gauche à droite, cela a beaucoup plus de sens.

    
réponse donnée Arronical 13.12.2016 - 10:43
la source
20

Cela pourrait avoir du sens si vous le localisiez.

Au début, stderr et stdout vont à la même chose (généralement le terminal, que j'appelle ici pts ):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Je me réfère à stdin, stdout et stderr par leurs numéros descripteur de fichier ici: ce sont des descripteurs de fichiers 0, 1 et 2 respectivement.

Maintenant, dans le premier ensemble de redirections, nous avons > file.txt et 2>&1 .

Donc:

  1. > file.txt : fd/1 passe maintenant à file.txt . Avec > , 1 est le descripteur de fichier implicite lorsque rien n'est spécifié, il s'agit donc de 1>file.txt :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    
  2. 2>&1 : fd/2 va maintenant à l'endroit où fd/1 actuellement va:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt
    

D'autre part, avec 2>&1 > file.txt , l'ordre étant inversé:

  1. 2>&1 : fd/2 va maintenant à l'endroit où fd/1 va actuellement, ce qui signifie que rien ne change:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
    
  2. > file.txt : fd/1 passe maintenant à file.txt :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    

Le point important est que la redirection ne signifie pas que le descripteur de fichier redirigé suivra toutes les modifications futures du descripteur de fichier cible; cela ne prendra que l'état actuel .

    
réponse donnée muru 13.12.2016 - 15:25
la source
11

Je pense que cela aide de penser que le shell va d'abord configurer la redirection à gauche et compléter avant de configurer la prochaine redirection.

La ligne de commande Linux de William Shotts dit

  

D'abord, nous redirigeons la sortie standard vers le fichier, puis nous redirigeons   descripteur de fichier 2 (erreur standard) au descripteur de fichier un (standard   sortie)

cela a du sens, mais alors

  

Notez que l'ordre des redirections est significatif. le   la redirection de l'erreur standard doit toujours avoir lieu après la redirection   sortie standard ou cela ne fonctionne pas

mais en fait, nous pouvons rediriger stdout vers stderr après avoir redirigé stderr vers un fichier ayant le même effet

$ uname -r 2>/dev/null 1>&2
$ 

Donc, dans command > file 2>&1 , le shell envoie stdout à un fichier, puis envoie stderr à stdout (qui est envoyé à un fichier). Considérant que, dans command 2>&1 > file , le shell redirige d'abord stderr vers stdout (c'est-à-dire l'affiche dans le terminal où se trouve normalement stdout), puis redirige stdout vers le fichier. TLCL est trompeur en disant que nous devons rediriger stdout en premier: puisque nous pouvons rediriger d'abord stderr vers un fichier, puis lui envoyer stdout. Ce que nous ne pouvons pas faire, c'est rediriger stdout vers stderr ou vice versa avant la redirection vers un fichier. Un autre exemple

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

On pourrait penser que cela disposerait de stdout au même endroit que stderr, mais ce n’est pas le cas, il redirige stdout vers stderr (l’écran) d’abord, et redirige uniquement stderr, comme lorsque nous avons essayé l’inverse ...

J'espère que cela apporte un peu de lumière ...

    
réponse donnée Zanna 13.12.2016 - 10:43
la source
10

Vous avez déjà reçu quelques très bonnes réponses. Permettez-moi de souligner qu'il y a deux concepts différents impliqués ici, dont la compréhension aide énormément:

Arrière-plan: descripteur de fichier vs. table de fichiers

Votre descripteur de fichier est juste un numéro 0 ... n, qui est l’index de la table de descripteur de fichier de votre processus. Par convention, STDIN = 0, STDOUT = 1, STDERR = 2 (notez que les termes STDIN etc. ne sont que des symboles / macros utilisés conventionnellement dans certains langages de programmation et pages de manuel, il n'y a pas de véritable "objet" appelé STDIN; aux fins de cette discussion, STDIN est 0, etc.).

Cette table de descripteur de fichier en elle-même ne contient aucune information sur le contenu du fichier. Au lieu de cela, il contient un pointeur sur une autre table de fichiers; cette dernière contient des informations sur un fichier physique réel (ou périphérique de bloc, ou tube, ou tout ce que Linux peut adresser via le mécanisme de fichier) et plus d’informations (c.-à-d. si elle est destinée à la lecture ou à l’écriture).

Ainsi, lorsque vous utilisez > ou < dans votre shell, vous remplacez simplement le pointeur du descripteur de fichier respectif pour pointer vers autre chose. La syntaxe 2>&1 pointe simplement le descripteur 2 vers 1 point. > file.txt ouvre simplement file.txt pour l'écriture et laisse STDOUT (descripteur de fichier 1) pointer sur cela.

Il existe d’autres goodies, par ex. 2>(xxx) (c.-à-d. créer un nouveau processus exécutant xxx , créer un canal, connecter le descripteur de fichier 0 du nouveau processus à l'extrémité de lecture du canal et connecter le descripteur de fichier 2 du processus d'origine à la fin de l'écriture pipe).

Ceci est aussi la base de "magic handle de fichier" dans un autre logiciel que votre shell. Par exemple, dans votre script Perl, vous pouvez, dup décrivez le descripteur de fichier STDOUT dans un autre fichier (temporaire), puis rouvrez STDOUT dans un fichier temporaire nouvellement créé. À partir de là, toutes les sorties STDOUT de votre script Perl et tous les appels system() de ce script se retrouveront dans ce fichier temporaire. Une fois terminé, vous pouvez re dup votre STDOUT sur le descripteur temporaire sur lequel vous l'avez enregistré, et hop, tout est comme avant. Vous pouvez même écrire dans ce descripteur temporaire pendant que votre sortie STDOUT va dans le fichier temporaire, vous pouvez toujours en sortir dans le fichier real STDOUT (généralement l'utilisateur).

Réponse

Pour appliquer les informations de base ci-dessus à votre question:

  

Dans quel ordre le shell exécute-t-il les commandes et redirige-t-il les flux?

De gauche à droite.

  

<command> > file.txt 2>&1

  1. fork sur un nouveau processus.
  2. Ouvrez file.txt et stockez son pointeur dans le descripteur de fichier 1 (STDOUT).
  3. Pointez STDERR (descripteur de fichier 2) sur le point auquel le fd 1 pointe maintenant (ce qui est encore le file.txt ouvert).
  4. exec le <command>
  

Cela redirige apparemment stderr vers stdout en premier, puis la sortie stdout résultante est redirigée vers file.txt.

Cela aurait du sens s’il n’y avait que des tables une , mais comme expliqué ci-dessus, il y en a deux. Les descripteurs de fichiers ne se pointent pas récursivement, cela n'a aucun sens de penser "rediriger STDERR vers STDOUT". La pensée correcte est "point STDERR vers les points STDOUT". Si vous changez STDOUT plus tard, STDERR reste là où il est, cela ne va pas de pair avec d'autres modifications de STDOUT.

    
réponse donnée AnoE 13.12.2016 - 23:34
la source
3

L'ordre est de gauche à droite. Le manuel de Bash a déjà couvert ce que vous demandez. Citation de la section REDIRECTION du manuel:

   Redirections  are  processed  in  the
   order they appear, from left to right.

et quelques lignes plus tard:

   Note that the order of redirections is signifi‐
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli‐
   cated from the standard output before the stan‐
   dard output was redirected to dirlist.

Il est important de noter que la redirection est d'abord résolue avant l'exécution des commandes! Voir lien

    
réponse donnée Sergiy Kolodyazhnyy 13.12.2016 - 23:47
la source
3

Il est toujours de gauche à droite ... sauf quand

Tout comme en maths, nous faisons de gauche à droite, sauf que la multiplication et la division sont faites avant l'addition et la soustraction, sauf que les opérations entre parenthèses (+ -) se feraient avant la multiplication et la division.

Selon le guide des débutants de Bash ici ( Guide Bash Beginners ), il y a 8 ordres de hiérarchie de ce qui vient en premier (avant de gauche à droite):

  1. Extension d'accolade "{}"
  2. Extension de tilde "~"
  3. Paramètre shell et expression de variable "$"
  4. Substitution de commandes "$ (commande)"
  5. Expression arithmétique "$ ((EXPRESSION))"
  6. Substitution de processus De quoi nous parlons ici "& lt; (LIST)" ou "& gt; (LIST)"
  7. Fractionnement de mots "'& lt; espace & gt; & lt; tab & gt; & lt; newline & gt;'"
  8. Extension du nom de fichier "*", "?", etc.

Donc, il est toujours de gauche à droite ... sauf quand ...

    
réponse donnée WinEunuuchs2Unix 17.12.2016 - 04:18
la source

Lire d'autres questions sur les étiquettes