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

Tengo un script donde necesito iniciar un comando, luego paso 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: el su tiene éxito, pero entonces el símbolo del sistema simplemente me está mirando. Si who am i exit en el prompt, el echo y who am i etc. ¡empiezo a ejecutar! Y el echo done. no se ejecuta en absoluto.

Del mismo modo, necesito que esto funcione sobre 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 de su o ssh en particular. La intención es que esta pregunta se convierta en un canónico para este patrón particular.

Un script de shell es una secuencia de comandos. El shell leerá el archivo de script y ejecutará esos comandos uno después del otro.

En el caso habitual, no hay sorpresas aquí; 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 así no es como funciona.

Básicamente, los scripts funcionan exactamente como los comandos interactivos, pero cómo funcionan exactamente debe entenderse correctamente. Interactivamente, el shell lee un comando (desde la entrada estándar), ejecuta ese comando (con la entrada de la entrada estándar), y cuando termina, lee otro comando (desde la entrada estándar).

Ahora, al ejecutar un script, la entrada estándar sigue siendo el 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. (De hecho, lo contrario sería muy engorroso: cualquier read consumiría la siguiente línea del guión, cat absorbería todo el rest del guión, ¡y no habría forma de interactuar con él!) El archivo de guión solo contiene comandos para el instancia de shell que lo ejecuta (aunque, por supuesto, todavía puede usar un documento aquí, etc. para insertar entradas como argumentos de comando).

En otras palabras, estos comandos “incomprendidos” ( su , ssh , sh , sudo , bash , etc.) cuando se 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, a menudo eso no es lo que quieres.

Todos estos comandos tienen formas de aceptar comandos por medios que no sean una sesión de terminal interactiva. Normalmente, cada comando admite una forma de pasar los comandos como opciones o argumentos:

 su root 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' | ssh user@remote printf 'uname -a; who am i; uptime' | sh 

que también le permite usar convenientemente documentos aquí:

 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 solo argumento de comando, ese comando puede ser sh o bash con múltiples comandos:

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

Por otro lado, generalmente no necesita una exit explícita porque el comando terminará de todos modos cuando haya ejecutado el script (secuencia de comandos) que pasó para su ejecución.

Agregando a la respuesta de tripleee :

Es importante recordar que la sección de la secuencia de comandos formateada como un 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 forma ligeramente diferente, dependiendo de dónde desee que se realicen esas expansiones.

1. Todas las expansiones deben realizarse dentro del scope del shell primario.

Entonces el delimitador del documento aquí debe ser sin comillas .

 command <  

Ejemplo:

 #!/bin/bash a=0 mylogin=$(whoami) sudo sh <  

Salida:

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

2. Todas las expansiones se deben realizar dentro del scope del shell hijo.

Luego el delimitador del documento aquí debe ser citado .

 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 

Salida:

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

3. Algunas expansiones se deben realizar en el shell hijo, algunas - en el padre.

Entonces, el delimitador del documento aquí debe estar sin comillas y debe escapar de las expresiones de expansión que deben realizarse en el shell secundario .

Ejemplo:

 #!/bin/bash a=0 mylogin=$(whoami) sudo sh <  

Salida:

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

Si desea una solución genérica que funcione para cualquier tipo de progtwig, puede usar el comando expect .

Extracto de la página del manual:

Expect es un progtwig que “habla” a otros progtwigs interactivos de acuerdo con un guión. Siguiendo el guión, Expect sabe qué se puede esperar de un progtwig y cuál debería ser la respuesta correcta. Un lenguaje interpretado proporciona ramificaciones y estructuras de control 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.

Aquí hay un ejemplo de trabajo usando 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 

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 comienzo del comando, o si se hubiera asignado un tty, y no funcionará para ningún progtwig interactivo.

Ejemplo de errores si usa un tubo

 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 parámetros -t , pero cuando sudo le pida la contraseña, fallará.

Sin el uso de un progtwig como el expect cualquier llamada a una función / progtwig que pueda obtener información de stdin hará que falle el siguiente comando:

 ssh use@host < <'____HERE' echo "Enter your name:" read name echo "ok." ____HERE --> The `echo "ok."` string will be passed to the "read" command