¿Cuál es la forma más limpia de ssh y ejecutar múltiples comandos en Bash?

349

Ya tengo un agente ssh configurado, y puedo ejecutar comandos en un servidor externo en el script Bash haciendo cosas como:

ssh blah_server "ls; pwd;"

Ahora, lo que realmente me gustaría hacer es ejecutar muchos comandos largos en un servidor externo. Encerrar todo esto entre comillas sería bastante feo, y realmente preferiría evitar hablar varias veces solo para evitar esto.

Entonces, ¿hay alguna manera de hacer esto de una vez encerrada entre paréntesis o algo así? Estoy buscando algo en la línea de:

ssh blah_server (
   ls some_folder;
   ./someaction.sh;
   pwd;
)

Básicamente, estaré contento con cualquier solución siempre que esté limpia.

Editar

Para aclarar, estoy hablando de que esto sea parte de un script bash más grande. Es posible que otras personas necesiten lidiar con el script más adelante, por lo que me gustaría mantenerlo limpio. No quiero tener un script bash con una línea que se vea así:

ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"

porque es extremadamente feo y difícil de leer.

Eli
fuente
2
Hmm, ¿qué hay de poner todo eso en un script en el servidor y simplemente llamarlo con una sola sshinvocación?
Nikolai Fetissov
@Nikolai si los comandos depende del lado del cliente, que puede escribirse en un script de shell y, a continuación scp, sshy correr. Esta será la forma más limpia, creo.
khachik
66
Esto es parte de un script bash más grande, por lo que prefiero no dividirlo con la mitad viviendo en mi computadora personal y la otra mitad viviendo en el servidor y ejecutando ssh. Si es posible, me gustaría mantenerlo como un script ejecutado desde mi computadora personal. ¿Realmente no hay una forma limpia de encerrar un montón de comandos en un ssh?
Eli
La mejor manera es no usar bash sino Perl, Python, Ruby, etc.
salva
1
¿Por qué quieres evitar poner los comandos remotos entre comillas? Puede tener nuevas líneas dentro de las comillas, tantas como desee; y usar una cadena en lugar de una entrada estándar significa que la entrada estándar está disponible, por ejemplo, para leer la entrada al script remoto. (Aunque en Unix, comillas simples son generalmente preferibles a las comillas dobles, a menos que necesite específicamente la cáscara local para evaluar algunas partes de la cadena.)
tripleee

Respuestas:

457

¿Qué tal un documento Bash Here :

ssh otherhost << EOF
  ls some_folder; 
  ./someaction.sh 'some params'
  pwd
  ./some_other_action 'other params'
EOF

Para evitar los problemas mencionados por @Globalz en los comentarios, puede (dependiendo de lo que esté haciendo en el sitio remoto) salirse con la suya al reemplazar la primera línea con

ssh otherhost /bin/bash << EOF

Tenga en cuenta que puede hacer la sustitución de variables en el documento Aquí, pero es posible que tenga que lidiar con problemas de citas. Por ejemplo, si cita la "cadena de límite" (es decir, EOFen lo anterior), entonces no puede hacer sustituciones variables. Pero sin citar la cadena límite, las variables se sustituyen. Por ejemplo, si ha definido $NAMEanteriormente en su script de shell, podría hacer

ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF

y crearía un archivo en el destino otherhostcon el nombre de lo que le haya asignado $NAME. También se aplican otras reglas sobre las citas de script de shell, pero son demasiado complicadas para entrar aquí.

