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

32

Je tentais de rediriger les deux stdoutet stderrà un jour fichier, et je suis tombé sur ceci:

<command> > file.txt 2>&1

Apparemment, cette redirection stderrvers d’ stdoutabord, puis le résultat stdoutest redirigé vers file.txt.

Cependant, pourquoi n'est pas la commande <command> 2>&1 > file.txt? On pourrait naturellement lire ceci comme (en supposant une exécution de gauche à droite) la commande étant exécutée en premier, la stderrredirection vers stdout, puis lastdout écriture enfile.txt . Mais ce qui précède ne fait que rediriger stderrvers l'écran.

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

Train Heartnet
la source
7
TLCL dit ceci: "Nous redirigeons d'abord 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)" et "Notez que l'ordre des redirections est important. La redirection de l'erreur standard doit toujours se produire après la redirection de la sortie standard, sinon cela ne fonctionne pas "
Zanna
@ Zanna: Oui, ma question venait précisément de la lecture de celle-ci dans TLCL! :) Je suis intéressé à savoir pourquoi cela ne fonctionnerait pas, c.-à-d. Comment le shell interprète les commandes en général.
Train Heartnet
Eh bien, dans votre question, vous dites le contraire: "Cela semble rediriger stderr d'abord vers stdout ..." - ce que je comprends de TLCL, c'est que le shell envoie stdout au fichier, puis stderr à stdout (c'est-à-dire au fichier). Mon interprétation est que si vous envoyez stderr à stdout, il sera affiché dans le terminal et la redirection ultérieure de stdout n'inclura pas stderr (c'est-à-dire que la redirection de stderr vers l'écran est terminée avant que la redirection de stdout ne se produise?)
Zanna
7
Je sais que c'est une chose à l'ancienne, mais votre shell est livré avec un manuel qui explique ces choses - par exemple, la redirection dans le bashmanuel . Au fait, les redirections ne sont pas des commandes.
Reinier Post
La commande ne peut pas être exécutée avant que les redirections ne soient configurées: lorsque l' execvappel -family syscall est appelé pour effectivement transférer le sous-processus à la commande en cours de démarrage, le shell est alors hors de la boucle (le code ne s'exécute plus dans ce processus) ) et n’a aucun moyen de contrôler ce qui se passe à partir de ce moment; Les redirections doivent donc toutes être effectuées avant le début de l'exécution, alors que le shell a une copie de lui-même en cours d'exécution dans le processus fork()pour exécuter votre commande.
Charles Duffy

Réponses:

41

Lorsque vous exécutez <command> 2>&1 > file.txtstderr, celui-ci est redirigé 2>&1vers l’emplacement actuel de stdout, votre terminal. Après cela, stdout est redirigé vers le fichier par >, mais stderr n’est pas redirigé avec lui, reste donc comme sortie terminal.

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

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

Arronique
la source
Cela a du sens si vous pensez en termes de descripteurs de fichiers et d'appels "dup / fdreopen" exécutés dans l'ordre, de gauche à droite
Mark K Cowan
20

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 pts):

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

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

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

Alors:

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

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2va maintenant où que fd/1 ce soit actuellement :

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

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

  1. 2>&1: fd/2va maintenant où que fd/1ce soit, ce qui signifie que rien ne change:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1va 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 .

muru
la source
Merci, cela semble être une explication plus naturelle! :) Tu as fait une légère faute de frappe dans 2.la deuxième partie; fd/1 -> file.txtet non fd/2 -> file.txt.
Train Heartnet
11

Je pense qu'il est utile de penser que le shell va d'abord configurer la redirection à gauche et la terminer avant de configurer la redirection suivante.

La ligne de commande Linux de 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
$ 

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

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

Zanna
la source
Tellement plus éloquent!
Arronical
Ah, maintenant je comprends! Merci beaucoup, @Zanna et @Arronical! Je viens de commencer mon voyage en ligne de commande. :)
Train Heartnet
@TrainHeartnet c'est un plaisir! J'espère que vous l'appréciez autant que moi: D
Zanna
@ Zanna: En effet, je le suis! : D
Train Heartnet
2
@TrainHeartnet pas de soucis, un monde de frustration et de joie vous attend!
Arronical
10

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, STDINetc. ne sont ici que des symboles / macros utilisés par convention dans certains langages de programmation et pages de manuel, il n’existe pas d’objet "réel" appelé STDIN; le but de cette discussion, STDIN est 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 adresser 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 respectif pour désigner autre chose. La syntaxe 2>&1pointe simplement le descripteur 2 sur 1 où. > file.txts'ouvre simplement file.txtpour l'écriture et laisse STDOUT (le descripteur de fichier 1) pointer vers cela.

Il y a d'autres goodies, par exemple 2>(xxx) (ex: créer un nouveau processus en cours d'exécutionxxx , créer un canal, connecter le descripteur de fichier 0 du nouveau processus à la fin de la lecture du canal et connecter le descripteur de fichier 2 du processus original à la fin de l’écriture du processus). tuyau).

C’est aussi la base de la "magie du traitement de fichier" dans un logiciel autre que votre shell. Par exemple, vous pouvez, dans votre script Perl, dupconvertir le descripteur de fichier STDOUT en 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 system()appels de ce script se retrouveront dans ce fichier temporaire. Lorsque vous avez terminé, vous pouvez rétablir dupvotre STDOUT dans le descripteur temporaire dans lequel vous l'avez enregistré, et 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 toujours générer des éléments dans le vrai STDOUT (généralement, l'utilisateur).

Répondre

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 hors d'un nouveau processus.
  2. Ouvrez file.txtet stockez 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 déjà ouvert file.txt).
  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'une seule table, 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.

AnoE
la source
Upvoting pour le bit "file handle magic" - bien qu'il ne réponde pas directement à la question, j'ai appris quelque chose de nouveau aujourd'hui ...
Floris
3

L'ordre est de gauche à droite. Le manuel de Bash a déjà couvert ce que vous demandez. Citation de la REDIRECTIONsection 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

Sergiy Kolodyazhnyy
la source
3

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. Processus de substitution 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 ...

WinEunuuchs2Unix
la source
1
Pour être clair: la substitution et la redirection de processus sont deux opérations indépendantes. Vous pouvez faire l'un sans l'autre. Avoir une substitution de processus ne signifie pas que bash décide de changer l'ordre dans lequel elle traite la redirection
muru