ssh
tem um recurso irritante quando você executa:
ssh user@host cmd and "here's" "one arg"
Em vez de executar isso cmd
com seus argumentos host
, concatena isso cmd
e argumentos com espaços e executa um shell host
para interpretar a sequência resultante (acho que é por isso que é chamada ssh
e não sexec
).
Pior, você não sabe qual shell será usado para interpretar essa string, pois esse é o shell de login user
que nem é garantido como Bourne, pois ainda há pessoas usando tcsh
o shell de login e fish
está em ascensão.
Existe uma maneira de contornar isso?
Suponhamos que tem um comando como uma lista de argumentos armazenados em uma bash
matriz, cada um dos quais pode conter qualquer sequência de bytes não nulos, existe alguma maneira para que seja executado sobre host
como user
de uma forma consistente independentemente da concha de login do que user
na host
(que assumiremos que é uma das principais famílias de shell do Unix: Bourne, csh, rc / es, fish)?
Outra suposição razoável que devo fazer é que exista um sh
comando host
disponível $PATH
que seja compatível com Bourne.
Exemplo:
cmd=(
'printf'
'<%s>\n'
'arg with $and spaces'
'' # empty
$'even\n* * *\nnewlines'
"and 'single quotes'"
'!!'
)
Posso executá-lo localmente com ksh
/ zsh
/ bash
/ yash
as:
$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>
ou
env "${cmd[@]}"
ou
xterm -hold -e "${cmd[@]}"
...
Como eu iria executá-lo em host
tão user
longo ssh
?
ssh user@host "${cmd[@]}"
obviamente não vai funcionar.
ssh user@host "$(printf ' %q' exec "${cmd[@]}")"
funcionaria apenas se o shell de login do usuário remoto fosse o mesmo que o shell local (ou entendesse a citação da mesma maneira que printf %q
no shell local a produz) e seja executado no mesmo local.
fonte
cmd
argumento fosse/bin/sh -c
que acabaríamos com um shell posix em 99% de todos os casos, não é? É claro que escapar de caracteres especiais é um pouco mais doloroso, mas isso resolveria o problema inicial?ssh host sh -c 'some cmd'
mesmossh host 'sh -c some cmd'
que o shell de logon do usuário remoto interpreta essash -c some cmd
linha de comando. Precisamos escrever o comando na sintaxe correta para que a Shell (e não sabemos o que é) para quesh
ser chamado por lá com-c
esome cmd
argumentos.sh -c 'some cmd'
esome cmd
passam a ser interpretadas da mesma forma em todos esses shells. Agora, e se eu quiser executar aecho \'
linha de comando Bourne no host remoto?echo command-string | ssh ... /bin/sh
é uma solução que eu dei na minha resposta, mas isso significa que você não pode alimentar dados para o stdin desse comando remoto.cmd
forcmd=(echo "foo bar")
, a linha de comando do shell transmitidassh
deve ser algo como a linha de comando `` 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 ' ' .Respostas:
Não acho que nenhuma implementação
ssh
tenha uma maneira nativa de passar um comando do cliente para o servidor sem envolver um shell.Agora, as coisas podem ficar mais fáceis se você puder dizer ao shell remoto para executar apenas um intérprete específico (como
sh
, para o qual conhecemos a sintaxe esperada) e fornecer o código para executar por outro meio.Essa outra média pode ser, por exemplo , entrada padrão ou uma variável de ambiente .
Quando nenhum deles pode ser usado, proponho uma terceira solução hacky abaixo.
Usando stdin
Se você não precisar alimentar nenhum dado com o comando remoto, essa é a solução mais fácil.
Se você sabe que o host remoto possui um
xargs
comando que suporta a-0
opção e o comando não é muito grande, você pode:Essa
xargs -0 env --
linha de comando é interpretada da mesma forma com todas essas famílias de shell.xargs
lê a lista de argumentos delimitados por nulo no stdin e os passa como argumentos paraenv
. Isso pressupõe que o primeiro argumento (o nome do comando) não contenha=
caracteres.Ou você pode usar
sh
no host remoto depois de citar cada elemento usando ash
sintaxe de citação.Usando variáveis de ambiente
Agora, se você precisar alimentar alguns dados do cliente para o stdin do comando remoto, a solução acima não funcionará.
No
ssh
entanto, algumas implantações de servidor permitem a passagem de variáveis de ambiente arbitrárias do cliente para o servidor. Por exemplo, muitas implantações openssh em sistemas baseados no Debian permitem passar variáveis cujo nome começa comLC_
.Nesses casos, você pode ter uma
LC_CODE
variável, por exemplo, contendo o código shquotedsh
, como descrito acima, e executarsh -c 'eval "$LC_CODE"'
no host remoto depois de solicitar ao seu cliente que passe essa variável (novamente, essa é uma linha de comando que é interpretada da mesma forma em todos os shell):Construindo uma linha de comando compatível com todas as famílias de shell
Se nenhuma das opções acima for aceitável (porque você precisa de stdin e sshd não aceita nenhuma variável ou porque precisa de uma solução genérica), precisará preparar uma linha de comando para o host remoto compatível com todos os conchas suportadas.
Isso é particularmente complicado porque todas essas conchas (Bourne, csh, rc, es, fish) têm sua própria sintaxe diferente e, em particular, diferentes mecanismos de citação e algumas delas têm limitações difíceis de contornar.
Aqui está uma solução que eu criei, eu a descrevo mais abaixo:
Esse é um
perl
script de wrapperssh
. Eu chamo issosexec
. Você chama assim:então no seu exemplo:
E o wrapper se transforma
cmd and its args
em uma linha de comando que todos os shells acabam interpretando como chamandocmd
com seus argumentos (independentemente do conteúdo).Limitações:
yash
for o shell de logon remoto, você não pode passar um comando cujos argumentos contenham caracteres inválidos, mas isso é uma limitação,yash
pois você não pode contornar isso de qualquer maneira.sh
, também assume que o sistema remoto possui oprintf
comando.Para entender como ele funciona, você precisa saber como a citação funciona nos diferentes shells:
'...'
são citações fortes, sem caráter especial."..."
são aspas fracas onde"
podem ser escapadas com barra invertida.csh
. O mesmo que Bourne, exceto que"
não pode ser escapado por dentro"..."
. Além disso, um caractere de nova linha deve ser inserido como prefixo com uma barra invertida. E!
causa problemas mesmo dentro de aspas simples.rc
. As únicas citações são'...'
(forte). Uma citação única entre aspas simples é inserida como''
(como'...''...'
). Aspas duplas ou barras invertidas não são especiais.es
. O mesmo que rc, exceto que aspas externas, a barra invertida pode escapar de uma única aspas.fish
: o mesmo que Bourne, exceto que a barra invertida escapa por'
dentro'...'
.Com todas essas restrições, é fácil ver que não é possível citar argumentos de linha de comando de maneira confiável, para que funcione com todos os shells.
Usando aspas simples como em:
funciona em todos, exceto:
não funcionaria
rc
.não funcionaria
csh
.não funcionaria
fish
.No entanto, deve ser capaz de resolver a maioria desses problemas se conseguirmos armazenar esses personagens problemáticos em variáveis, como barra invertida no
$b
, aspas simples em$q
, nova linha na$n
(e!
no$x
de expansão história CSH) de forma independente shell.funcionaria em todas as conchas. No entanto, isso ainda não funcionaria para a nova linha
csh
. Se$n
contiver nova linha, emcsh
, você deverá escrevê-$n:q
la para expandir para uma nova linha e isso não funcionará para outros shells. Então, o que acabamos fazendo aqui é chamarsh
esh
expandi-los$n
. Isso também significa ter que fazer dois níveis de citação, um para o shell de login remoto e outro parash
.O
$preamble
código é a parte mais complicada. Ele faz uso das várias regras citando diferentes em todas as conchas de ter algumas seções do código interpretado por apenas uma das conchas (enquanto ele está comentado para os outros) cada um dos quais apenas definindo os$b
,$q
,$n
,$x
variáveis para seus respectivos shell.Aqui está o código do shell que seria interpretado pelo shell de login do usuário remoto no
host
seu exemplo:Esse código acaba executando o mesmo comando quando interpretado por qualquer um dos shells suportados.
fonte
tl; dr
Para uma solução mais elaborada, leia os comentários e inspecione a outra resposta .
descrição
Bem, minha solução não funcionará com não-
bash
conchas. Mas, assumindo que estábash
do outro lado, as coisas ficam mais simples. Minha idéia é reutilizarprintf "%q"
para escapar. Também geralmente é mais legível ter um script do outro lado, que aceite argumentos. Mas se o comando for curto, provavelmente não há problema em incorporá-lo. Aqui estão alguns exemplos de funções para usar em scripts:local.sh
:remote.sh
:A saída:
Como alternativa, você mesmo pode fazer
printf
o trabalho, se souber o que está fazendo:fonte
bash
esteja disponível na máquina remota. Também existem alguns problemas com aspas ausentes, o que causaria problemas com espaços em branco e curingas.bash
conchas. Mas espero que as pessoas achem isso útil. Eu tentei resolver os outros problemas. Sinta-se à vontade para me dizer se falta algo além dabash
coisa.ssh_run user@host "${cmd[@]}"
). Você ainda tem algumas aspas ausentes.printf %q
não é segura para uso em um código de idioma diferente (e também é bastante problemático; por exemplo, em códigos de idioma usando o conjunto de caracteres BIG5, ele (4.3.48) citaε
comoα`
!). Para isso, o melhor é citar tudo e com aspas simples, comoshquote()
na minha resposta.