español - scripts linux ejercicios resueltos
Se necesitan explicaciones para el comportamiento del comando de Linux bash builtin exec (1)
Desde el Manual de referencia de Bash , recibo lo siguiente sobre el comando incorporado de basc bash:
Si se proporciona el comando, reemplaza el shell sin crear un nuevo proceso.
Ahora tengo el siguiente script bash
:
#!/bin/bash
exec ls;
echo 123;
exit 0
Esto ejecutado, obtuve esto:
cleanup.sh ex1.bash file.bash file.bash~ output.log
(files from the current directory)
Ahora, si tengo este script:
#!/bin/bash
exec ls | cat
echo 123
exit 0
Obtengo el siguiente resultado:
cleanup.sh
ex1.bash
file.bash
file.bash~
output.log
123
Mi pregunta es:
Si cuando se invoca exec
, reemplaza el shell sin crear un nuevo proceso , ¿por qué cuando se pone | cat
| cat
, el echo 123
está impreso, pero sin él, no lo está. Entonces, me gustaría que alguien me explique cuál es la lógica de este comportamiento.
Gracias.
EDITAR: Después de la respuesta de @torek, tengo un comportamiento aún más difícil de explicar:
1. el exec ls>out
crea el archivo de out
y coloca el resultado del comando ls
;
2. exec ls>out1 ls>out2
crea solo los archivos, pero no coloca ningún resultado. Si el comando funciona como se sugiere, creo que el comando número 2 debería tener el mismo resultado que el comando número 1 (aún más, creo que no debería haber creado el archivo out2
).
En este caso particular, tiene el exec
en una tubería. Para ejecutar una serie de comandos de canalización, el shell inicialmente debe bifurcarse, formando un subconjunto. (Específicamente tiene que crear la tubería, luego la horquilla, para que todo lo que se ejecute "a la izquierda" de la tubería pueda enviar su salida a lo que esté "a la derecha" de la tubería).
Para ver que esto es, de hecho, lo que está sucediendo, compare:
{ ls; echo this too; } | cat
con:
{ exec ls; echo this too; } | cat
El primero ejecuta ls
sin salir del subconjunto, por lo que este subconjunto todavía está alrededor para ejecutar el echo
. Este último ejecuta ls
dejando el subconjunto, que por lo tanto ya no está allí para hacer el echo
, y this too
se imprime.
(El uso de { cmd1; cmd2; }
normalmente suprime la acción de la horquilla del subconjunto que se obtiene con paréntesis (cmd1; cmd2)
, pero en el caso de una tubería, la horquilla es "forzada", por así (cmd1; cmd2)
.)
La redirección del shell actual ocurre solo si no hay "nada para ejecutar", por así decirlo, después de la palabra " exec
. Por lo tanto, por ejemplo, exec >stdout 4<input 5>>append
modifica el shell actual, pero exec foo >stdout 4<input 5>>append
intenta ejecutar el comando foo
. [Nota: esto no es estrictamente exacto; ver apéndice.]
Curiosamente, en un shell interactivo, después de que la exec foo >output
falle porque no hay ningún comando foo
, el shell se mantiene, pero stdout permanece redirigido a la output
archivo. (Puede recuperar con exec >/dev/tty
. En un script, el error al exec foo
termina el script.)
#! /bin/bash
shopt -s execfail
exec ls | cat -E
echo this goes to stdout
echo this goes to stderr 1>&2
(nota: cat -E
se simplifica desde mi habitual cat -vET
, que es mi práctico cat -vET
para "déjame ver caracteres no impresos de una forma reconocible"). Cuando se ejecuta este script, el resultado de ls
tiene cat -E
aplicado (en Linux esto hace que el final de línea sea visible como un signo $), pero el resultado enviado a stdout y stderr (en las dos líneas restantes) no se redirige . Cambiar el | cat -E
| cat -E
to > out
y, después de ejecutar el script, observe los contenidos del archivo out
: los dos echo
finales no están ahí.
Ahora cambie el ls
a foo
(o algún otro comando que no se encuentre) y ejecute el script nuevamente. Esta vez la salida es:
$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr
y el archivo ahora tiene los contenidos producidos por la primera línea de echo
.
Esto hace que lo que el exec
"realmente hace" sea lo más obvio posible (pero no más obvio, como Albert Einstein no lo expresó :-)).
Normalmente, cuando el shell va a ejecutar un "comando simple" (consulte la página del manual para la definición precisa, pero esto excluye específicamente los comandos en una "interconexión"), prepara cualquier operación de redirección de E / S especificada con <
, >
, y así sucesivamente abriendo los archivos necesarios. Luego, el intérprete de comandos invoca fork
(o una variante equivalente pero más eficiente como vfork
o clone
según el sistema operativo subyacente, la configuración, etc.) y, en el proceso secundario, reorganiza los descriptores de archivos abiertos (utilizando llamadas dup2
o equivalentes) para lograr el arreglos finales deseados: > out
mueve el descriptor abierto a fd 1-stdout-while 6> out
mueve el descriptor abierto a fd 6.
Sin embargo, si especifica la palabra clave exec
, el shell suprime el paso de la fork
. Realiza la apertura de todos los archivos y la reorganización del descriptor de archivo como de costumbre, pero esta vez afecta a todos los comandos posteriores . Finalmente, después de haber hecho todas las redirecciones, el shell intenta execve()
(en el sentido de llamada al sistema) el comando, si hay uno. Si no hay ningún comando, o si la llamada a execve()
falla y se supone que el intérprete continuará ejecutándose (es interactivo o configuró execfail
), el intérprete se execfail
. Si el execve()
tiene éxito, el shell ya no existe, habiendo sido reemplazado por el nuevo comando. Si execfail
está configurado y el shell no es interactivo, el shell se cierra.
(También existe la complicación adicional de la función de shell command_not_found_handle
: el ejecutor de bash parece suprimir su ejecución, en función de los resultados de la prueba. La palabra clave exec
en general hace que el shell no vea sus propias funciones, es decir, si tiene una función de shell f, ejecutar f
como un comando simple ejecuta la función de shell, al igual que (f)
que lo ejecuta en un subconjunto, pero al ejecutar (exec f)
salta sobre él.)
ls>out1 ls>out2
crea dos archivos (con o sin un exec
), esto es bastante simple: el shell abre cada redirección, y luego utiliza dup2
para mover los descriptores de archivos. Si tiene dos >
redireccionamientos ordinarios, el shell abre ambos, mueve el primero a fd 1 (stdout), luego mueve el segundo a fd 1 (stdout nuevamente), cerrando el primero en el proceso. Finalmente, ejecuta ls ls
, porque eso es lo que queda después de eliminar el >out1 >out2
. Mientras no haya un archivo llamado ls
, el comando ls
queja a stderr y no escribe nada en stdout.