Pourquoi $ ls > ls.out
provoque-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?
Pourquoi $ ls > ls.out
provoque-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 résolue en premier: ainsi, au moment où ls
exécute, le fichier de sortie a déjà été créé.
C'est aussi la raison pour laquelle la lecture et l'écriture sur le même fichier à l'aide de la 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 s'exécuter avant la résolution de la redirection)
La substitution de commande est exécutée avant l’évaluation de la commande externe, donc 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 s'exécuter avant la résolution de la redirection)
sponge
écrit dans le fichier uniquement lorsque l'exécution du reste du canal est terminée. Par conséquent, ls
est exécuté avant la création de ls.out
( sponge
est fourni avec le package moreutils
):
$ 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
)
L’extension du nom de fichier est effectuée avant la résolution de la redirection, 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
$
Pourquoi les redirections sont-elles résolues avant que le programme / script / soit exécuté, je ne vois pas de raison spécifique pour que cela soit obligatoire à le faire, mais je vois deux raisons pour lesquelles mieux de le faire:
ne pas rediriger STDIN au préalable rendrait le programme / script / quoi que ce soit jusqu'à ce que STDIN soit redirigé;
ne pas rediriger STDOUT au préalable devrait obligatoirement faire en sorte que le shell mette en mémoire tampon la sortie du programme / script / peu importe jusqu'à ce que STDOUT soit redirigé;
Une perte de temps dans le premier cas et une perte de temps et de mémoire dans le second cas.
C'est exactement ce qui m'arrive, je ne prétends pas que ce sont les raisons réelles; mais je suppose que dans l’ensemble, si l’on avait le choix, ils iraient de toute façon avec la redirection pour les raisons susmentionnées.
De man bash
:
REDIRECTION
Avant d’exécuter une commande, ses entrées et sorties peuvent être redirigées en utilisant une notation spéciale interprétée par le shell. La redirection permet les descripteurs de fichier des commandes à dupliquer, à ouvrir, à fermer, à faire référence vers différents fichiers, et peut changer les fichiers lus par la commande et écrit à.
La première phrase suggère que la sortie est faite pour 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 le canal nommé, puis de la classer. Notez l'utilisation de &
pour renvoyer le contrôle sur le terminal à l'utilisateur
DIR:/xieerqi
[email protected]:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
[email protected]:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
[email protected]:$ cat /tmp/namedPipe.fifo > ls.out
Mais pourquoi?
Pensez à cela - où sera le résultat? Un programme a des fonctions comme printf
, sprintf
, puts
, qui par défaut vont à stdout
, mais leur sortie peut-elle être transférée au 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 de 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 que la commande ne s'exécute ou que la commande n'ait nulle part où écrire sa sortie.
C'est parce que "tout est un fichier" dans notre monde. La sortie à l'écran est SDOUT (également appelé descripteur de fichier 1). Pour qu'une application écrive sur le terminal, ouvre fd1 et y écrit comme un fichier.
Lorsque vous redirigez une sortie d'une application dans un shell, vous modifiez fd1 pour qu'il pointe effectivement vers le fichier. Lorsque vous conduisez, vous modifiez le STDOUT d'une application pour en faire un autre (fd0).
Mais tout va bien, mais vous pouvez très facilement voir comment cela fonctionne avec strace
. 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
Cela ouvre ls.out
comme fd3
. Ecrire uniquement. 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) pour fd10 et le fermons. C'est parce que nous n'émettons rien dans le STDOUT réel avec cette commande. Il se termine en dupliquant le descripteur d’écriture dans ls.out
et en fermant l’original.
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
Ceci 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 sera effectivement associé au descripteur de fichier ouvert sur ls.out
. Lorsque l'enfant émet SIGCHLD
, cela indique que le processus parent est terminé et qu'il peut reprendre. Il se termine avec un peu plus de jonglage et une clôture de ls.out
.
Pourquoi y a-t-il beaucoup de jonglerie? 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 qui sera invisible à partir de la commande précédente. Nous affectons toujours les descripteurs de fichiers, mais pas de manière visible pour les systèmes de fichiers.
ls | sponge ls.out
Il y a aussi un bel article sur Mise en place de redirection et d'opérateurs de pipeline en shell . Ce qui montre comment la redirection pourrait être implémentée pour que $ ls > ls.out
puisse ressembler à:
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");
}
}
Lire d'autres questions sur les étiquettes command-line bash redirect