¿Por qué hace $ ls > ls.out
que '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?
ls > ../ls.out
¿Por qué hace $ ls > ls.out
que '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?
ls > ../ls.out
Respuestas:
Al evaluar el comando, la >
redirección se resuelve primero: por lo tanto, para cuando se ls
ejecute 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 ls
se ejecuta antes de ls.out
crearse:
$ 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)
sponge
escribe en el archivo solo cuando el resto de la tubería ha terminado de ejecutarse, por lo que ls
se ejecuta antes de ls.out
crearse ( sponge
se proporciona con el moreutils
paquete):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(funciona para ls > ls.out
el caso específico)
La expansión del nombre de archivo se realiza antes de que se resuelva la redirección, por lo que ls
se 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.
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 stdin
redirecció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?
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.out
podemos ver los siguientes puntos destacados:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Esto se abre ls.out
como 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.out
y 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í sponge
y 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
También hay un buen artículo sobre Implementación de operadores de redirección y canalización en shell . Lo que muestra cómo se podría implementar la redirección para $ ls > ls.out
que se vea así:
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");
}
}