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
| Comando | Codice 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 nosuchcmd | 127, comando non trovato sul server |
ssh -p 1 server true | 255, 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.