ssh
a une fonctionnalité ennuyeuse en ce que lorsque vous exécutez:
ssh user@host cmd and "here's" "one arg"
Au lieu d'exécuter cela cmd
avec ses arguments host
, il concatène cela cmd
et les arguments avec des espaces et exécute un shell host
pour interpréter la chaîne résultante (je suppose que c'est pourquoi elle est appelée ssh
et non sexec
).
Pire, vous ne savez pas quel shell va être utilisé pour interpréter cette chaîne car c'est le shell de connexion user
qui n'est même pas garanti d'être Bourne comme il y a toujours des gens qui utilisent tcsh
comme shell de connexion et qui fish
sont en augmentation.
Y a-t-il un moyen de contourner cela?
Supposons que j'ai une commande sous la forme d'une liste d'arguments stockés dans un bash
tableau, chacun pouvant contenir n'importe quelle séquence d'octets non nuls, existe-t-il un moyen de l'exécuter de host
manière user
cohérente, quel que soit le shell de connexion de celui-ci user
sur host
(que nous supposerons être l'une des principales familles de shell Unix: Bourne, csh, rc / es, fish)?
Une autre hypothèse raisonnable que je devrais être en mesure de faire est qu'il y ait une sh
commande host
disponible $PATH
qui est compatible avec Bourne.
Exemple:
cmd=(
'printf'
'<%s>\n'
'arg with $and spaces'
'' # empty
$'even\n* * *\nnewlines'
"and 'single quotes'"
'!!'
)
Je peux l'exécuter localement avec ksh
/ zsh
/ bash
/ yash
comme:
$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>
ou
env "${cmd[@]}"
ou
xterm -hold -e "${cmd[@]}"
...
Comment pourrais-je l'exécuter host
comme user
plus ssh
?
ssh user@host "${cmd[@]}"
ne fonctionnera évidemment pas.
ssh user@host "$(printf ' %q' exec "${cmd[@]}")"
ne fonctionnerait que si le shell de connexion de l'utilisateur distant était le même que le shell local (ou comprend la citation de la même manière que printf %q
dans le shell local le produit) et s'exécute dans les mêmes paramètres régionaux.
la source
cmd
argument était que/bin/sh -c
nous nous retrouverions avec un shell posix dans 99% des cas, n'est-ce pas? Bien sûr, échapper des caractères spéciaux est un peu plus douloureux de cette façon, mais cela résoudrait-il le problème initial?ssh host sh -c 'some cmd'
, de la même manièressh host 'sh -c some cmd'
que le shell de connexion de l'utilisateur distant interprète cettesh -c some cmd
ligne de commande. Nous devons écrire la commande dans la syntaxe correcte pour ce shell (et nous ne savons pas de quoi il s'agit) afin qu'ellesh
soit appelée là-bas avec-c
etsome cmd
arguments.sh -c 'some cmd'
etsome cmd
sont interprétées de la même manière dans tous ces shells. Maintenant, que faire si je veux exécuter laecho \'
ligne de commande Bourne sur l'hôte distant?echo command-string | ssh ... /bin/sh
est une solution que j'ai donnée dans ma réponse, mais cela signifie que vous ne pouvez pas fournir de données au stdin de cette commande à distance.cmd
c'est le cascmd=(echo "foo bar")
, la ligne de commande du shell passée àssh
devrait être quelque chose comme `` echo '' foo bar '. The *first* space (the one before
echo) is superflous, but doen't harm. The other one (the ones before
' foo bar ') is needed. With
'% q ', we'd pass a
' echo''foo bar'` ligne de commande.Réponses:
Je ne pense pas qu'une implémentation de
ssh
ait une manière native de passer une commande du client au serveur sans impliquer un shell.Maintenant, les choses peuvent devenir plus faciles si vous pouvez dire au shell distant d'exécuter uniquement un interpréteur spécifique (comme
sh
, pour lequel nous connaissons la syntaxe attendue) et de donner le code à exécuter par un autre moyen.Cet autre moyen peut être par exemple une entrée standard ou une variable d'environnement .
Lorsque ni l'un ni l'autre ne peut être utilisé, je propose une troisième solution hacky ci-dessous.
Utilisation de stdin
Si vous n'avez pas besoin de fournir de données à la commande à distance, c'est la solution la plus simple.
Si vous savez que l'hôte distant possède une
xargs
commande qui prend en charge l'-0
option et que la commande n'est pas trop grande, vous pouvez faire:Cette
xargs -0 env --
ligne de commande est interprétée de la même manière avec toutes ces familles de shell.xargs
lit la liste d'arguments séparés par des valeurs nulles sur stdin et les transmet comme arguments àenv
. Cela suppose que le premier argument (le nom de la commande) ne contient pas de=
caractères.Ou vous pouvez utiliser
sh
sur l'hôte distant après avoir cité chaque élément en utilisant lash
syntaxe de citation.Utilisation de variables d'environnement
Maintenant, si vous avez besoin de fournir des données du client au stdin de la commande à distance, la solution ci-dessus ne fonctionnera pas.
Certains
ssh
déploiements de serveur permettent cependant de transmettre des variables d'environnement arbitraires du client au serveur. Par exemple, de nombreux déploiements openssh sur des systèmes basés sur Debian permettent de passer des variables dont le nom commence parLC_
.Dans ces cas, vous pourriez avoir une
LC_CODE
variable contenant par exemple le code shquotedsh
comme ci-dessus et s'exécutersh -c 'eval "$LC_CODE"'
sur l'hôte distant après avoir dit à votre client de passer cette variable (encore une fois, c'est une ligne de commande qui est interprétée de la même manière dans chaque shell):Construire une ligne de commande compatible avec toutes les familles de shell
Si aucune des options ci-dessus n'est acceptable (parce que vous avez besoin de stdin et que sshd n'accepte aucune variable, ou parce que vous avez besoin d'une solution générique), alors vous devrez préparer une ligne de commande pour l'hôte distant qui soit compatible avec tous coquilles supportées.
C'est particulièrement délicat car tous ces shells (Bourne, csh, rc, es, fish) ont leur propre syntaxe différente, et en particulier différents mécanismes de citation et certains d'entre eux ont des limitations difficiles à contourner.
Voici une solution que j'ai trouvée, je la décris plus bas:
C'est un
perl
script wrapper autourssh
. Je l'appellesexec
. Vous l'appelez comme:donc dans votre exemple:
Et l'encapsuleur se transforme
cmd and its args
en ligne de commande que tous les shells finissent par interpréter comme appelantcmd
avec ses arguments (indépendamment de leur contenu).Limites:
yash
est le shell de connexion à distance, vous ne pouvez pas passer une commande dont les arguments contiennent des caractères non valides, mais c'est une limitation dans la mesureyash
où vous ne pouvez pas contourner de toute façon.sh
, il suppose également que le système distant a laprintf
commande.Pour comprendre comment cela fonctionne, vous devez savoir comment fonctionne la citation dans les différents shells:
'...'
sont des citations fortes sans caractère spécial."..."
sont des guillemets faibles où il"
est possible d'échapper avec une barre oblique inverse.csh
. Identique à Bourne, sauf qu'il"
ne peut pas s'échapper à l'intérieur"..."
. De plus, un caractère de nouvelle ligne doit être saisi, préfixé par une barre oblique inverse. Et!
cause des problèmes même à l'intérieur des guillemets simples.rc
. Les seules citations sont'...'
(fortes). Un guillemet simple entre guillemets simples est entré comme''
(comme'...''...'
). Les guillemets doubles ou les contre-obliques ne sont pas spéciaux.es
. Identique à rc, sauf que les guillemets extérieurs, la barre oblique inverse peut échapper à un guillemet simple.fish
: identique à Bourne sauf que la barre oblique inverse s'échappe à l''
intérieur'...'
.Avec toutes ces contraintes, il est facile de voir que l'on ne peut pas citer de manière fiable des arguments de ligne de commande pour qu'il fonctionne avec tous les shells.
Utiliser des guillemets simples comme dans:
fonctionne dans tous, mais:
ne fonctionnerait pas
rc
.ne fonctionnerait pas
csh
.ne fonctionnerait pas
fish
.Cependant, nous devrions être en mesure de contourner la plupart de ces problèmes si nous parvenons à stocker ces caractères problématiques dans des variables, comme la barre oblique inverse
$b
, les guillemets simples$q
, la nouvelle ligne dans$n
(et!
dans$x
pour l'expansion de l'historique csh) de manière indépendante du shell.fonctionnerait dans tous les obus. Cela ne fonctionnerait toujours pas pour newline
csh
. Si$n
contient un retour à la ligne, danscsh
, vous devez l'écrire$n:q
pour qu'il s'étende à un retour à la ligne et cela ne fonctionnera pas pour les autres shells. Donc, ce que nous finissons par faire ici, c'est d'appelersh
et de lessh
étendre$n
. Cela signifie également avoir à faire deux niveaux de devis, un pour le shell de connexion à distance et un poursh
.Le
$preamble
dans ce code est la partie la plus délicate. Il utilise les différentes différentes règles de cotation dans toutes les coquilles d'avoir certaines sections du code interprété par une seule des obus (alors qu'il est commentée pour les autres) , dont chacun vient définir les$b
,$q
,$n
,$x
variables pour leur coquille respective.Voici le code shell qui serait interprété par le shell de connexion de l'utilisateur distant sur
host
pour votre exemple:Ce code finit par exécuter la même commande lorsqu'il est interprété par l'un des shells pris en charge.
la source
tl; dr
Pour une solution plus élaborée, lisez les commentaires et inspectez l'autre réponse .
la description
Eh bien, ma solution ne fonctionnera pas avec des non-
bash
shells. Mais en supposant que c'estbash
à l'autre bout, les choses deviennent plus simples. Mon idée est de réutiliserprintf "%q"
pour échapper. De manière générale, il est plus lisible d'avoir un script à l'autre extrémité, qui accepte des arguments. Mais si la commande est courte, il est probablement correct de l'intégrer. Voici quelques exemples de fonctions à utiliser dans les scripts:local.sh
:remote.sh
:Le résultat:
Alternativement, vous pouvez faire
printf
le travail vous-même, si vous savez ce que vous faites:la source
bash
soit disponible sur la machine distante. Il y a aussi quelques problèmes avec les guillemets manquants qui pourraient causer des problèmes avec les espaces et les caractères génériques.bash
obus. Mais j'espère que les gens le trouveront utile. J'ai cependant essayé de résoudre les autres problèmes. N'hésitez pas à me dire s'il me manque autrebash
chose que la chose.ssh_run user@host "${cmd[@]}"
). Vous avez encore des citations manquantes.printf %q
n'est pas sûre à utiliser dans un environnement local différent (et est également assez boguée; par exemple dans les environnements locaux utilisant le jeu de caractères BIG5, il (4.3.48) citeε
commeα`
!). Pour cela, le mieux est de tout citer et avec des guillemets simples comme avec leshquote()
dans ma réponse.