Por que o comando `cd` não funciona via SSH?

41

Eu estava tentando fazer backup de alguns arquivos via SSH, mas em vez dos que tareu queria, consegui minha pasta pessoal. Fiz mais alguns testes e tudo se resume a isso:

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

Qual a minha surpresa lista arquivos /rootnão /boot. Mas se eu executar o /bin/shcomando inteiro a partir de um terminal, ele corretamente cdimprimirá os /bootarquivos.

O que está acontecendo aqui?

Ambroz Bizjak
fonte
1
A menos que seja um exemplo, por que você não está usando ssh root@server /bin/sh -c "ls -l /boot"?
recatada
É engraçado você ter perguntado. Tentei fazer isso antes de comentar e ainda não obtive a listagem do diretório.
unxnut
2
@demure porque eu realmente queria tar, e alcatrão / boot irá incluir o caminho de inicialização no resultado, que eu não quero
Ambroz Bizjak

Respostas:

35

O ssh não permite que você especifique um comando com precisão, como você fez, como uma série de argumentos a serem passados ​​para o execvp no host remoto. Em vez disso, concatena todos os argumentos em uma string e os executa por meio de um shell remoto. Isso se destaca como uma das principais falhas de design do ssh, na minha opinião ... na maioria das vezes, é uma ferramenta unix bem comportada, mas quando chega a hora de especificar um comando, optamos por usar uma única string monolítica em vez de um argv, como foi projetado para MSDOS ou algo assim!

Como o ssh passará seu comando como uma única sequência sh -c, você não precisará fornecer o seu próprio sh -c. Quando você faz, o resultado é

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

com a citação original perdida. Portanto, os comandos separados por &&são:

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

O primeiro deles executa um shell com o texto de comando "cd" e $0= "boot". O comando "cd" é concluído com êxito, $0é irrelevante e /bin/sh -cindica sucesso, e então ls -lacontece.


fonte
2
Embora eu concorde, é lamentável que sshse comporte assim, se não fosse, teria que ser chamado sexec, não ssh. sshé a versão segura de rsh(que se comportou da mesma maneira nesse sentido) e rlogin( rshchamada rloginquando não passou por uma linha de comando). É lamentável que isso também não seja implementado rexec.
Stéphane Chazelas
@StephaneChazelas já houve um comando rexec? É apenas uma função da biblioteca C, tanto quanto eu sei. Bom ponto, porém, sobre rsh. Ser um substituto substituto do rsh foi a chave para a rápida adoção nos primeiros dias do ssh, quando todo mundo tinha toneladas de scripts já usando o rsh.
Eu definitivamente usei isso no passado. Olhando agora, ele deve ter sido no HPUX, como muitas outras unidades parecem não ter, tenho que admitir que estava com a impressão de que era mais difundido.
Stéphane Chazelas
Obrigado. Isso realmente ajudou, esp. desde que eu estou encapsulando com ssh. Eu tive que fazer ssh -t machine1 'ssh -t machine2 "echo \" 1 && 2 \ ""' para obter '1 && 2' como saída. Isso matou algumas horas.
Ghayes
Se você deseja passar um comando com precisão, por exemplo, a partir de "$ @", você pode usar o seguinte: "`printf "%q " "$@"`"with bash's printfpara citar uma string ou strings com escape de shell.
Sam Watkins
36

É uma questão de citação. O Ssh já executa o comando que você passa em um shell. Quando você passa vários parâmetros, eles são concatenados com um espaço intermediário para criar uma sequência. Portanto, o comando remoto que você está executando remotamente é /bin/sh -c cd /boot && ls -l(sem aspas, porque as aspas no seu comando foram interpretadas pelo shell local).

/bin/sh -c cd /bootruns /bin/she diz-lhe para executar o comando cde também para conjunto $0para /boot. Feito isso, o shell pai (o iniciado por sshd) é executado ls -l.

No seu caso, basta remover o sh -cque é completamente inútil, a menos que seu shell remoto (conforme indicado em /etc/passwdou em outro banco de dados de senhas) não entenda esse comando.

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

Se você precisar chamar um shell diferente, deverá citar o comando remoto para protegê-lo da expansão pelo shell remoto chamado por sshd. Por exemplo, se o seu shell de login é traço e você deseja executar um comando bash:

ssh root@server 'bash -c "cd ~bob && ls -l"'
Gilles 'SO- parar de ser mau'
fonte
8

Eu acho que tem mais a ver com a forma como as opções estão sendo analisadas pelo shell. Por exemplo, isso funciona:

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

Isso tem o mesmo problema que o seu comando:

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

Se você ativar o -vswitch, sshpoderá ver o que está acontecendo:

1º comando:

debug1: Enviando comando: / bin / sh -c "cd / boot && ls -l"

2º comando:

debug1: Comando de envio: / bin / sh -c cd / boot && ls -l

Normalmente, ao enviar comandos, sshvocê deve prestar atenção especial às aspas e agrupar aspas entre aspas, pois as várias camadas as separam. Também não se incomode em enviar /bin/sh.

Você pode fazer algo muito útil depois de entender a citação ssh, como a seguir. Isso executará o comando no servidor remoto, mas coletará os resultados em um arquivo localmente no sistema em que você executou o sshcomando:

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

ou isto, em que você tar um diretório em um servidor remoto e o cria no sistema local:

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

Referências

slm
fonte
4

Normalmente, você não precisa especificar qual shell usar. Isso funciona:

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

Mas se o seu shell padrão for problemático, certifique-se de citar todo o comando, incluindo a chamada do shell. Um dos seguintes trabalhos

ssh user@host '/bin/sh -c "cd /boot && pwd"'
ssh user@host "/bin/sh -c \"cd /boot && pwd\""
tylerl
fonte
Observe que, embora cd /boot && pwdtrabalhe em todos os invólucros das principais famílias (Bourne, csh, rc), /bin/sh -c "cd /boot && pwd"não funcionaria se o shell remoto for da rcfamília onde "não é especial.
Stéphane Chazelas