Paul Tomblin
fuente
66
¡Esto se ve exactamente como lo que quiero! Como funciona ¿Tendría un enlace a una página que lo explica?
Eli
99
+1 Estaba pensando en mí mismo: aquí hay un lugar para leer sobre esto: tldp.org/LDP/abs/html/here-docs.html
bosmacs
14
También obtengo esta salida en mi local: el pseudo-terminal no se asignará porque stdin no es un terminal.
Globalz
14
Puede ser importante que cite la palabra (es decir, primero 'EOF') para evitar la expansión de las líneas de comando. Ver: hombre bash | menos + / 'Aquí Documentos'
reunido
66
Paul, solo lo sugiero porque con casi 200k vistas sobre esta pregunta, parece que mucha gente viene aquí cuando hace scripts con ssh. Es común tener que inyectar valores al crear secuencias de comandos. Si no fuera por el ruido en las otras preguntas y comentarios, simplemente haría una y estaría en camino, pero es poco probable que se vea en esta etapa. Una nota al pie de página puede ahorrarle a la gente algunos dolores de cabeza importantes.
koyae
123

Edite su script localmente, luego póngalo en ssh, por ejemplo

cat commands-to-execute-remotely.sh | ssh blah_server

donde se commands-to-execute-remotely.shparece a tu lista de arriba:

ls some_folder
./someaction.sh
pwd;
bosmacs
fuente
77
Esto tiene la gran ventaja de que usted sabe exactamente lo que está ejecutando el script remoto: no hay problemas con las citas. Si necesita comandos dinámicos, puede usar un script de shell con un subshell, que aún se conecta al ssh, es decir ( echo $mycmd $myvar ; ...) | ssh myhost, al igual que con el uso de cat, usted sabe exactamente lo que está pasando en el flujo de comandos ssh. Y, por supuesto, la subshell en el script puede ser de varias líneas para facilitar la
RichVel
66
¿Se puede hacer esto con argumentos en el commands-to-execute-remotely.sh?
elaRosca
1
Creo que esto es mucho más útil que la solución de paultomblin. Dado que el script se puede redirigir a cualquier servidor ssh sin tener que abrir el archivo. También es mucho más legible.
Patrick Bassut
3
sí, pero usando echo o un documento aquí (ver la respuesta superior): use: $localvarpara interpretar una variable definida localmente,\$remotevar para interpretar remotamente una variable definida remotamente, \$(something with optionnal args)para obtener la salida de algo ejecutado en el servidor remoto. Un ejemplo que puede enviar a través de 1 (o, como se muestra aquí, múltiples comandos ssh):echo " for remotedir in /*/${localprefix}* ; do cd \"\$remotedir\" && echo \"I am now in \$(pwd) on the remote server \$(hostname) \" ; done " | ssh user1@hop1 ssh user2@hop2 ssh user@finalserver bash
Olivier Dulac
2
Hmm ... ¿en qué se diferencia esto del uso de redirección de entrada, es decir ssh blah_server < commands-to-execute-remotely.sh?
flow2k
41

Para que coincida con su código de muestra, puede ajustar sus comandos dentro de qoutes simples o dobles. Por ejemplo

ssh blah_server "
  ls
  pwd
"
Andrei B
fuente
Me gusta este formato, sin embargo, lamentablemente no es útil para almacenar datos estándar en una variable.
Signus
1
Signus, ¿qué quieres decir con "almacenar datos estándar en una variable"?
Andrei B el
1
@Signus Es perfectamente posible hacer lo que usted describe, aunque probablemente quiera usar comillas simples en lugar de comillas dobles alrededor de los comandos remotos (o escapar de los operadores que deben escaparse dentro de comillas dobles para evitar que su shell local intercepte y interpolarlos).
tripleee
No puedo poner dentro del código de comillas dobles un comando como este fileToRemove = $ (find. -Type f -name 'xxx.war'). fileToRemove debe tener un nombre de archivo dentro, pero en su lugar tiene una cadena vacía. ¿Se debe escapar algo?
jkonst
37

Veo dos formas:

Primero haces un socket de control como este:

 ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>

y ejecuta tus comandos

 ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>

De esta manera, puede escribir un comando ssh sin volver a conectarse al servidor.

El segundo sería generar dinámicamente el script, scpejecutarlo y ejecutarlo.

