Pourquoi $ ls > ls.out
cause-t-il l'inclusion de 'ls.out' dans la liste des noms de fichiers du répertoire en cours? Pourquoi cela a-t-il été choisi? Pourquoi pas autrement?
Lors de l'évaluation de la commande, la redirection >
est d'abord résolue: ainsi, au moment où ls
est lancé, le fichier de sortie a déjà été créé.
C'est également la raison pour laquelle la lecture et l'écriture dans le même fichier à l'aide d'une redirection >
au sein de la même commande tronque le fichier; au moment où la commande est exécutée, le fichier a déjà été tronqué:
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
Astuces pour éviter cela:
<<<"$(ls)" > ls.out
(fonctionne pour toute commande devant être exécutée avant que la redirection ne soit résolue)
La substitution de commande est exécutée avant l'évaluation de la commande externe. Par conséquent, ls
est exécuté avant la création de ls.out
:
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(fonctionne pour toute commande devant être exécutée avant que la redirection ne soit résolue)
sponge
écrit dans le fichier uniquement lorsque le reste du canal est terminé, de sorte que ls
est exécuté avant que ls.out
soit créé (sponge
est fourni avec le paquetage moreutils
name__):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(fonctionne pour le cas spécifique de ls > ls.out
)
Le développement du nom de fichier est effectué avant que la redirection ne soit résolue. Par conséquent, ls
sera exécuté sur ses arguments, qui ne contiendront pas ls.out
:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
Pourquoi les redirections sont résolues avant le programme/script/quel que soit ce qui est exécuté, je ne vois pas de raison spécifique pour laquelle il est obligatoire de le faire, mais je vois deux raisons pour lesquelles c'est meilleur pour le faire:
ne pas rediriger STDIN au préalable rendrait le programme/script/ce qui est en attente jusqu'à ce que STDIN soit redirigé;
ne pas rediriger STDOUT au préalable doit obligatoirement obliger Shell à mettre en tampon la sortie du programme/script/quel que soit jusqu'à ce que STDOUT soit redirigé;
Donc, une perte de temps dans le premier cas et une perte de temps et de mémoire dans le second cas.
C’est ce qui m’arrive à l’esprit, je ne prétends pas que ce sont les véritables raisons; mais je suppose que globalement, si on avait le choix, ils iraient de toute façon avec une redirection auparavant pour les raisons susmentionnées.
De man bash
:
REDIRECTION
Avant qu'une commande ne soit exécutée, son entrée et sa sortie peuvent être redirigées à l'aide d'une notation spéciale interprétée par le shell. La redirection permet aux descripteurs de fichier des commandes d'être dupliqués, ouverts, fermés, de se référer à différents fichiers et de modifier les fichiers lus et écrits par la commande.
La première phrase suggère que la sortie doit aller ailleurs que stdin
avec une redirection juste avant l'exécution de la commande. Ainsi, pour être redirigé vers un fichier, le fichier doit d'abord être créé par le shell lui-même.
Pour éviter d'avoir un fichier, je vous suggère de rediriger d'abord la sortie vers un canal nommé, puis vers un fichier. Notez l'utilisation de &
pour rendre à l'utilisateur le contrôle du terminal
DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out
mais pourquoi?
Pensez à cela - où sera la sortie? Un programme a des fonctions telles que printf
name__, sprintf
name__, puts
name__, qui sont toutes par défaut associées à stdout
name__, mais leur sortie peut-elle disparaître du fichier si le fichier n'existe pas à la première place? C'est comme de l'eau. Pouvez-vous obtenir un verre d'eau sans mettre le verre sous le robinet d'abord?
Je ne suis pas en désaccord avec les réponses actuelles. Le fichier de sortie doit être ouvert avant l'exécution de la commande, sinon la commande n'aura nulle part où écrire sa sortie.
C'est parce que "tout est un fichier" dans notre monde. La sortie à l'écran est SDOUT (aka descripteur de fichier 1). Pour qu'une application puisse écrire sur le terminal, il ouvre fd1 et écrit dessus comme un fichier.
Lorsque vous redirigez la sortie d'une application dans un shell, vous modifiez fd1 pour qu'il pointe en fait sur le fichier. Lorsque vous dirigez le canal, vous modifiez le STDOUT d'une application pour devenir le STDIN (fd0) d'une autre.
Mais tout est gentil en disant cela, mais vous pouvez facilement voir comment cela fonctionne avec strace
name__. C'est assez lourd, mais cet exemple est assez court.
strace sh -c "ls > ls.out" 2> strace.out
Dans strace.out
nous pouvons voir les faits saillants suivants:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Ceci ouvre ls.out
en tant que fd3
. Écrivez seulement. Tronque (écrase) s'il existe, sinon crée.
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
C'est un peu de jonglage. Nous shuntons STDOUT (fd1) à fd10 et le fermons. En effet, nous ne produisons rien dans le vrai STDOUT avec cette commande. Il termine en dupliquant le descripteur d'écriture sur ls.out
et en fermant celui d'origine.
stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
C'est la recherche de l'exécutable. Une leçon peut-être pour ne pas avoir un long chemin;)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn() = 31933
dup2(10, 1) = 1
close(10) = 0
Ensuite, la commande s'exécute et le parent attend. Au cours de cette opération, tout STDOUT aura effectivement été mappé sur le descripteur de fichier ouvert sur ls.out
. Lorsque l'enfant émet SIGCHLD
name__, cela indique au processus parent qu'il est terminé et qu'il peut reprendre. Il se termine avec un peu plus de jonglerie et une clôture de ls.out
.
Pourquoi y at-il donc beaucoup à jongler? Non, je ne suis pas tout à fait sûr non plus.
Bien sûr, vous pouvez changer ce comportement. Vous pouvez mettre en mémoire tampon quelque chose comme sponge
et cela sera invisible depuis la commande précédente. Nous affectons toujours les descripteurs de fichier, mais pas de manière visible par le système de fichiers.
ls | sponge ls.out
Il existe également un article de Nice sur Implémentation des opérateurs de redirection et de pipe dans Shell . Ce qui montre comment la redirection pourrait être implémentée afin que $ ls > ls.out
ressemble à ceci:
main(){
close(1); // Release fd no - 1
open("ls.out", "w"); // Open a file with fd no = 1
// Child process
if (fork() == 0) {
exec("ls");
}
}