écho ou impression / dev / stdin / dev / stdout / dev / stderr

13

Je veux imprimer la valeur de / dev / stdin, / dev / stdout et / dev / stderr.

Voici mon script simple:

#!/bin/bash
echo your stdin is : $(</dev/stdin)
echo your stdout is : $(</dev/stdout)
echo your stderr is : $(</dev/stderr)

j'utilise les tuyaux suivants:

[root@localhost home]# ls | ./myscript.sh
[root@localhost home]# testerr | ./myscript.sh

$(</dev/stdin)semble seulement fonctionner, j'ai également trouvé sur quelques autres questions que les gens utilisent: "${1-/dev/stdin}"essayé sans succès.

Omar BISTAMI
la source

Réponses:

29

stdin, stdoutet stderrsont des flux attachés aux descripteurs de fichier 0, 1 et 2 respectivement d'un processus.

À l'invite d'un shell interactif dans un terminal ou un émulateur de terminal, tous ces 3 descripteurs de fichier feraient référence à la même description de fichier ouverte qui aurait été obtenue en ouvrant un fichier de terminal ou de périphérique pseudo-terminal (quelque chose comme /dev/pts/0) en lecture + écriture mode.

Si à partir de ce shell interactif, vous démarrez votre script sans utiliser de redirection, votre script héritera de ces descripteurs de fichier.

Sous Linux, /dev/stdin, /dev/stdout, /dev/stderrsont des liens symboliques vers /proc/self/fd/0, /proc/self/fd/1, /proc/self/fd/2respectivement, se symlinks spéciales au fichier réel qui est ouvert sur les descripteurs de fichiers.

Ce ne sont pas stdin, stdout, stderr, ce sont des fichiers spéciaux qui identifient les fichiers vers lesquels stdin, stdout, stderr vont (notez que c'est différent dans d'autres systèmes que Linux qui ont ces fichiers spéciaux).

lire quelque chose depuis stdin signifie lire depuis le descripteur de fichier 0 (qui pointera quelque part dans le fichier référencé par /dev/stdin).

Mais dans $(</dev/stdin), le shell ne lit pas depuis stdin, il ouvre un nouveau descripteur de fichier pour lire sur le même fichier que celui ouvert sur stdin (donc lire depuis le début du fichier, pas là où stdin pointe actuellement).

Sauf dans le cas particulier des terminaux ouverts en mode lecture + écriture, stdout et stderr ne sont généralement pas ouverts à la lecture. Ils sont censés être des flux sur lesquels vous écrivez . La lecture du descripteur de fichier 1 ne fonctionnera donc généralement pas. Sous Linux, l'ouverture /dev/stdoutou la /dev/stderrlecture (comme dans $(</dev/stdout)) fonctionnerait et vous permettrait de lire à partir du fichier où stdout va (et si stdout était un tuyau, cela lirait à l'autre extrémité du tuyau et s'il s'agissait d'un socket , il échouerait car vous ne pouvez pas ouvrir de socket).

Dans notre cas du script exécuté sans redirection à l'invite d'un shell interactif dans un terminal, tous les fichiers / dev / stdin, / dev / stdout et / dev / stderr seront ce fichier de périphérique de terminal / dev / pts / x.

La lecture de ces fichiers spéciaux renvoie ce qui est envoyé par le terminal (ce que vous tapez sur le clavier). Leur écrire enverra le texte au terminal (pour affichage).

echo $(</dev/stdin)
echo $(</dev/stderr)

