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.sh
es...
#!/bin/bash
whoami
sudo su -l root
whoami
Y ejecutarlo bash
me 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.sh
produce 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
: dash
lee 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 sed
no 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 read
solo lea la primera nueva línea, de modo que head
vea 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. dash
no. El zsh
y ksh93
empaquetado 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 whoami
se 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 su
es un truco del pasado.