término
fuente
20

Esto también se puede hacer de la siguiente manera. Ponga sus comandos en una secuencia de comandos, nombremos comandos-inc.sh

#!/bin/bash
ls some_folder
./someaction.sh
pwd

Guarda el archivo

Ahora ejecútelo en el servidor remoto.

ssh user@remote 'bash -s' < /path/to/commands-inc.sh

Nunca me ha fallado.

RJ
fuente
¡Similar a lo que estaba pensando originalmente! ¿Pero por qué es bash -snecesario?
flow2k
Además, ¿se #!/bin/bashusa realmente?
flow2k
2
El -s está ahí por compatibilidad. De man bash -s Si la opción -s está presente, o si no quedan argumentos después del procesamiento de la opción, los comandos se leen desde la entrada estándar. Esta opción permite establecer los parámetros posicionales al invocar un shell interactivo.
RJ
1
RJ, gracias! Con mi primer comentario, parece que solo lo hacemos ssh user@remote < /path/to/commands-inc.sh(parece funcionar para mí). Bueno, supongo que su versión garantiza que usemos el bashshell, y no otro shell, ese es el propósito, ¿no?
flow2k
2
Gracias. Sí, algunos de nosotros tenemos nuestras notas y hábitos de versiones anteriores de bash y otras conchas. :-)
RJ
10

Ponga todos los comandos en un script y se puede ejecutar como

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh
Jai Prakash
fuente
Algo extraño pero funciona bien. Y los argumentos se pueden pasar al script (antes o después de la redirección). por ejemplo, 'ssh <remote-user> @ <remote-host> "bash -s" arg1 <./ remote-commands.sh arg2' eg 'ssh <remote-user> @ <remote-host> "bash -s" - - -arg1 <./ remote-commands.sh arg2 '
gaoithe
Tuve una pregunta similar con otra respuesta anterior, pero ¿cuál es el propósito de incluir bash -s?
flow2k
1
@ flow2k -s es para indicar el bash para leer los comandos de la entrada estándar. Aquí está la descripción de las manpáginasIf the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell.
Jai Prakash
@JaiPrakash Gracias por esto, pero en realidad estaba pensando si podríamos eliminar el bashcomando por completo, es decir ssh remote-user@remote-host <./remote-commands.sh. Lo intenté a mi manera y pareció funcionar, aunque no estoy seguro de si es deficiente de alguna manera.
flow2k
@ flow2k, según tengo entendido de las páginas man, no habrá ninguna deficiencia sin esa opción. Es necesario si está intentando transmitir algún argumento. - gracias
Jai Prakash
9

SSH y ejecutar múltiples comandos en Bash.

Comandos separados con punto y coma dentro de una cadena, pasados ​​a echo, todos canalizados al comando ssh. Por ejemplo:

echo "df -k;uname -a" | ssh 192.168.79.134

Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda2       18274628 2546476  14799848  15% /
tmpfs             183620      72    183548   1% /dev/shm
/dev/sda1         297485   39074    243051  14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux
arnab
fuente
arnab, en el futuro, describa brevemente lo que hace su código. Luego hable sobre cómo funciona, luego ingrese el código. Luego ponga el código en un bloque de código para que sea fácil de leer.
Eric Leschinski
A partir de la pregunta, ofreció exactamente lo que el OP quiere evitar: "No quiero tener un script bash con una línea que se vea como ..."
jww
6

Para cualquiera que tropezó aquí como yo, tuve éxito al escapar del punto y coma y la nueva línea:

Primer paso: el punto y coma. De esta manera, no rompemos el comando ssh:

ssh <host> echo test\;ls
                    ^ backslash!

Enumeró el directorio host / home remoto (conectado como root), mientras que

ssh <host> echo test;ls
                    ^ NO backslash

enumeró el directorio de trabajo actual.

Siguiente paso: romper la línea:

                      v another backslash!
