Pourquoi est-ce $ ls > ls.out
que 'ls.out' est inclus dans la liste des noms de fichiers dans le répertoire courant? Pourquoi cela a-t-il été choisi? Pourquoi pas autrement?
ls > ../ls.out
Pourquoi est-ce $ ls > ls.out
que 'ls.out' est inclus dans la liste des noms de fichiers dans le répertoire courant? Pourquoi cela a-t-il été choisi? Pourquoi pas autrement?
ls > ../ls.out
Réponses:
Lors de l'évaluation de la commande, la >
redirection est résolue en premier: ainsi, au moment de l' ls
exécution, 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 dans la même commande tronque le fichier; au moment où la commande s'exécute, 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 la résolution de la redirection)
La substitution de commande est exécutée avant l'évaluation de la commande externe, elle ls
est donc exécutée avant sa ls.out
création:
$ 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 la résolution de la redirection)
sponge
écrit dans le fichier uniquement lorsque le reste du canal a terminé son exécution, il ls
est donc exécuté avant sa ls.out
création ( sponge
est fourni avec le moreutils
package):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(fonctionne pour ls > ls.out
le cas spécifique de)
L'extension du nom de fichier est effectuée avant que la redirection ne soit résolue, donc ls
s'exécutera sur ses arguments, qui ne contiendront pas ls.out
:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
Sur la raison pour laquelle les redirections sont résolues avant l'exécution du programme / script / quoi que ce soit, je ne vois pas de raison spécifique pour laquelle il est obligatoire de le faire, mais je vois deux raisons pour lesquelles il vaut mieux le faire:
ne pas rediriger STDIN au préalable ferait en sorte que le programme / script / que ce soit soit bloqué jusqu'à ce que STDIN soit redirigé;
ne pas rediriger STDOUT au préalable devrait nécessairement faire du tampon shell la sortie du programme / script / quoi que ce 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 juste ce qui me vient à l'esprit, je ne prétends pas que ce sont les raisons réelles; mais je suppose que dans l'ensemble, si on avait le choix, ils iraient avec redirection avant de toute façon pour les raisons susmentionnées.
De man bash
:
REDIRECTION
Avant qu'une commande ne soit exécutée, ses entrées et sorties peuvent être redirigées à l'aide d'une notation spéciale interprétée par le shell. La redirection permet de dupliquer, d'ouvrir, de fermer, de gérer les fichiers des commandes, de faire référence à différents fichiers et de modifier les fichiers à partir desquels la commande lit et écrit.
La première phrase suggère que la sortie est faite pour aller ailleurs stdin
qu'avec la 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 la sortie vers le canal nommé d'abord, puis vers le fichier. Notez l'utilisation de &
pour rendre le contrôle du terminal à l'utilisateur
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-y - où sera la sortie? Un programme a des fonctions comme printf
, sprintf
, puts
, tous par défaut aller à stdout
, mais peut leur sortie être allé fichier si le fichier n'existe pas en premier lieu? C'est comme de l'eau. Pouvez-vous obtenir un verre d'eau sans mettre le verre sous le robinet en premier?
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 aucun endroit pour écrire sa sortie.
En effet, «tout est un fichier» dans notre monde. La sortie à l'écran est SDOUT (aka descripteur de fichier 1). Pour qu'une application écrive sur le terminal, elle ouvre fd1 et y écrit comme un fichier.
Lorsque vous redirigez la sortie d'une application dans un shell, vous modifiez fd1 afin qu'il pointe réellement vers le fichier. Lorsque vous dirigez, vous modifiez le STDOUT d'une application pour devenir le STDIN d'une autre (fd0).
Mais c'est bien de le dire, mais vous pouvez très facilement voir comment cela fonctionne strace
. C'est assez lourd mais cet exemple est assez court.
strace sh -c "ls > ls.out" 2> strace.out
À l'intérieur, strace.out
nous pouvons voir les faits saillants suivants:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Cela s'ouvre ls.out
comme fd3
. Écris seulement. Tronque (remplace) 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. C'est parce que nous ne sortons rien vers le vrai STDOUT avec cette commande. Il termine en dupliquant la poignée d'écriture ls.out
et en fermant la poignée 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
Il s'agit de rechercher 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, n'importe quel STDOUT aura effectivement été mappé sur le descripteur de fichier ouvert ls.out
. Lorsque l'enfant émet SIGCHLD
, cela indique au processus parent qu'il est terminé et qu'il peut reprendre. Il se termine par un peu plus de jonglage et une fin de ls.out
.
Pourquoi y a-t-il tant de jonglage? Non, je ne suis pas tout à fait sûr non plus.
Bien sûr, vous pouvez modifier ce comportement. Vous pouvez mettre en mémoire tampon quelque chose comme sponge
et cela sera invisible à partir de la commande en cours. Nous affectons toujours les descripteurs de fichiers, mais pas de manière visible par le système de fichiers.
ls | sponge ls.out
Il y a aussi un bel article sur la mise en œuvre des opérateurs de redirection et de pipe dans le shell . Ce qui montre comment la redirection pourrait être implémentée et $ ls > ls.out
pourrait ressembler à 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");
}
}