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
| Comando | Có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 nosuchcmd | 127, comando no encontrado en el servidor |
ssh -p 1 server true | 255, 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.