ssh <host> echo test\;\
ls

Esto nuevamente enumeró el directorio de trabajo remoto - formato mejorado:

ssh <host>\
  echo test\;\
  ls

Si realmente es mejor que el documento o las citas alrededor de las líneas discontinuas, bueno, no debo decidirlo ...

(Usando bash, Ubuntu 14.04 LTS.)

Aconcagua
fuente
1
Lo que es aún mejor, puede usar && y || de esta manera, también - echo test \ & \ & ls
Miro Kropacek
@MiroKropacek Eso también es bueno. ¿Mejor? Depende de lo que quieras; ejecutar el segundo comando condicionalmente , luego sí, ejecutarlo incondicionalmente (no importa si el primero tuvo éxito o falló), luego no ...
Aconcagua
@MiroKropacek, no tuve que escapar del && al poner comillas dobles alrededor de los comandos:ssh host "echo test && ls"
not2savvy
5

Las respuestas publicadas utilizando cadenas de varias líneas y múltiples secuencias de comandos bash no me funcionaron.

  • Las cuerdas largas multilínea son difíciles de mantener.
  • Las secuencias de comandos bash separadas no mantienen variables locales.

Aquí hay una forma funcional de ssh y ejecutar múltiples comandos mientras se mantiene el contexto local.

LOCAL_VARIABLE=test

run_remote() {
    echo "$LOCAL_VARIABLE"
    ls some_folder; 
    ./someaction.sh 'some params'
    ./some_other_action 'other params'
}

ssh otherhost "$(set); run_remote"
Michael Petrochuk
fuente
3
Estás pensando en esto como si la única razón por la que alguien quisiera hacer esto es si está sentado en una línea de comando. Hay otras razones más comunes para esta pregunta, así, como la ejecución de comandos a través de entornos distribuidos con herramientas como Rundeck, Jenkins, etc.
Charles Addis
esto funciona pero recibo mensajes de error no deseados
Annahri
5

No estoy seguro de si es el más limpio para comandos largos, pero ciertamente es el más fácil:

ssh user@host "cmd1; cmd2; cmd3"
DimiDak
fuente
¡Lo mejor en simplicidad!
not2savvy
4

Esto funciona bien para crear scripts, ya que no tiene que incluir otros archivos:

#!/bin/bash
ssh <my_user>@<my_host> "bash -s" << EOF
    # here you just type all your commmands, as you can see, i.e.
    touch /tmp/test1;
    touch /tmp/test2;
    touch /tmp/test3;
EOF
sjas
fuente
La parte "$ (which bash) -s" le da la ubicación de bash en la máquina local, en lugar de la máquina remota. Creo que quiere '$ (which bash) -s' en su lugar (comillas simples para suprimir la sustitución de parámetros locales).
jsears
1
Paul Tomblin proporcionó la respuesta del documento Bash Here 6 años antes. ¿Qué valor agrega esta respuesta?
jww
-sbandera, lo cito, es posible que pueda salirse con la suya reemplazando la primera línea con ... , y no pude salir con solo llamar a bash, lo recuerdo vagamente.
sjas
4

La forma más fácil de configurar su sistema para usar sesiones ssh individuales de forma predeterminada con multiplexación.

Esto se puede hacer creando una carpeta para los sockets:

mkdir ~/.ssh/controlmasters

Y luego agregue lo siguiente a su configuración .ssh:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/controlmasters/%r@%h:%p.socket
    ControlMaster auto
    ControlPersist 10m

Ahora, no necesita modificar ninguno de sus códigos. Esto permite múltiples llamadas a ssh y scp sin crear múltiples sesiones, lo cual es útil cuando se necesita más interacción entre sus máquinas locales y remotas.

Gracias a la respuesta de @ terminus, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ y https://en.wikibooks.org / wiki / OpenSSH / Cookbook / Multiplexing .

Jonathan
fuente