Pasar comandos como entrada a otro comando (su, ssh, sh, etc.)

Resuelto tripleee asked hace 8 años • 3 respuestas

Tengo una secuencia de comandos en la que necesito iniciar un comando y luego pasar algunos comandos adicionales como comandos a ese comando. Lo intenté

su
echo I should be root now:
who am I
exit
echo done.

... pero no funciona: tiene suéxito, pero luego el símbolo del sistema simplemente me mira fijamente. Si escribo exiten el mensaje, ¡ echoy who am ietc. comienzan a ejecutarse! Y echo done.no se ejecuta en absoluto.

De manera similar, necesito que esto funcione ssh:

ssh remotehost
# this should run under my account on remotehost
su
## this should run as root on remotehost
whoami
exit
## back
exit
# back

¿Cómo puedo solucionar esto?

Estoy buscando respuestas que resuelvan esto de manera general y que no sean específicas suni sshen particular. La intención es que esta pregunta se convierta en canónica para este patrón en particular.

tripleee avatar Jun 02 '16 15:06 tripleee
Aceptado

Agregando a la respuesta de tripleee :

Es importante recordar que la sección del script formateada como documento aquí para otro shell se ejecuta en un shell diferente con su propio entorno (y tal vez incluso en una máquina diferente).

Si ese bloque de su script contiene expansión de parámetros, sustitución de comandos y/o expansión aritmética, entonces debe usar la función de documento aquí del shell de manera ligeramente diferente, dependiendo de dónde desee que se realicen esas expansiones.

1. Todas las expansiones deben realizarse dentro del alcance del shell principal.

Entonces el delimitador del documento aquí no debe estar entre comillas .

command <<DELIMITER
...
DELIMITER

Ejemplo:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<END
    a=1
    mylogin=$(whoami)
    echo a=$a
    echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin

Producción:

a=0
mylogin=leon
a=0
mylogin=leon

2. Todas las expansiones deben realizarse dentro del alcance del shell secundario.

Luego se debe citar el delimitador del documento aquí .

command <<'DELIMITER'
...
DELIMITER

Ejemplo:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<'END'
    a=1
    mylogin=$(whoami)
    echo a=$a
    echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin

Producción:

a=1
mylogin=root
a=0
mylogin=leon

3. Algunas expansiones deben realizarse en el shell secundario, otras en el shell principal.

Luego, el delimitador del documento aquí no debe estar entrecomillado y debe escapar de aquellas expresiones de expansión que deben realizarse en el shell secundario .

Ejemplo:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<END
    a=1
    mylogin=\$(whoami)
    echo a=$a
    echo mylogin=\$mylogin
END
echo a=$a
echo mylogin=$mylogin

Producción:

a=0
mylogin=root
a=0
mylogin=leon
Leon avatar Jun 10 '2016 10:06 Leon

Un script de shell es una secuencia de comandos. El shell leerá el archivo de script y ejecutará esos comandos uno tras otro.

Como es habitual, aquí no hay sorpresas; pero un error frecuente de principiante es suponer que algunos comandos tomarán el control del shell y comenzarán a ejecutar los siguientes comandos en el archivo de script en lugar del shell que actualmente ejecuta este script. Pero no es así como funciona.

Básicamente, los scripts funcionan exactamente como los comandos interactivos, pero es necesario comprender correctamente cómo funcionan exactamente . De forma interactiva, el shell lee un comando (de la entrada estándar), ejecuta ese comando (con la entrada de la entrada estándar) y, cuando termina, lee otro comando (de la entrada estándar).

Ahora, al ejecutar un script, la entrada estándar sigue siendo la terminal (a menos que haya utilizado una redirección), pero los comandos se leen desde el archivo de script, no desde la entrada estándar. (Lo contrario sería muy engorroso: cualquiera readconsumiría la siguiente línea del script, catabsorbería el resto del script y no habría forma de interactuar con él). El archivo del script solo contiene comandos para la instancia de shell. que lo ejecuta (aunque, por supuesto, aún puede usar un documento aquí, etc. para incrustar entradas como argumentos de comando).

En otras palabras , estos comandos "mal entendidos" ( su,,,, etc. ) cuando sshse ejecutan solos (sin argumentos) iniciarán un shell interactivo, y en una sesión interactiva, eso obviamente está bien; pero cuando se ejecuta desde un script, muchas veces eso no es lo que desea.shsudobash

Todos estos comandos tienen formas de aceptar comandos de formas distintas a las de una sesión de terminal interactiva. Normalmente, cada comando admite una forma de pasarle comandos como opciones o argumentos:

su root -c 'who am i'
ssh user@remote uname -a
sh -c 'who am i; echo success'

Muchos de estos comandos también aceptarán comandos en la entrada estándar:

printf 'uname -a; who am i; uptime' | su
printf 'uname -a; who am i; uptime' | ssh user@remote
printf 'uname -a; who am i; uptime' | sh

que también le permite utilizar cómodamente aquí los documentos:

ssh user@remote <<'____HERE'
    uname -a
    who am i
    uptime
____HERE

sh <<'____HERE'
    uname -a
    who am i
    uptime
____HERE

Para los comandos que aceptan un único argumento de comando, ese comando puede ser sho bashcon múltiples comandos:

sudo sh -c 'uname -a; who am i; uptime'

Además, generalmente no necesita un explícito exitporque el comando terminará de todos modos cuando haya ejecutado el script (secuencia de comandos) que pasó para su ejecución.

tripleee avatar Jun 02 '2016 08:06 tripleee

Si desea una solución genérica que funcione para cualquier tipo de programa, puede utilizar el expectcomando.

Extracto de la página del manual:

Expect es un programa que "habla" con otros programas interactivos según un guión. Siguiendo el guión, Expect sabe qué se puede esperar de un programa y cuál debe ser la respuesta correcta. Un lenguaje interpretado proporciona estructuras de control ramificadas y de alto nivel para dirigir el diálogo. Además, el usuario puede tomar el control e interactuar directamente cuando lo desee, y luego devolver el control al script.

A continuación se muestra un ejemplo práctico que utiliza expect:

set timeout 60

spawn sudo su -

expect "*?assword" { send "*secretpassword*\r" }
send_user "I should be root now:"

expect "#" { send "whoami\r" }
expect "#" { send "exit\r" }
send_user "Done.\n"
exit

Luego, el script se puede iniciar con un simple comando:

$ expect -f custom.script

Puede ver un ejemplo completo en la siguiente página: http://www.journaldev.com/1405/expect-script-example-for-ssh-and-su-login-and-running-commands

Nota: La respuesta propuesta por @tripleee solo funcionaría si la entrada estándar pudiera leerse una vez al inicio del comando, o si se hubiera asignado un tty, y no funcionará para ningún programa interactivo.

Ejemplo de errores si usas una tubería

echo "su whoami" |ssh remotehost
--> su: must be run from a terminal

echo "sudo whoami" |ssh remotehost
--> sudo: no tty present and no askpass program specified

En SSH, puede forzar una asignación de TTY con múltiples -tparámetros, pero cuando sudole solicite la contraseña, fallará.

Sin el uso de un programa, expectcualquier llamada a una función/programa que pueda obtener información de la entrada estándar hará que el siguiente comando falle:

ssh use@host <<'____HERE'
  echo "Enter your name:"
  read name
  echo "ok."
____HERE
--> The `echo "ok."` string will be passed to the "read" command
Adam avatar Jun 10 '2016 12:06 Adam