Bash: solicitud remota interactiva

16

Tengo un script que se conecta a un servidor remoto y verifico si algún paquete está instalado:

ssh root@server 'bash -s' < myscript.sh

myscript.sh:

OUT=`rpm -qa | grep ntpdate`
if [ "$OUT" != "" ] ; then
    echo "ntpdate already installed"
else
    yum install $1
fi

Este ejemplo podría simplificarse. Aquí está el myscript2.shque tiene el mismo problema:

read -p "Package is not installed. Do you want to install it (y/n)?" choise

Mi problema es que bash no puede leer mis respuestas de forma interactiva.

¿Hay alguna manera de ejecutar el script local de forma remota sin perder la capacidad de solicitar al usuario?

Anthony Ananich
fuente
¿Puedes elaborar? ¿No está realmente claro lo que estás pidiendo? Un poco más de su código sería útil.
slm
1
Solo para su información, la razón por la que esto no funciona es porque está pasando su secuencia de comandos a través de STDIN. Por lo tanto, cuando bash va a leer desde STDIN, obtiene su script (o nada, ya que ya ha leído todo el script y no queda nada).
Patrick
1
Recurso útil relacionado con esto: backreference.org/2011/08/10/…
slm

Respuestas:

22

Intenta algo como esto:

$ ssh -t yourserver "$(<your_script)"

Las -tfuerzas de una asignación tty, $(<your_script)lee el archivo completo y en estos casos pasa el contenido como un argumento a ssh, que será ejecutado por el shell del usuario remoto.

Si el script necesita parámetros, páselos después del script:

$ ssh -t yourserver "$(<your_script)" arg1 arg2 ...

Funciona para mí, aunque no estoy seguro de si es universal.

Estera
fuente
3
Eso es exactamente lo que estaba buscando, ¡gracias!
Anthony Ananich
Buena solución, pero ¿hay alguna forma de pasarle un argumento a la your scriptvez que conserva las ventajas de la sintaxis que usó en su respuesta? Usando ssh -t yourserver "$(<your_script --some_arg)"solo resultados en bash: --some_arg: command not found.
sampablokuper el
1
@sampablokuper: pruebassh ... $(<script) script_arg1 script_arg2 ...
Mat
@ Mat, gracias, WFM :) ¿Quizás agregarlo al cuerpo de tu respuesta?
sampablokuper
3

Su problema es que sshinicia un inicio de sesión, un shell no interactivo en la máquina remota. La solución fácil obvia sería copiar el script en el servidor remoto y ejecutarlo desde allí:

scp myscript.sh root@server:/tmp && ssh root@server /tmp/myscript.sh

Si copiar no es una opción por alguna razón, modificaría el script para conectar primero y verificar si $1está instalado, luego volver a conectar e instalar según sea necesario:

OUT=$(ssh root@server rpm -qa | grep "$1");
if [ "$OUT" != "" ] ; then
    echo "$1 already installed"
else
   read -p "Package $1 is not installed. Do you want to install it (y/n)?" choice
   if [ "$choice" -eq "y" ]; then
       ssh root@server yum install "$1"
   fi
fi
terdon
fuente
De eso tenía miedo. La conexión a ssh es la operación más larga y estaba tratando de evitarla.
Anthony Ananich
@AnthonyAnanich Puede guardar el tiempo de establecimiento de la conexión configurando una conexión maestra .
Gilles 'SO- deja de ser malvado'
@Gilles Estaba pensando en sugerir eso, pero pensé que no funcionaría con mi sugerencia. Si la primera conexión ssh actúa como maestra, se cerrará después de las $OUT=$(ssh root@server rpm -qa | grep "$1");salidas y luego la segunda conexión tomará tanto tiempo como la primera. ¿Me equivoco?
terdon
1
@terdon Ejecuta algo como ssh -M foo.example.com sleep 99999999o ssh -M foo.example.com read <somefifocomo maestro y mátalo explícitamente (con killo echo done >somefifo) cuando hayas terminado.
Gilles 'SO- deja de ser malvado'
0

Aquí hay una buena explicación .

Así que adapté el guión a

hostname
echo -n "Make your choice :"
read choice
echo "You typed " ${choice}
echo done

Y esto no funcionó.

así que moví el script al control remoto para evitar la redirección local en ssh. (Mis comandos están en un archivo llamado f )

cat f | ssh user@remotehost.com 'cat >remf'
ssh user@remotehost bash remf

