Guillemets : quel shell lit quoi
Deux shells lisent votre commande : le vôtre d'abord, puis celui du serveur. Les guillemets décident lequel développe quoi.
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 joint ses arguments avec des espaces simples et envoie une seule chaîne : les guillemets que seul votre shell local voit sont donc perdus :
$ ssh server echo "a b"
a b
$ ssh server 'echo "a b"'
a b
Il en va de même pour les tubes. La première commande ci-dessous exécute wc sur le serveur, la seconde l'exécute sur votre machine :
ssh server 'ls /tmp | wc -l'
ssh server ls /tmp | wc -l
Pour transmettre une valeur locale qui contient des espaces ou des guillemets, laissez bash l'échapper :
F="a b'c"
ssh server "printf '%s\n' $(printf %q "$F")"
Plusieurs lignes : un heredoc
Mettez le marqueur du heredoc entre guillemets et rien n'est développé localement :
ssh server 'bash -s' <<'REMOTE'
echo "user: $(whoami)"
cd /tmp && pwd
REMOTE
Sans guillemets autour du marqueur, votre shell local développe d'abord les variables. Échappez celles destinées au serveur :
V=abc
ssh server 'bash -s' <<REMOTE
echo "local var: $V, remote: \$HOSTNAME"
REMOTE
Pour exécuter un script local sur le serveur avec des arguments :
ssh server 'bash -s -- one two' < script.sh
Codes de sortie
| Commande | Code de sortie |
|---|---|
ssh server 'exit 3' | 3 |
ssh server 'false; true' | 0, car seule la dernière commande compte |
ssh server 'set -e; false; echo no' | 1, et l'exécution s'arrête à false |
ssh server nosuchcmd | 127, commande introuvable sur le serveur |
ssh -p 1 server true | 255, SSH n'a pas pu se connecter |
Une commande distante peut aussi se terminer d'elle-même avec 255 : ce code ne fait donc que suggérer un échec de SSH. La sortie d'erreur reste séparée de la sortie standard, donc 2>/dev/null et > file fonctionnent comme d'habitude de votre côté.
Pourquoi ssh dans une boucle s'arrête après un hôte
ssh lit l'entrée standard et l'envoie au serveur : il avale donc le reste de la liste. Cette boucle ne s'est exécutée qu'une fois dans notre essai, avec trois lignes dans le fichier :
while read -r h; do ssh "$h" uptime; done < hosts
-n indique à ssh de ne pas lire l'entrée standard. Celle-ci s'est exécutée trois fois :
while read -r h; do ssh -n "$h" uptime; done < hosts
Avant une boucle sur des serveurs de production, lisez comment éviter les erreurs sur les serveurs de production.
Envoyer des données par l'entrée standard
Sans -n, l'entrée standard est utile. Les commandes distantes peuvent la lire :
printf 'hello\n' | ssh server 'tr a-z A-Z'
tar -cf - folder | ssh server 'tar -C /tmp -xf -'
Commandes qui ont besoin d'un terminal
Avec une commande, ssh n'alloue pas de terminal : tty affiche donc not a tty et sudo ne peut pas demander de mot de passe :
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
Ajoutez -t pour les programmes interactifs :
ssh -t server sudo systemctl restart nginx
Si votre propre entrée standard n'est pas un terminal, par exemple dans un script, -t affiche Pseudo-terminal will not be allocated because stdin is not a terminal. Utilisez -tt pour le forcer, et consultez le guide de cette erreur.
Tâches longues et commandes en arrière-plan
Une tâche en arrière-plan qui garde la sortie ouverte fait attendre ssh. La première commande ci-dessous a pris 2 secondes dans notre essai, et la seconde est revenue aussitôt :
ssh server 'sleep 2 &'
ssh server 'nohup sleep 2 >/dev/null 2>&1 &'
Pour un vrai travail qui doit survivre à la connexion, utilisez tmux sur le serveur. ssh -f server 'command' ne fait que passer votre ssh local en arrière-plan après la connexion.
L'environnement diffère de votre session de connexion
Une commande via SSH ne s'exécute pas dans un shell de connexion : des fichiers comme ~/.profile ne sont donc pas lus. Dans notre essai, shopt login_shell affichait off et PATH valait /usr/local/bin:/usr/bin:/bin:/usr/games. Si une commande fonctionne à la main mais indique command not found via SSH, utilisez son chemin complet.
Questions
Comment lancer sudo par SSH sans l'erreur de terminal ?
Ajoutez -t, comme dans ssh -t server sudo systemctl restart nginx, pour que sudo ait un terminal où demander le mot de passe. Dans les scripts, utilisez -tt ou configurez sudo pour cette commande.
Pourquoi une commande marche à la main mais pas par SSH ?
Une commande par SSH tourne dans un shell qui n'est pas de connexion, donc ~/.profile n'est pas lu et le PATH est plus court. Utilisez le chemin complet du programme.
Comment lancer plusieurs commandes en un seul appel SSH ?
Mettez-les dans une seule chaîne entre guillemets, comme ssh server 'cd /tmp && ls', ou envoyez un script avec ssh server 'bash -s' < script.sh.