Come eseguire un comando su un server via SSH

Metti il comando dopo l'host, tra apici singoli: ssh user@server 'uptime | cut -c1-20'. Viene eseguito sul server, l'output torna a te e ssh termina con il codice di uscita del comando, o 255 se è fallito SSH stesso. Gli apici singoli impediscono alla tua shell locale di modificare il comando. Aggiungi -n quando usi ssh dentro un ciclo.

Aggiornata il . OpenSSH 10.3 su macOS verso OpenSSH 10.0 su Debian 13 in un container usa e getta, con bash da entrambe le parti.

Gli apici: quale shell legge cosa

Due shell leggono il tuo comando: prima la tua e poi quella sul server. Gli apici decidono quale shell espande cosa.

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 unisce i suoi argomenti con singoli spazi e invia una sola stringa, quindi gli apici che vede solo la tua shell locale vanno persi:

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

Lo stesso vale per le pipe. Il primo comando qui sotto esegue wc sul server, il secondo lo esegue sulla tua macchina:

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

Per passare un valore locale che contiene spazi o apici, lascia che sia bash a fare l'escape:

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

Più righe: un heredoc

Metti tra apici il marcatore dell'heredoc e niente viene espanso in locale:

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

Senza apici attorno al marcatore, la tua shell locale espande prima le variabili. Fai l'escape di quelle destinate al server:

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

Per eseguire sul server uno script locale con degli argomenti:

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

Codici di uscita

ComandoCodice di uscita
ssh server 'exit 3'3
ssh server 'false; true'0, perché conta solo l'ultimo comando
ssh server 'set -e; false; echo no'1, e si ferma a false
ssh server nosuchcmd127, comando non trovato sul server
ssh -p 1 server true255, SSH non è riuscito a connettersi

Un comando remoto può terminare con 255 anche da solo, quindi 255 fa solo pensare a un errore di SSH. Lo standard error resta separato dallo standard output, quindi 2>/dev/null e > file funzionano come al solito dal tuo lato.

Perché ssh in un ciclo si ferma dopo un host

ssh legge lo standard input e lo invia al server, quindi si mangia il resto dell'elenco. Nella nostra prova questo ciclo è girato una sola volta, con tre righe nel file:

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

-n dice a ssh di non leggere lo standard input. Questo è girato tre volte:

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

Prima di un ciclo sui server di produzione, leggi come evitare errori sui server di produzione.

Inviare dati dallo standard input

Senza -n, lo standard input diventa utile. I comandi remoti possono leggerlo:

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

Comandi che richiedono un terminale

Con un comando, ssh non assegna un terminale, quindi tty stampa not a tty e sudo non può chiedere la password:

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

Aggiungi -t per i programmi interattivi:

ssh -t server sudo systemctl restart nginx

Se il tuo standard input non è un terminale, per esempio in uno script, -t stampa Pseudo-terminal will not be allocated because stdin is not a terminal. Usa -tt per forzarlo, e vedi la guida a quell'errore.

Lavori lunghi e comandi in background

Un lavoro in background che tiene ancora aperto l'output fa restare ssh in attesa. Il primo comando qui sotto nella nostra prova ha impiegato 2 secondi, il secondo è tornato subito:

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

Per un lavoro vero che deve sopravvivere alla connessione, usa tmux sul server. ssh -f server 'command' mette in background solo il tuo ssh locale dopo il login.

L'ambiente è diverso da quello del tuo login

Un comando via SSH non gira in una login shell, quindi file come ~/.profile non vengono letti. Nella nostra prova shopt login_shell ha stampato off e PATH era /usr/local/bin:/usr/bin:/bin:/usr/games. Se un comando funziona a mano ma dà command not found via SSH, usa il suo percorso completo.

Domande

Come eseguo sudo via SSH senza l'errore del terminale?

Aggiungi -t, come in ssh -t server sudo systemctl restart nginx, così sudo ha un terminale per chiedere la password. Negli script usa -tt o configura sudo per quel comando.

Perché un comando funziona a mano ma non via SSH?

Un comando via SSH gira in una shell che non è di login, quindi ~/.profile non viene letto e il PATH è più corto. Usa il percorso completo del programma.

Come eseguo più comandi in una sola chiamata SSH?

Mettili in un'unica stringa tra virgolette, come ssh server 'cd /tmp && ls', oppure invia uno script con ssh server 'bash -s' < script.sh.

Fonti

Altro in Sessioni e tmux

Tutte le guide