serons les mêmes. Pour développer $(</dev/stdin), le shell ouvrira / dev / pts / 0 et lira ce que vous tapez jusqu'à ce que vous appuyiez ^Dsur une ligne vide. Ils passeront ensuite l'expansion (ce que vous avez tapé dépouillé des sauts de ligne de fin et soumis à split + glob) echoqui sera ensuite sorti sur stdout (pour l'affichage).

Cependant dans:

echo $(</dev/stdout)

dans bash( et bashseulement ), il est important de réaliser qu'à l'intérieur $(...), stdout a été redirigé. C'est maintenant une pipe. Dans le cas de bash, un processus shell enfant lit le contenu du fichier (ici /dev/stdout) et l'écrit dans le canal, tandis que le parent lit à l'autre extrémité pour compenser l'expansion.

Dans ce cas, lorsque ce processus bash enfant s'ouvre /dev/stdout, il ouvre en fait l'extrémité de lecture du tuyau. Rien ne viendra jamais de ça, c'est une situation de blocage.

Si vous vouliez lire à partir du fichier pointé par les scripts stdout, vous devriez le contourner avec:

 { echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1

Cela dupliquerait le fd 1 sur le fd 3, donc / dev / fd / 3 pointerait vers le même fichier que / dev / stdout.

Avec un script comme:

#! /bin/bash -
printf 'content of file on stdin: %s\n' "$(</dev/stdin)"
{ printf 'content of file on stdout: %s\n' "$(</dev/fd/3)"; } 3<&1
printf 'content of file on stderr: %s\n' "$(</dev/stderr)"

Lorsqu'il est exécuté en tant que:

echo bar > err
echo foo | myscript > out 2>> err

Vous verriez outensuite:

content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar

Si , par opposition à la lecture de /dev/stdin, /dev/stdout, /dev/stderr, vous vouliez lire stdin, stdout et stderr ( ce qui serait logique encore moins), vous feriez:

#! /bin/sh -
printf 'what I read from stdin: %s\n' "$(cat)"
{ printf 'what I read from stdout: %s\n' "$(cat <&3)"; } 3<&1
printf 'what I read from stderr: %s\n' "$(cat <&2)"

Si vous avez recommencé ce deuxième script en tant que:

echo bar > err
echo foo | myscript > out 2>> err

Vous verriez dans out:

what I read from stdin: foo
what I read from stdout:
what I read from stderr:

et dans err:

bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor

Pour stdout et stderr, catéchoue car les descripteurs de fichiers étaient ouverts pour l' écriture uniquement, pas pour la lecture, l'expansion de $(cat <&3)et $(cat <&2)est vide.

Si vous l'appeliez comme:

echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err

(où <>s'ouvre en mode lecture + écriture sans troncature), vous verriez en out:

what I read from stdin: foo
what I read from stdout:
what I read from stderr: err

et dans err:

err

Vous remarquerez que rien n'a été lu depuis stdout, car le précédent printfavait écrasé le contenu de outavec what I read from stdin: foo\net laissé la position stdout dans ce fichier juste après. Si vous aviez amorcé outavec un texte plus grand, comme:

echo 'This is longer than "what I read from stdin": foo' > out

Ensuite, vous entrez out:

what I read from stdin: foo
read from stdin": foo
what I read from stdout: read from stdin": foo
what I read from stderr: err

Voyez comment l ' $(cat <&3)a lu ce qui restait après le premier printf et cela a également déplacé la position stdout au-delà afin que le prochain printfproduise ce qui a été lu après.

Stéphane Chazelas
la source
2

stdoutet stderrsont des sorties, vous ne les lisez pas, vous pouvez seulement y écrire. Par exemple:

echo "this is stdout" >/dev/stdout
echo "this is stderr" >/dev/stderr

les programmes écrivent sur stdout par défaut, donc le premier est équivalent à

echo "this is stdout"

et vous pouvez rediriger stderr par d'autres moyens tels que

echo "this is stderr" 1>&2
Michael Daffin
la source
1
Sous Linux, ce echo xn'est pas la même chose que echo x > /dev/stdoutsi stdout ne va pas vers un canal ou certains périphériques de caractères comme un périphérique tty. Par exemple, si stdout va dans un fichier normal, le fichier echo x > /dev/stdoutsera tronqué et remplacé par son contenu au x\nlieu d'écrire x\nà la position courante de stdout.
Stéphane Chazelas
@ Michael-Daffin> Merci, donc si je veux lire stdout et stderr je ne peux utiliser que cat? (car ce sont des fichiers), par exemple, je veux montrer à l'utilisateur la dernière erreur qui s'est produite echo this is your error $(cat /dev/stderr)?
Omar BISTAMI
@OmarBISTAMI Vous ne pouvez pas lire de stdoutet stderrce sont des sorties.
Kusalananda
1

myscript.sh:

#!/bin/bash
filan -s

Exécutez ensuite:

$ ./myscript.sh
    0 tty /dev/pts/1
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ ls | ./myscript.sh
    0 pipe 
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ ls | ./myscript.sh > out.txt
$ cat out.txt
    0 pipe 
    1 file /tmp/out.txt
    2 tty /dev/pts/1

Vous devrez probablement installer filanavec d' sudo apt install socatabord.

wisbucky
la source