web-dev-qa-db-fra.com

Pourquoi 'ls> ls.out' provoque-t-il l'inclusion de 'ls.out' dans la liste des noms?

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?

26
Edward Torvalds

Lors de l'évaluation de la commande, la redirection > est d'abord résolue: ainsi, au moment où lsest 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, lsest 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 lsest exécuté avant que ls.out soit créé (spongeest fourni avec le paquetage moreutilsname__):

    $ 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, lssera 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.

36
kos

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 stdinavec 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 printfname__, sprintfname__, putsname__, qui sont toutes par défaut associées à stdoutname__, 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?

11
Sergiy Kolodyazhnyy

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 stracename__. 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 SIGCHLDname__, 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 spongeet 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
10
Oli

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"); 
    }
}
6
incBrain