¿En qué se diferencia la redirección de archivos bash al estándar del shell (`sh`) en Linux?

¿En qué se diferencia la redirección de archivos bash al estándar del shell (`sh`) en Linux?

Escribí un script que cambia de usuario mientras se ejecuta y lo ejecuté usando la redirección de archivos al estándar. También lo user-switch.shes...

#!/bin/bash

whoami
sudo su -l root
whoami

Y ejecutarlo bashme da el comportamiento que espero.

$ bash < user-switch.sh
vagrant
root

Sin embargo, si ejecuto el script con sh, obtengo un resultado diferente

$ sh < user-switch.sh 
vagrant
vagrant

¿Por qué se bash < user-switch.shproduce un resultado diferente al de sh < user-switch.sh?

Notas:

  • sucede en dos cajas diferentes que ejecutan Debian Jessie

Respuesta1

Un script similar, sin sudo, pero con resultados similares:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

Con bash, el resto del script va como entrada sed, con dash, el shell lo interpreta.

Al ejecutarlos strace: dashlee un bloque del script (ocho kB aquí, más que suficiente para contener todo el script) y luego genera sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Lo que significa que el identificador de archivo está al final del archivo y sedno verá ninguna entrada. La parte restante se almacena en búfer dentro de dash. (Si el script fuera más largo que el tamaño del bloque de 8 kB, la parte restante sería leída por sed.)

Bash, por otro lado, busca hasta el final del último comando:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Si la entrada proviene de una tubería, como aquí:

$ cat script.sh | bash

No se puede rebobinar, ya que no se pueden buscar tuberías ni enchufes. En este caso, Bash vuelve a leer la entrada.un personaje a la vezpara evitar sobrelecturas. (fd_to_buffered_stream()eninput.c) Hacer una llamada completa al sistema para cada byte no es muy efectivo en principio. En la práctica, no creo que las lecturas supongan una gran sobrecarga en comparación, por ejemplo, con el hecho de que la mayoría de las cosas que hace el shell implican generar procesos completamente nuevos.

Una situación similar es esta:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

El subshell debe asegurarse de que readsolo lea la primera nueva línea, de modo que headvea la siguiente línea. (Esto también funciona dash).


En otras palabras, Bash hace todo lo posible para admitir la lectura de la misma fuente para el script en sí y para los comandos ejecutados desde él. dashno. El zshy ksh93empaquetado en Debian van con Bash en esto.

Respuesta2

El shell lee el script desde la entrada estándar. Dentro del script, ejecuta un comando que también quiere leer la entrada estándar. ¿Qué entrada irá a dónde?No puedes decirlo de manera confiable.

La forma en que funcionan los shells es que leen un fragmento de código fuente, lo analizan y, si encuentran un comando completo, lo ejecutan y luego continúan con el resto del fragmento y el resto del archivo. Si el fragmento no contiene un comando completo (con un carácter de terminación al final; creo que todos los shells leen hasta el final de una línea), el shell lee otro fragmento, y así sucesivamente.

Si un comando en el script intenta leer desde el mismo descriptor de archivo desde el cual el shell está leyendo el script, entonces el comando encontrará lo que venga después del último fragmento que leyó. Esta ubicación es impredecible: depende del tamaño de fragmento elegido por el shell, y eso puede depender no sólo del shell y su versión, sino también de la configuración de la máquina, la memoria disponible, etc.

Bash busca hasta el final del código fuente de un comando en el script antes de ejecutar el comando. Esto no es algo con lo que pueda contar, no sólo porque otros shells no lo hacen, sino también porque esto sólo funciona si el shell lee desde un archivo normal. Si el shell lee desde una tubería (p. ej. ssh remote-host.example.com <local-script-file.sh), los datos que se han leído se leen y no se pueden dejar de leer.

Si desea pasar información a un comando en el script, debe hacerlo explícitamente, normalmente con unaquí documento. (Un documento aquí suele ser el más conveniente para la entrada de varias líneas, pero cualquier método servirá). El código que escribió sólo funciona en unos pocos shells, sólo si el script se pasa como entrada al shell desde un archivo normal; Si esperaba que el segundo whoamise pasara como entrada sudo …, piénselo de nuevo, teniendo en cuenta que la mayoría de las veces el script no se pasa a la entrada estándar del shell.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Tenga en cuenta que esta década puede utilizar sudo -i root. Correr sudo sues un truco del pasado.

información relacionada