J'essayais de rediriger à la fois stdout
et stderr
vers un fichier aujourd'hui, et je suis tombé sur ceci:
<command> > file.txt 2>&1
Apparemment, ceci redirige 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 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 stdout
et ensuite, le stdout
étant écrit dans file.txt
. Mais ce qui précède ne fait que rediriger stderr
vers l'écran.
Comment le shell interprète-t-il les deux commandes?
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.
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
name__):
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:
> 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>&1
: fd/2
va maintenant où que ce soit fd/1
actuellement:
fd/0 -> pts
fd/1 -> file.txt
fd/2 -> file.txt
Par contre, avec 2>&1 > file.txt
, l'ordre est inversé:
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
> 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.
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 ...
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:
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, dup
liquer 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).
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
fork
d'un nouveau processus.file.txt
et enregistrez son pointeur dans le descripteur de fichier 1 (STDOUT).file.txt
déjà ouvert).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.
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):
Donc ça reste toujours à droite ... sauf quand ...
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