Qual é a maneira mais limpa de ssh e executar vários comandos no Bash?

349

Eu já tenho um agente ssh configurado e posso executar comandos em um servidor externo no script Bash, fazendo coisas como:

ssh blah_server "ls; pwd;"

Agora, o que eu realmente gostaria de fazer é executar muitos comandos longos em um servidor externo. Incluir tudo isso entre aspas seria bastante feio, e eu realmente prefiro evitar o ssh'ing várias vezes apenas para evitar isso.

Então, existe uma maneira de fazer isso de uma só vez entre parênteses ou algo assim? Estou procurando algo ao longo das linhas de:

ssh blah_server (
   ls some_folder;
   ./someaction.sh;
   pwd;
)

Basicamente, ficarei feliz com qualquer solução, desde que esteja limpa.

Editar

Para esclarecer, estou falando sobre fazer parte de um script bash maior. Outras pessoas podem precisar lidar com o script no final da linha, então eu gostaria de mantê-lo limpo. Eu não quero ter um script bash com uma linha que se parece com:

ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"

porque é extremamente feio e difícil de ler.

Eli
fonte
2
Hmm, que tal colocar tudo isso em um script no servidor e apenas chamá-lo com uma sshchamada?
Nikolai Fetissov
@Nikolai se os comandos depende do lado do cliente, que pode ser escrito em um shell script, então scp, sshe prazo. Esta será a maneira mais limpa, eu acho.
Khachik
6
Isso faz parte de um script maior do bash, então eu prefiro não dividi-lo com metade vivendo no meu computador pessoal e a outra metade vivendo no servidor e executando o ssh. Se possível, eu realmente gostaria de mantê-lo como um script executado no meu computador pessoal. Não existe realmente uma maneira limpa de incluir um monte de comandos em um ssh?
Eli
A melhor maneira é não usar bash, mas Perl, Python, Ruby, etc.
salva
11
Por que você deseja evitar colocar os comandos remotos entre aspas? Você pode ter novas linhas dentro das aspas, quantas desejar; e usar uma string em vez da entrada padrão significa que a entrada padrão está disponível para, por exemplo, a leitura da entrada no script remoto. (Embora em Unix, aspas simples são geralmente preferível à aspas duplas, a menos que você precisa especificamente o shell local para avaliar algumas partes da cadeia.)
tripleee

Respostas:

456

Que tal um documento do Bash Here :

ssh otherhost << EOF
  ls some_folder; 
  ./someaction.sh 'some params'
  pwd
  ./some_other_action 'other params'
EOF

Para evitar os problemas mencionados por @Globalz nos comentários, você poderá (dependendo do que está fazendo no site remoto) evitar a substituição da primeira linha por

ssh otherhost /bin/bash << EOF

Observe que você pode fazer a substituição de variáveis ​​no documento Aqui, mas pode ser necessário lidar com problemas de cotação. Por exemplo, se você citar a "string de limite" (ou seja, EOFacima), não poderá fazer substituições de variáveis. Mas sem citar a cadeia de limite, as variáveis ​​são substituídas. Por exemplo, se você definiu $NAMEacima em seu script de shell, poderá fazer

ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF

e criaria um arquivo no destino otherhostcom o nome do que você atribuiu $NAME. Outras regras sobre a citação de scripts de shell também se aplicam, mas são muito complicadas para entrar aqui.

Paul Tomblin
fonte
6
Parece exatamente o que eu quero! Como é que isso funciona? Você teria um link para uma página que explica isso?
Eli
9
+1 Estava apenas pensando nisso - aqui está um lugar para ler sobre isso: tldp.org/LDP/abs/html/here-docs.html
bosmacs
14
Também recebo esta saída no meu local: o pseudo-terminal não será alocado porque stdin não é um terminal.
Globalz 5/11/12
14
Pode ser importante que você cite a palavra (ou seja, primeiro 'EOF') para impedir a expansão das linhas de comando. Veja: man bash | less + / 'Here Documents'
assem
6
Paul, eu apenas o sugiro porque, com quase 200 mil visualizações sobre essa questão, parece que muitas pessoas estão vindo para cá quando scripts com ssh. É comum precisar injetar valores ao criar scripts. Se não fosse o barulho nas outras perguntas e comentários, eu apenas faria uma e estaria a caminho, mas é improvável que seja visto nesta fase. Uma nota de rodapé de uma linha pode poupar às pessoas algumas dores de cabeça importantes.
koyae
122

Edite seu script localmente e coloque-o no ssh, por exemplo

cat commands-to-execute-remotely.sh | ssh blah_server

onde se commands-to-execute-remotely.shparece com sua lista acima:

ls some_folder
./someaction.sh
pwd;
bosmacs
fonte
7
Isso tem a grande vantagem de saber exatamente o que está sendo executado pelo script remoto - sem problemas com a citação. Se você precisar de comandos dinâmicos, poderá usar um script de shell com um subshell, ainda sendo canalizado para o ssh, ou seja, ( echo $mycmd $myvar ; ...) | ssh myhostcomo no uso do gato, você sabe exatamente o que está entrando no fluxo de comandos do ssh. E, claro, o subshell no script podem ser multi-line para facilitar a leitura - veja linuxjournal.com/content/bash-sub-shells
RichVel
6
Você pode fazer isso com argumentos no commands-to-execute-remotely.sh?
elaRosca
11
Eu acho que isso é muito mais útil do que a solução de paultomblin. Como o script pode ser redirecionado para qualquer servidor ssh sem precisar abrir o arquivo. Também é muito mais legível.
Patrick Bassut 26/03
3
sim, mas usando echo ou um documento aqui (consulte a resposta superior): use: $localvarpara interpretar uma variável definida localmente, \$remotevarinterpretar remotamente uma variável definida remotamente, \$(something with optionnal args)para obter a saída de algo executado no servidor remoto. Um exemplo de como você pode ssh através de 1 (ou, como mostrado aqui, vários comandos ssh):echo " for remotedir in /*/${localprefix}* ; do cd \"\$remotedir\" && echo \"I am now in \$(pwd) on the remote server \$(hostname) \" ; done " | ssh user1@hop1 ssh user2@hop2 ssh user@finalserver bash
Olivier Dulac
2
Hmm ... como isso é diferente de usar o redirecionamento de entrada, ou seja ssh blah_server < commands-to-execute-remotely.sh?
flow2k
41

Para corresponder ao seu código de amostra, você pode agrupar seus comandos dentro de qoutes únicos ou duplos. Por exemplo

ssh blah_server "
  ls
  pwd
"
Andrei B
fonte
Eu gosto desse formato, no entanto, infelizmente, não é útil para armazenar dados padrão em uma variável.
Signus
11
Signus, o que você quer dizer com "armazenamento de dados padrão em uma variável"?
Andrei B
11
@Signus É perfeitamente possível fazer o que você descreve, embora você provavelmente deseje usar aspas simples em vez de aspas duplas nos comandos remotos (ou escapar dos operadores que precisam ser escapados dentro de aspas duplas para impedir que seu shell local intercepte e interpolando-os).
Tripleee
Não consigo colocar no código de aspas duplas um comando como este fileToRemove = $ (find. -Type f -name 'xxx.war'). fileToRemove deve ter um nome de arquivo dentro, mas, em vez disso, possui uma string vazia. Algo precisa ser escapado?
Jkonst
37

Eu vejo duas maneiras:

Primeiro você faz um soquete de controle como este:

 ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>

e execute seus comandos

 ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>

Dessa forma, você pode escrever um comando ssh sem realmente se reconectar ao servidor.

O segundo seria gerar dinamicamente o script, scpinserindo-o e executando-o.

terminus
fonte
20

Isso também pode ser feito da seguinte maneira. Coloque seus comandos em um script, vamos chamá-lo de command-inc.sh

#!/bin/bash
ls some_folder
./someaction.sh
pwd

Salve o arquivo

Agora execute-o no servidor remoto.

ssh user@remote 'bash -s' < /path/to/commands-inc.sh

Nunca falhou por mim.

RJ
fonte
Semelhante ao que eu estava pensando originalmente! Mas por que é bash -snecessário?
flow2k
Além disso, é #!/bin/bashrealmente usado?
flow2k
2
O -s existe para compatibilidade. From man bash -s Se a opção -s estiver presente, ou se nenhum argumento permanecer após o processamento da opção, os comandos serão lidos a partir da entrada padrão. Esta opção permite que os parâmetros posicionais sejam definidos ao chamar um shell interativo.
RJ
11
RJ, obrigado! Com o meu primeiro comentário, parece que acabamos de fazer ssh user@remote < /path/to/commands-inc.sh(parece funcionar para mim). Bem, acho que sua versão garante que usamos o bashshell, e não outro shell - esse é o objetivo, não é?
flow2k
2
Obrigado. Sim, alguns de nós têm nossas anotações e hábitos de versões mais antigas do bash e de outras conchas. :-)
RJ
10

Coloque todos os comandos em um script e ele pode ser executado como

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh
Jai Prakash
fonte
Um pouco estranho, mas funciona bem. E os argumentos podem ser passados ​​para o script (antes ou depois do redirecionamento). por exemplo, 'ssh <remote-user> @ <remote-host> "bash -s" arg1 <./ remote-commands.sh arg2' por exemplo 'ssh <remote-user> @ <remote-host> "bash -s" - Você pode usar o seguinte comando: --arg1 <./ remote-commands.sh arg2 '
gaoithe
Eu tive uma pergunta semelhante com outra resposta acima, mas qual é o objetivo de incluir bash -s?
flow2k
11
@ flow2k -s é para indicar o bash para ler os comandos da entrada padrão. Aqui está a descrição das manpáginasIf the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell.
Jai Prakash
@JaiPrakash Obrigado por isso, mas eu estava realmente pensando se podemos simplesmente acabar com o bashcomando completamente, ou seja ssh remote-user@remote-host <./remote-commands.sh. Eu tentei do meu jeito e parecia funcionar, embora não tenha certeza se é de alguma forma deficiente.
flow2k
@ flow2k pelo meu entendimento das páginas de manual, não haverá nenhuma deficiência sem essa opção. É necessário se você estiver tentando transmitir argumentos. - graças
Jai Prakash
9

