Cómo ejecutar un comando por SSH

Pon el comando después del host, entre comillas simples: ssh user@server 'uptime | cut -c1-20'. Se ejecuta en el servidor, su salida vuelve a ti y ssh termina con el código de salida del comando, o 255 si falló SSH. Las comillas simples impiden que tu shell local modifique el comando. Añade -n cuando uses ssh dentro de un bucle.

Actualizada el . OpenSSH 10.3 en macOS hacia OpenSSH 10.0 en Debian 13 en un contenedor desechable, con bash en ambos lados.

Comillas: qué shell lee qué

Dos shells leen tu comando: primero el tuyo y luego el del servidor. Las comillas deciden qué shell expande qué.

NAME=local
ssh server "echo $NAME"       # local shell expands it, prints: local
ssh server 'echo $HOME $NAME' # server expands it, prints: /home/tester
                              # NAME does not exist on the server

ssh une sus argumentos con espacios simples y envía una sola cadena, así que las comillas que solo ve tu shell local se pierden:

$ ssh server echo "a  b"
a b
$ ssh server 'echo "a  b"'
a  b

Lo mismo ocurre con las tuberías. El primer comando de abajo ejecuta wc en el servidor, y el segundo lo ejecuta en tu máquina:

ssh server 'ls /tmp | wc -l'
ssh server ls /tmp | wc -l

Para pasar un valor local que contenga espacios o comillas, deja que bash lo escape:

F="a b'c"
ssh server "printf '%s\n' $(printf %q "$F")"

Varias líneas: un heredoc

Pon comillas al marcador del heredoc y nada se expande en local:

ssh server 'bash -s' <<'REMOTE'
echo "user: $(whoami)"
cd /tmp && pwd
REMOTE

Sin comillas alrededor del marcador, tu shell local expande primero las variables. Escapa las que van dirigidas al servidor:

V=abc
ssh server 'bash -s' <<REMOTE
echo "local var: $V, remote: \$HOSTNAME"
REMOTE

Para ejecutar en el servidor un script local con argumentos:

ssh server 'bash -s -- one two' < script.sh

Códigos de salida

ComandoCódigo de salida
ssh server 'exit 3'3
ssh server 'false; true'0, porque solo cuenta el último comando
ssh server 'set -e; false; echo no'1, y se detiene en false
ssh server nosuchcmd127, comando no encontrado en el servidor
ssh -p 1 server true255, SSH no pudo conectar

Un comando remoto también puede terminar con 255 por sí mismo, así que 255 solo sugiere un fallo de SSH. La salida de error estándar va separada de la salida estándar, por lo que 2>/dev/null y > file funcionan como siempre de tu lado.

Por qué ssh en un bucle se detiene tras un host

ssh lee la entrada estándar y la envía al servidor, así que se come el resto de la lista. Este bucle se ejecutó una sola vez en nuestra prueba, con tres líneas en el archivo:

while read -r h; do ssh "$h" uptime; done < hosts

-n le dice a ssh que no lea la entrada estándar. Este se ejecutó tres veces:

while read -r h; do ssh -n "$h" uptime; done < hosts

Antes de un bucle sobre servidores de producción, lee cómo evitar errores en servidores de producción.

Enviar datos por la entrada estándar

Sin -n, la entrada estándar es útil. Los comandos remotos pueden leerla:

printf 'hello\n' | ssh server 'tr a-z A-Z'
tar -cf - folder | ssh server 'tar -C /tmp -xf -'

Comandos que necesitan una terminal

Con un comando, ssh no asigna terminal, así que tty imprime not a tty y sudo no puede pedir una contraseña:

sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper

Añade -t para programas interactivos:

ssh -t server sudo systemctl restart nginx

Si tu propia entrada estándar no es una terminal, por ejemplo en un script, -t imprime Pseudo-terminal will not be allocated because stdin is not a terminal. Usa -tt para forzarlo, y consulta la guía de ese error.

Trabajos largos y comandos en segundo plano

Un trabajo en segundo plano que mantiene abierta la salida hace que ssh siga esperando. El primer comando de abajo tardó 2 segundos en nuestra prueba, y el segundo volvió al instante:

ssh server 'sleep 2 &'
ssh server 'nohup sleep 2 >/dev/null 2>&1 &'

Para trabajo real que deba sobrevivir a la conexión, usa tmux en el servidor. ssh -f server 'command' solo pasa tu ssh local a segundo plano tras iniciar sesión.

El entorno difiere del de tu inicio de sesión

Un comando por SSH no se ejecuta en un shell de login, así que archivos como ~/.profile no se leen. En nuestra prueba shopt login_shell imprimió off y PATH era /usr/local/bin:/usr/bin:/bin:/usr/games. Si un comando funciona a mano pero da command not found por SSH, usa su ruta completa.

Preguntas

¿Cómo ejecuto sudo por SSH sin el error de terminal?

Añade -t, como en ssh -t server sudo systemctl restart nginx, para que sudo tenga un terminal donde pedir la contraseña. En scripts usa -tt o configura sudo para ese comando.

¿Por qué un comando funciona a mano pero no por SSH?

Un comando por SSH se ejecuta en una shell que no es de inicio de sesión, así que no se lee ~/.profile y el PATH es más corto. Usa la ruta completa del programa.

¿Cómo ejecuto varios comandos en una sola llamada SSH?

Ponlos en una sola cadena entre comillas, como ssh server 'cd /tmp && ls', o envía un script con ssh server 'bash -s' < script.sh.

Fuentes

Más en Sesiones y tmux

Todas las guías