Pourquoi 'ls> ls.out' fait-il inclure 'ls.out' dans la liste des noms?

26

Pourquoi est-ce $ ls > ls.outque '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?

Edward Torvalds
la source
3
probablement parce qu'il crée d'abord un fichier ls.out puis y écrit la sortie
Dimitri Podborski
1
Si vous souhaitez éviter l'inclusion, vous pouvez toujours stocker le fichier de sortie dans un répertoire différent. Par exemple, vous pouvez choisir le répertoire parent (à condition que vous ne soyez pas à la racine du système de fichiers) avec ls > ../ls.out
Elder Geek

Réponses:

36

Lors de l'évaluation de la commande, la >redirection est résolue en premier: ainsi, au moment de l' lsexé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 lsest donc exécutée avant sa ls.outcré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 lsest donc exécuté avant sa ls.outcréation ( spongeest fourni avec le moreutilspackage):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
  • ls * > ls.out(fonctionne pour ls > ls.outle cas spécifique de)

    L'extension du nom de fichier est effectuée avant que la redirection ne soit résolue, donc lss'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.

kos
la source
1
Notez que pendant la redirection, le shell ne touche pas réellement les données (sur la redirection d'entrée ou la redirection de sortie). Il ouvre simplement le fichier et transmet le descripteur de fichier au programme.
Peter Green
11

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 stdinqu'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?

Sergiy Kolodyazhnyy
la source
10

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.outnous pouvons voir les faits saillants suivants:

open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

Cela s'ouvre ls.outcomme 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.outet 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 spongeet 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
Oli
la source
6

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.outpourrait 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"); 
    }
}
Dimitri Podborski
la source