SSH e executar vários comandos no Bash.

Separe os comandos com ponto-e-vírgula dentro de uma sequência, transmitidos para eco, todos direcionados para o comando ssh. Por exemplo:

echo "df -k;uname -a" | ssh 192.168.79.134

Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda2       18274628 2546476  14799848  15% /
tmpfs             183620      72    183548   1% /dev/shm
/dev/sda1         297485   39074    243051  14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux
arnab
fonte
arnab, no futuro, descreva brevemente o que seu código faz. Depois fale sobre como ele funciona e insira o código. Em seguida, coloque o código em um bloco de código para facilitar a leitura.
Eric Leschinski
A partir da pergunta, você ofereceu exatamente o que o OP quer evitar: "Não quero ter um script bash com uma linha que se pareça com ..."
jww 25/10/16
6

Para quem tropeça aqui como eu - tive sucesso ao escapar do ponto e vírgula e da nova linha:

Primeiro passo: o ponto e vírgula. Dessa forma, não quebramos o comando ssh:

ssh <host> echo test\;ls
                    ^ backslash!

Listou o diretório remoto hosts / home (conectado como root), enquanto

ssh <host> echo test;ls
                    ^ NO backslash

listou o diretório de trabalho atual.

Próxima etapa: dividindo a linha:

                      v another backslash!
ssh <host> echo test\;\
ls

Isso novamente listou o diretório ativo remoto - formatação aprimorada:

ssh <host>\
  echo test\;\
  ls

Se realmente melhor do que aqui documento ou aspas em torno de linhas quebradas - bem, não eu para decidir ...

(Usando o bash, Ubuntu 14.04 LTS.)

Aconcágua
fonte
11
O que é ainda melhor, você pode usar && e || Desta forma, também - teste de echo \ & \ & ls
Miro Kropacek
@MiroKropacek Isso também é legal. Melhor? Depende do que você quer; executar segundo comando condicionalmente , então sim, executá-lo incondicionalmente (não importa se primeiro sucesso ou fracasso), então não ...
Aconcagua
@MiroKropacek, não precisei escapar do && ao colocar aspas duplas nos comandos:ssh host "echo test && ls"
not2savvy
5

As respostas postadas usando seqüências de linhas múltiplas e vários scripts bash não funcionaram para mim.

  • Cordas multilinhas longas são difíceis de manter.
  • Os scripts bash separados não mantêm variáveis ​​locais.

Aqui está uma maneira funcional de ssh e executar vários comandos, mantendo o contexto local.

LOCAL_VARIABLE=test

run_remote() {
    echo "$LOCAL_VARIABLE"
    ls some_folder; 
    ./someaction.sh 'some params'
    ./some_other_action 'other params'
}

ssh otherhost "$(set); run_remote"
Michael Petrochuk
fonte
3
Você está pensando nisso como se a única razão pela qual alguém quisesse fazer isso fosse se estivesse sentado em uma linha de comando. Há outras razões comuns para esta questão, bem como, tais como a execução de comandos em ambientes distribuídos com ferramentas como Rundeck, jenkins, etc.
Charles Addis
isso funciona, mas eu estou recebendo mensagens de erro indesejadas
Annahri 24/01
5

Não tenho certeza se o mais limpo para comandos longos, mas certamente o mais fácil:

ssh user@host "cmd1; cmd2; cmd3"
DimiDak
fonte
Melhor em simplicidade!
not2savvy
4

Isso funciona bem para criar scripts, pois você não precisa incluir outros arquivos:

#!/bin/bash
ssh <my_user>@<my_host> "bash -s" << EOF
    # here you just type all your commmands, as you can see, i.e.
    touch /tmp/test1;
    touch /tmp/test2;
    touch /tmp/test3;
EOF
sjas
fonte
A parte "$ (what bash) -s" fornece a localização do bash na máquina local, e não na máquina remota. Eu acho que você quer '$ (what bash) -s' (aspas simples para suprimir a substituição de parâmetro local).
jsears
11
Paul Tomblin forneceu a resposta do documento Bash Here 6 anos antes. Que valor é adicionado por esta resposta?
JWW
-sflag, cito-o de que você pode se safar de substituir a primeira linha por ... , e não fui capaz de me safar apenas de chamar bash, lembro-me vagamente.
sjas
4

A maneira mais fácil de configurar seu sistema para usar sessões ssh únicas por padrão com multiplexação.

Isso pode ser feito criando uma pasta para os soquetes:

mkdir ~/.ssh/controlmasters

E adicionando o seguinte à sua configuração .ssh:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/controlmasters/%r@%h:%p.socket
    ControlMaster auto
    ControlPersist 10m

Agora, você não precisa modificar nenhum do seu código. Isso permite várias chamadas para ssh e scp sem criar várias sessões, o que é útil quando é necessário que haja mais interação entre as máquinas local e remota.

Graças à resposta do @ terminus, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ e https://en.wikibooks.org / wiki / OpenSSH / Cookbook / Multiplexação .

Jonathan
fonte