Pourquoi la commande `cd` ne fonctionne-t-elle pas via SSH?

41

J'essayais de sauvegarder certains fichiers via SSH, mais au lieu de tarceux que je voulais, j'ai mon dossier personnel. J'ai fait d'autres tests et cela se résume à ceci:

ssh root@server /bin/sh -c "cd /boot && ls -l"

Qui à ma grande surprise liste des fichiers dans /rootpas /boot. Mais si j’exécute l’ensemble de la /bin/shcommande à partir d’un terminal, il cds’imprime correctement et affiche les /bootfichiers.

Qu'est-ce qu'il se passe ici?

Ambroz Bizjak
la source
1
À moins qu'il ne s'agisse d'un exemple, pourquoi n'utilisez-vous pas ssh root@server /bin/sh -c "ls -l /boot"?
Demure
C'est drôle tu as demandé. J'ai essayé de le faire avant de commenter et je n'ai toujours pas obtenu la liste des répertoires.
unxnut
2
@Demure parce que je voulais vraiment tar, et tar / boot inclura le chemin de démarrage dans le résultat, ce que je ne veux pas
Ambroz Bizjak

Réponses:

35

ssh ne vous permet pas de spécifier une commande précisément, comme vous l'avez fait, sous la forme d'une série d'arguments à transmettre à execvp sur l'hôte distant. Au lieu de cela, il concatène tous les arguments dans une chaîne et les exécute via un shell distant. À mon avis, c’est là un défaut de conception majeur dans ssh. C’est un outil unix bien conçu dans la plupart des cas, mais quand vient le temps de spécifier une commande, il a choisi d’utiliser une seule chaîne monolithique au lieu d’un argv, comme il a été conçu pour MSDOS ou quelque chose comme ça!

Puisque ssh passera votre commande sous forme de chaîne unique sh -c, vous n'avez pas besoin de fournir la vôtre sh -c. Quand vous le faites, le résultat est

sh -c '/bin/sh -c cd /boot && ls -l'

avec la citation d'origine perdu. Donc, les commandes séparées par le &&sont:

`/bin/sh -c cd /boot`
`ls -l`

Le premier de ceux-ci exécute un shell avec le texte de commande "cd" et $0= "boot". La commande "cd" se termine avec succès, le $0est sans importance et /bin/sh -cindique le succès, puis le ls -lse produit.


la source
2
Bien que je sois d’accord, il est malheureux de sshse comporter de la sorte. Sinon, il faudrait s’appeler sexec, non ssh. sshest la version sécurisée de rsh(qui se comporte de la même manière à cet égard) et rlogin( rshappelée rloginlorsqu'elle n'est pas passée en ligne de commande). C'est juste dommage que cela ne soit pas mis rexecen œuvre aussi bien.
Stéphane Chazelas
@StephaneChazelas a t-il déjà eu une commande rexec? Pour moi, c'est juste une bibliothèque C. Bon point cependant, à propos de rsh. Le remplacement immédiat de rsh était la clé d'une adoption rapide dans les premiers jours de ssh, alors que tout le monde disposait déjà de tonnes de scripts utilisant rsh.
Je l'ai certainement utilisé dans le passé. En regardant maintenant, cela devait être sur HPUX comme pas beaucoup d'autres Unices semblent l'avoir, je dois admettre que même si j'étais sous l'impression qu'il était plus répandu.
Stéphane Chazelas
Merci. Cela a vraiment aidé, esp. depuis que je creuse un tunnel avec ssh. Je devais faire ssh -t machine1 'ssh -t machine2 "echo \" 1 && 2 \ ""' pour obtenir '1 && 2' en sortie. Cela a tué quelques heures.
Ghayes
Si vous voulez passer une commande avec précision, par exemple à partir de "$ @", vous pouvez utiliser ceci: "`printf "%q " "$@"`"avec bash printfpour citer une chaîne ou des chaînes avec des échappements de shell.
Sam Watkins
36

C'est une citation. Ssh exécute déjà la commande que vous passez dans un shell. Lorsque vous transmettez plusieurs paramètres, ils sont concaténés avec un espace entre eux pour créer une chaîne. La commande distante que vous exécutez à distance est donc /bin/sh -c cd /boot && ls -l(pas de guillemets, car ceux-ci ont été interprétés par le shell local).

/bin/sh -c cd /bootpistes /bin/shet lui dit de lancer la commande cdet aussi ensemble $0à /boot. Une fois que cela est fait, le shell parent (celui lancé par sshd) s'exécute ls -l.

Dans votre cas, supprimez simplement ce sh -cqui est complètement inutile à moins que votre shell distant (comme indiqué dans /etc/passwdou une autre base de données de mots de passe) ne comprenne pas cette commande.

ssh root@server "cd /boot && ls -l"

Si vous avez besoin d'appeler un autre shell, vous devez citer la commande distante pour le protéger contre l'expansion du shell distant invoqué par sshd. Par exemple, si votre shell de connexion est Dash et que vous souhaitez exécuter une commande bash:

ssh root@server 'bash -c "cd ~bob && ls -l"'
Gilles, arrête de faire le mal
la source
8

Je pense que cela a plus à voir avec la façon dont les options sont analysées par le shell. Par exemple, cela fonctionne:

$ ssh root@server /bin/sh -c '"cd /boot && ls -l"'

Cela a le même problème que votre commande:

$ ssh root@server /bin/sh -c 'cd /boot && ls -l'

Si vous activez le -vcommutateur, sshvous pouvez voir ce qui se passe:

1ère commande:

debug1: Commande d'envoi: / bin / sh -c "cd / boot && ls -l"

2ème commande:

debug1: Commande d'envoi: / bin / sh -c cd / boot && ls -l

En règle générale, lorsque vous envoyez des commandes, sshvous devez porter une attention particulière à la citation et insérer des guillemets entre guillemets à mesure que les différentes couches les suppriment. Aussi, ne vous embêtez pas d'envoyer /bin/sh.

Vous pouvez faire une chose très utile une fois que vous avez compris comment citer sshce qui suit. Cela exécutera la commande sur le serveur distant mais collectera les résultats dans un fichier situé localement sur le système sur lequel vous avez exécuté la sshcommande:

$ ssh root@server 'free -m' > /tmp/memory.status

ou ceci, où vous tarifiez un répertoire sur un serveur distant et le créez sur le système local:

$ ssh remotehost 'tar zcvf - SOURCEDIR' | cat > DESTFILE.tar.gz

Les références

slm
la source
4

En règle générale, vous n'avez pas à spécifier le shell à utiliser. Cela marche:

ssh user@host "cd /boot && pwd"

Mais si votre shell par défaut est problématique, assurez-vous simplement de citer la commande entière , y compris l'invocation du shell. Une des oeuvres suivantes

ssh user@host '/bin/sh -c "cd /boot && pwd"'
ssh user@host "/bin/sh -c \"cd /boot && pwd\""
tylerl
la source
Notez que s'il cd /boot && pwdfonctionne dans tous les shells des grandes familles (Bourne, csh, rc), /bin/sh -c "cd /boot && pwd"il ne fonctionnerait pas si le shell distant est de la rcfamille où il "n’est pas spécial.
Stéphane Chazelas