El programa Bash no se ejecuta si falla la redirección

9

En bash, me doy cuenta de que si falla un comando que usa la redirección, los programas que se ejecutan antes no se ejecutan.

Por ejemplo, este programa abre el archivo "a" y escribe 50 bytes en el archivo "a". Sin embargo, ejecutar este comando con redirección a un archivo con permisos insuficientes (~ root / log), no produce cambios en el tamaño del archivo de "a".

$ ./write_file.py >> ~root/log
-bash: /var/root/log: Permission denied
cdal at Mac in ~/experimental/unix_write
$ ls -lt
total 16
-rw-rw-r--  1 cdal  staff  0 Apr 27 08:54 a <-- SHOULD BE 50 BYTES

Uno podría pensar que el programa se ejecutaría, capturaría cualquier salida (pero también escribiría en el archivo "a") y luego no podría escribir ninguna salida en ~ root / log. En cambio, el programa nunca se ejecuta.

¿Por qué es esto y cómo elige bash el orden de los "controles" que realiza antes de ejecutar un programa? ¿Se realizan también otras verificaciones?

ps Estoy tratando de determinar si un programa ejecutado bajo cron se ejecutó realmente cuando se lo redirigió a un archivo de "permiso denegado".

Charlie Dalsass
fuente
Todo está en buen estado de funcionamiento (es decir, propiedad y permisos de su archivo .py), su programa se ejecuta correctamente. Sus problemas provienen de la redirección. No tiene permiso para escribir un directorio filein / root. Y ha redirigido su stdoutpara hacer exactamente eso. Por lo tanto, no verá ningún resultado, aunque su programa se haya ejecutado.
MelBurslan
2
Mel, eso no es cierto, el programa nunca se ejecutó. Ver las respuestas a continuación.
Charlie Dalsass
Usted: "Ejecute el write_file.pyprograma y envíe su salida a ~root/logbash:" Lo siento, ¡pero no tiene permiso para escribir en ese archivo! "El shell está haciendo exactamente lo que debe hacer. Si no puede hacer lo que le pidió hazlo, te informa inmediatamente por qué hay un problema, dándote la oportunidad de decidir cómo lidiar con él. Para todos los mantenedores de bash saben, podrían ocurrir cosas muy malas si ejecutas ese comando y no puedes guardar la salida. Si fuera lo suficientemente importante como para designar un lugar para guardarlo, sería un error que ASS | U | ME estuviese bien correr sin guardar stdout.
Monty Harder

Respuestas:

18

En realidad no se trata de ordenar cheques, simplemente el orden en que el shell configura las cosas. Las redirecciones se configuran antes de ejecutar el comando; así que en su ejemplo, el shell intenta abrirse ~root/logpara agregar antes de intentar hacer algo que involucre ./write_file.py. Como el archivo de registro no se puede abrir, la redirección falla y el shell deja de procesar la línea de comando en ese punto.

Una forma de demostrar esto es tomar un archivo no ejecutable e intentar ejecutarlo:

$ touch demo
$ ./demo
zsh: permission denied: ./demo
$ ./demo > ~root/log
zsh: permission denied: /root/log

Esto muestra que el shell ni siquiera mira ./democuando no se puede configurar la redirección.

Stephen Kitt
fuente
Wow, es así de simple? No me di cuenta de que las redirecciones se hicieron primero. Gracias por esta respuesta y por otras excelentes respuestas también.
Charlie Dalsass
66
Si no se hicieran primero, ¿dónde se escribiría la salida?
Charles Duffy el
Y si la salida no se puede escribir, ¿cómo sabemos que es seguro ejecutar el comando? Tal vez el comando genera información que se está eliminando de un almacén de datos, y es absolutamente necesario que se capture la salida. Lo bueno es que bash no lo dejará correr hasta que arregles esos permisos, ¿eh?
Monty Harder
11

Desde la página de manual de bash, sección REDIRECCIÓN (énfasis por mí):

Antes de ejecutar un comando, su entrada y salida se pueden redirigir utilizando una notación especial interpretada por el shell.

...

Si no se abre o crea un archivo, la redirección falla.

Por lo tanto, el shell intenta abrir el archivo de destino stdout, que falla, y el comando no se ejecuta en absoluto.

Murphy
fuente
Muchas gracias. Deseo que la página del manual se aclare un poco con "... si la salida no se puede redirigir, el programa no se ejecutará".
Charlie Dalsass
Actualizado; está bastante oculto algunos párrafos a continuación.
Murphy
En realidad, está bastante claro. "Un error al abrir o crear un archivo hace que la redirección falle". Ahí está. Gracias de nuevo.
Charlie Dalsass
3

Vale la pena observar que el shell debe establecer redirecciones antes de iniciar el programa.

Considera tu ejemplo:

./write_file.py >> ~root/log

Lo que sucede en el shell es:

  1. Nosotros (la cáscara) fork(); el proceso hijo hereda los descriptores de archivo abierto de su padre (el shell).
  2. En el proceso hijo, nosotros fopen()(la expansión de) "~ root / log", y dup2()lo fd 1 (y close()el fd temporal). Si fopen()falla, llame exit()para informar el error al padre.
  3. Todavía en el niño, nosotros exec()"./write_file.py". Este proceso ya no ejecuta ninguno de nuestros códigos (a menos que no podamos ejecutarlo, en cuyo caso debemos exit()informar el error al padre).
  4. El padre hará wait()que el hijo termine y maneje su código de salida (copiándolo $?, al menos).

Por lo tanto, la redirección debe ocurrir en el elemento secundario entre fork()y exec(): no puede suceder antes fork()porque no debe cambiar la salida estándar del shell, y no puede suceder después exec()porque el nombre de archivo y el código ejecutable del shell ahora han sido reemplazados por el programa Python . El padre no tiene acceso a los descriptores de archivo del niño (e incluso si lo hiciera, no podría garantizar la redirección entre exec()y la primera escritura en stdout).

Toby Speight
fuente
0

Lamento informarle que es todo lo contrario. El shell necesita abrir su E / S primero y luego pasa el control al programa.

tee podría ser útil en este caso: ./write_file.py | tee -a ~root/log > /dev/null

Julie Pelletier
fuente
¿El script de Python no morirá en SIGPIPE después de que el tee falle?
Kevin
No de acuerdo con la prueba que hice, pero te sugiero que lo pruebes.
Julie Pelletier