web-dev-qa-db-fra.com

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

J'essayais de rediriger à la fois stdoutet stderrvers un fichier aujourd'hui, et je suis tombé sur ceci:

<command> > file.txt 2>&1

Apparemment, ceci redirige stderrvers stdouten premier, puis le stdoutrésultant est redirigé vers file.txt.

Cependant, pourquoi l’ordre <command> 2>&1 > file.txt n’est-il pas? On pourrait naturellement lire ceci comme (en supposant une exécution de gauche à droite) que la commande soit exécutée en premier, le stderrétant redirigé vers stdoutet ensuite, le stdoutétant écrit dans file.txt. Mais ce qui précède ne fait que rediriger stderrvers l'écran.

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

32
Train Heartnet

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 dernier, il reste donc en tant que sortie terminal.

Avec <command> > file.txt 2>&1, stdout est d'abord redirigé vers le fichier par >, puis 2>&1 redirige stderr vers la destination de stdout, le fichier.

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

41
Arronical

Cela peut avoir un sens si vous le tracez.

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

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

Je fais référence à stdin, stdout et stderr par leur numéro descripteur de fichier: ce sont des descripteurs de fichier 0, 1 et 2 respectivement.

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

Alors:

  1. > file.txt: fd/1 passe maintenant à file.txt. Avec >, 1 est le descripteur de fichier impliqué 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 où que ce soit fd/1actuellement:

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

Par contre, avec 2>&1 > file.txt, l'ordre est inversé:

  1. 2>&1: fd/2 va maintenant à l'endroit où va actuellement fd/1, 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 ultérieures apportées au descripteur de fichier cible; il ne prendra que l'état actuel.

20
muru

Je pense qu’il est utile de penser que le shell définira d’abord la redirection à gauche, puis la complétera avant de configurer la redirection suivante.

La ligne de commande Linux par William Shotts dit

Tout d'abord, nous redirigeons la sortie standard vers le fichier, puis nous redirigeons le descripteur de fichier 2 (erreur standard) vers le descripteur de fichier numéro un (sortie standard).

cela a du sens, mais alors

Notez que l'ordre des redirections est significatif. La redirection d'erreur standard doit toujours se produire après la redirection de la sortie standard, sinon cela ne fonctionne pas

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

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

Ainsi, dans command > file 2>&1, le shell envoie stdout à un fichier, puis envoie stderr à stdout (qui est envoyé dans un fichier). Considérant que, dans command 2>&1 > file, le Shell redirige d’abord stderr vers stdout (c’est-à-dire qu’il l’affiche dans le terminal où stdout se rend normalement), puis redirige ensuite stdout vers le fichier. TLCL est trompeur en disant que nous devons d'abord rediriger stdout: nous pouvons tout d'abord rediriger stderr vers un fichier, puis lui envoyer stdout. Ce que nous ne pouvons pas faire est de 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 éliminerait stdout au même endroit que stderr, mais ce n’est pas le cas, il redirige stdout vers stderr (l’écran) en premier, puis ne redirige que stderr, comme lorsque nous l’avons essayé à l’inverse ...

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

11
Zanna

Vous avez déjà quelques très bonnes réponses. Permettez-moi de souligner cependant que deux concepts différents sont en jeu, dont la compréhension aide énormément:

Arrière-plan: descripteur de fichier et table de fichiers

Votre descripteur de fichier est juste un nombre 0 ... n, qui correspond à l'index de la table des descripteurs de fichier de votre processus. Par convention, STDIN = 0, STDOUT = 1, STDERR = 2 (notez que les termes STDIN etc., ici, ne sont que des symboles/macros utilisés par convention dans certains langages de programmation et pages de manuel, il n'y a pas réellement d '"objet" appelé STDIN; pour les besoins de cette discussion, STDIN is 0, etc.).

Cette table de descripteur de fichier en elle-même ne contient aucune information sur la nature du fichier. Au lieu de cela, il contient un pointeur sur une autre table de fichiers; ce dernier contient des informations sur un fichier physique réel (ou un périphérique bloc, ou un canal, ou tout autre élément que Linux peut traiter via le mécanisme de fichier), ainsi que des informations supplémentaires (que ce soit en lecture ou en écriture).

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

Il y a d'autres goodies, par exemple 2>(xxx) (c'est-à-dire: 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 à l'extrémité d'écriture du canal).

C’est également la base de la "magie du traitement de fichier" dans un logiciel autre que votre Shell. Par exemple, vous pouvez, dans votre script Perl, dupliquer le descripteur de fichier STDOUT dans un autre (temporaire), puis rouvrir STDOUT dans un fichier temporaire nouvellement créé. À partir de ce moment, toutes les sorties STDOUT de votre propre script Perl et tous les appels system() de ce script se retrouveront dans ce fichier temporaire. Une fois terminé, vous pouvez res_dup votre STDOUT dans le descripteur temporaire dans lequel vous l'avez sauvegardé, et le reste, tout est comme avant. Vous pouvez même écrire dans ce descripteur temporaire entre-temps. Ainsi, si votre sortie réelle de STDOUT est dirigée vers le fichier temporaire, vous pouvez quand même exporter des éléments vers réel STDOUT (généralement, l'utilisateur).

Réponse

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

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

De gauche à droite.

<command> > file.txt 2>&1

  1. fork d'un nouveau processus.
  2. Ouvrez file.txt et enregistrez son pointeur dans le descripteur de fichier 1 (STDOUT).
  3. Pointez STDERR (descripteur de fichier 2) sur quelque chose que le fd 1 pointe maintenant (qui est encore le file.txt déjà ouvert).
  4. exec le <command>

Apparemment, cela redirige stderr vers stdout en premier, puis la stdout résultante est redirigée vers file.txt.

Cela aurait du sens s'il n'y avait qu'un n tableau, mais comme expliqué ci-dessus, il y en a deux. Les descripteurs de fichier ne se pointent pas de manière récursive, cela n'a aucun sens de penser "rediriger STDERR vers STDOUT". La bonne idée est de "pointer STDERR vers tous les points de STDOUT". Si vous changez de STDOUT plus tard, STDERR reste en place, il ne va pas comme par magie avec les modifications ultérieures apportées à STDOUT.

10
AnoE

C'est toujours laissé à droite ... sauf quand

Comme en mathématiques, nous allons de gauche à droite, sauf que la multiplication et la division sont effectuées avant l'addition et la soustraction, sauf que les opérations entre parenthèses (+ -) sont effectuées avant la multiplication et la division.

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

  1. Expansion de l'accolade "{}"
  2. Expansion Tilde "~"
  3. Paramètre shell et expression de variable "$"
  4. Substitution de commande "$ (commande)"
  5. Expression arithmétique "$ ((EXPRESSION))"
  6. Process Substitution de ce dont nous parlons ici "<(LIST)" ou "> (LIST)"
  7. Fractionnement de mots "'<espace> <tab> <nouvelle ligne>'"
  8. Extension de nom de fichier "*", "?", Etc.

Donc ça reste toujours à droite ... sauf quand ...

3
WinEunuuchs2Unix

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 résolue avant toute commande! Voir https://askubuntu.com/a/728386/295286

3
Sergiy Kolodyazhnyy