Esto funcionó. Aquí está la salida:

christian@clafujiu:~/tmp$ ssh localhost bash tmp/f
christian@localhost's password: 
Linux clafujiu 2.6.32-52-generic #114-Ubuntu SMP Wed Sep 11 19:00:15 UTC 2013 i686 GNU/Linux
Sun Nov 10 14:58:56 GMT 2013
Make your choice :abc
You typed  abc
done

Como @terdon mencionó que la intención original era ejecutar scripts locales de forma remota, la copia remota se puede automatizar, este es solo un ejemplo en una sola línea.

REMID=`cat f |ssh user@remotehost 'cat > remf_$$; echo $$'` ;ssh root@redtoadservices.com "bash remf_${REMID} ; rm -v remf_${REMID}"
X Tian
fuente
2
¿Cómo "funcionó"? ¿Ejecutó este script con bash -s < script.shel OP? ¿Podría incluir la explicación en su respuesta en lugar de vincularla?
terdon
Vaya, lo siento, olvidé agregar algo crítico. También copié el script en el control remoto para evitar la redirección local. Agregaré esto a mi respuesta para aclararlo.
X Tian
1
Si copia el script al control remoto, el problema desaparece. El problema del OP es que está tratando de dar una entrada interactiva a un script local que se ejecuta de forma remota. Por supuesto, se ejecutará si lo copia. El OP quiere ejecutar este script cuando se conecta a un servidor remoto, presumiblemente a muchos servidores remotos. Dudo que copiarlo sea práctico a menos que lo automatices.
terdon
0

He buscado soluciones a este problema varias veces en el pasado, sin embargo, nunca he encontrado una solución completamente satisfactoria. La conexión a ssh pierde tu interactividad. Dos conexiones (scp / ssh) son más lentas, y su archivo temporal podría quedar por ahí. Y todo el script en la línea de comando a menudo termina escapando del infierno.

Recientemente descubrí que el tamaño del búfer de la línea de comandos suele ser bastante grande ('getconf ARG_MAX> 2MB donde busqué). Y esto me hizo pensar en cómo podría usar esto y mitigar el problema de escape.

El resultado es:

ssh -t <host> /bin/bash "<(echo "$(cat my_script | base64 | tr -d '\n')" | base64 --decode)" <arg1> ...

o usando un documento aquí y cat:

ssh -t <host> /bin/bash $'<(cat<<_ | base64 --decode\n'$(cat my_script | base64)$'\n_\n)' <arg1> ...

He ampliado esta idea para producir un script de ejemplo BASH completamente funcional sshxque pueda ejecutar scripts arbitrarios (no solo BASH), donde los argumentos también pueden ser archivos de entrada locales, a través de ssh. Ver aquí .

SourceSimian
fuente
Interesante, pero ¿cómo y / o cuándo es esto mejor que la respuesta de Mat ?
Scott,
1
La respuesta de Mat es un buen primer paso (que a menudo uso), sin embargo, no puede manejar una secuencia de comandos de contenido arbitrario, como los "caracteres que se deben escapar. Y también está restringido a los scripts de BASH. Mi solución es un caso general para cualquier contenido de script de cualquier lenguaje de script instalado. Además, en sshx demuestro aún más la serialización de archivos de argumentos, lo cual es bastante ingenioso.
SourceSimian
(1) ¿Puedes publicar un MCVE de un script para el cual la respuesta de Mat falla (y la tuya funciona)? (2) Consulte ¿Qué tiene de malo "echo $ (cosas)" o "echo` cosas` "?  Su echo "$(cat my_script | base64)" | base64 --decodeaspecto es equivalente (-ish) a cat my_script | base64 | base64 --decode, que se parece mucho a un no-op.
Scott
Hola @Scott (1): Considere el guión: echo "ARG1=$1, USER=$USER, END". a) $ ssh host "$(<my_bash)" foo-> ARG1=, USER=user, END foo. b) $ ssh host bash "<(echo $(cat my_bash|base64) | base64 --decode)" foo-> ARG1=foo, USER=user, END. Aquí (a) se está ejecutando efectivamente: bash -c $'#!/bin/bash\necho "ARG1=$1, USER=$USER, END" foo'o algo similar. Tenga en cuenta que el argumento no funciona. Donde (b) se está ejecutando: bash <(echo IyEvY...5EIgo= | base64 --decode) foo. (2) Usé base64 como la codificación de transporte.
SourceSimian