¿Por qué 'ls> ls.out' hace que 'ls.out' se incluya en la lista de nombres?

26

¿Por qué hace $ ls > ls.outque 'ls.out' se incluya en la lista de nombres de archivos en el directorio actual? ¿Por qué se eligió esto? ¿Por qué no de otra manera?

Edward Torvalds
fuente
3
probablemente porque primero crea un archivo ls.out y luego escribe la salida en él
Dimitri Podborski
1
Si desea evitar la inclusión, siempre puede almacenar el archivo de salida en un directorio diferente. Por ejemplo, podría elegir el directorio principal (siempre que no se encuentre en la raíz del sistema de archivos) con ls > ../ls.out
Elder Geek

Respuestas:

36

Al evaluar el comando, la >redirección se resuelve primero: por lo tanto, para cuando se lsejecute el archivo de salida ya se ha creado.

Esta es también la razón por la cual leer y escribir en el mismo archivo usando una >redirección dentro del mismo comando trunca el archivo; para cuando el comando ejecuta el archivo ya se ha truncado:

$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$ 

Trucos para evitar esto:

  • <<<"$(ls)" > ls.out (funciona para cualquier comando que deba ejecutarse antes de que se resuelva la redirección)

    La sustitución del comando se ejecuta antes de evaluar el comando externo, por lo que lsse ejecuta antes de ls.outcrearse:

    $ ls
    bar  foo
    $ <<<"$(ls)" > ls.out
    $ cat ls.out 
    bar
    foo
  • ls | sponge ls.out (funciona para cualquier comando que deba ejecutarse antes de que se resuelva la redirección)

    spongeescribe en el archivo solo cuando el resto de la tubería ha terminado de ejecutarse, por lo que lsse ejecuta antes de ls.outcrearse ( spongese proporciona con el moreutilspaquete):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
  • ls * > ls.out(funciona para ls > ls.outel caso específico)

    La expansión del nombre de archivo se realiza antes de que se resuelva la redirección, por lo que lsse ejecutará en sus argumentos, que no contendrán ls.out:

    $ ls
    bar  foo
    $ ls * > ls.out
    $ cat ls.out 
    bar
    foo
    $

Sobre por qué las redirecciones se resuelven antes de que se ejecute el programa / script / lo que sea, no veo una razón específica por la que sea obligatorio hacerlo, pero veo dos razones por las que es mejor hacerlo:

  • no redirigir STDIN de antemano haría que el programa / script / lo que sea se mantenga hasta que se redirija STDIN;

  • no redirigir STDOUT de antemano necesariamente debe hacer que el shell buffer sea la salida del programa / script / lo que sea hasta que STDOUT sea redirigido;

Entonces, una pérdida de tiempo en el primer caso y una pérdida de tiempo y memoria en el segundo caso.

Esto es justo lo que se me ocurre, no estoy afirmando que estas sean las razones reales; pero supongo que, en general, si uno tuviera una opción, iría con la redirección antes de todos modos por las razones mencionadas anteriormente.

kos
fuente
1
Tenga en cuenta que durante la redirección, el shell no toca los datos (ya sea en la redirección de entrada o en la redirección de salida). Simplemente abre el archivo y pasa el descriptor de archivo al programa.
Peter Green
11

De man bash:

REDIRECCION

Antes de ejecutar un comando, su entrada y salida pueden redirigirse utilizando una notación especial interpretada por el shell. La redirección permite que los identificadores de archivos de los comandos se dupliquen, abran, cierren, hagan referencia a diferentes archivos y pueden cambiar los archivos desde los que el comando lee y escribe.

La primera oración sugiere que la salida debe ir a otro lugar que no sea la stdinredirección justo antes de ejecutar el comando. Por lo tanto, para ser redirigido a un archivo, primero el archivo debe ser creado por el propio shell.

Para evitar tener un archivo, le sugiero que redirija la salida a la tubería con nombre primero y luego al archivo. Tenga en cuenta el uso de &para devolver el control sobre el terminal al usuario

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

¿Pero por qué?

Piensa en esto: ¿dónde estará la salida? Un programa cuenta con funciones como printf, sprintf, puts, todos los cuales por defecto ir a stdout, pero puede su producción se ha ido al archivo si el archivo no existe en el primer lugar? Es como el agua ¿Puedes tomar un vaso de agua sin poner primero un vaso debajo del grifo?

Sergiy Kolodyazhnyy
fuente
10

No estoy en desacuerdo con las respuestas actuales. El archivo de salida debe abrirse antes de que se ejecute el comando o el comando no tendrá ningún lugar para escribir su salida.

Esto se debe a que "todo es un archivo" en nuestro mundo. La salida a la pantalla es SDOUT (también conocido como descriptor de archivo 1). Para que una aplicación escriba en el terminal, abre fd1 y le escribe como un archivo.

Cuando redirige la salida de una aplicación en un shell, está alterando fd1, por lo que en realidad apunta al archivo. Cuando canaliza, modifica el STDOUT de una aplicación para convertirse en el STDIN de otra (fd0).


Pero es bueno decir eso, pero puedes ver fácilmente cómo funciona esto strace. Es bastante pesado, pero este ejemplo es bastante corto.

strace sh -c "ls > ls.out" 2> strace.out

Dentro strace.outpodemos ver los siguientes puntos destacados:

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

Esto se abre ls.outcomo fd3. Escribir solamente. Trunca (sobrescribe) si existe, de lo contrario crea.

fcntl(1, F_DUPFD, 10)                   = 10
close(1)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0

Esto es un poco de malabarismo. Desviamos STDOUT (fd1) a fd10 y lo cerramos. Esto se debe a que no estamos enviando nada al STDOUT real con este comando. Termina duplicando el controlador de escritura ls.outy cerrando el 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

Esto es buscando el ejecutable. Una lección tal vez para no tener un largo camino;)

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

Luego se ejecuta el comando y el padre espera. Durante esta operación, cualquier STDOUT se habrá asignado al identificador de archivo abierto ls.out. Cuando el hijo emite SIGCHLD, esto le dice al proceso principal que ha finalizado y que puede reanudar. Termina con un poco más de malabarismo y un cierre de ls.out.

¿Por qué hay por lo tanto malabarismo? No, tampoco estoy completamente seguro.


Por supuesto que puedes cambiar este comportamiento. Puede almacenar en la memoria intermedia algo así spongey eso será invisible desde el comando en curso. Todavía estamos afectando a los descriptores de archivos, pero no de manera visible en el sistema de archivos.

ls | sponge ls.out
Oli
fuente