Bashで複数のコマンドをsshして実行する最もクリーンな方法は何ですか?


349

私はすでにsshエージェントを設定しており、次のようなことを行うBashスクリプトで外部サーバーのコマンドを実行できます。

ssh blah_server "ls; pwd;"

さて、私が本当にやりたいことは、外部サーバーで多くの長いコマンドを実行することです。これらすべてを引用符で囲むと非常に見苦しくなります。これを回避するためだけに、sshを複数回実行することは避けたいと思います。

だから、私はこれを括弧で囲まれた何かで行うことができる方法はありますか?私は次の線に沿って何かを探しています:

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

基本的に、私はそれがクリーンである限り、どんな解決策でも満足します。

編集する

明確にするために、私はこれがより大きなbashスクリプトの一部であることについて話しています。他の人はすぐにスクリプトに対処する必要があるかもしれないので、私はそれをきれいに保ちたいと思います。次のような1行のbashスクリプトは必要ありません。

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

とても醜くて読みにくいからです。


2
うーん、それをすべてサーバー上のスクリプトに入れて、1回のssh呼び出しでそれを呼び出すのはどうですか。
Nikolai Fetissov

コマンドは、クライアント側に依存する場合@Nikolaiは、彼らはその後、シェルスクリプトに書き込むことができるscpsshと実行します。これは最もクリーンな方法だと思います。
khachik

6
これはより大きなbashスクリプトの一部であるため、半分を自分のパソコン上に置き、残りの半分をサーバー上に置いてsshを実行するのではなく、スクリプトを分割しないでください。できれば、パソコンから1つのスクリプトを実行するだけでそれを維持したいのですが。sshに一連のコマンドを入れるクリーンな方法は本当にありませんか?
Eli

最善の方法は、bashではなく、Perl、Python、Rubyなどを使用することです
salva

1
リモートコマンドを引用符で囲まないようにしたいのはなぜですか?引用符の中に、好きなだけ改行を入れることができます。標準入力の代わりに文字列を使用するということは、リモートスクリプトへの入力の読み取りなど、標準入力が利用できることを意味します。(Unixでは、文字列の一部を評価するためにローカルシェルを特に必要としない限り、通常は二重引用符よりも単一引用符が優先されます。)
tripleee

回答:


456

Bash Hereドキュメントはどうですか:

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

コメントで@Globalzによって言及された問題を回避するために、(リモートサイトで何を行っているかに応じて)最初の行を次のように置き換えることで回避できる場合があります。

ssh otherhost /bin/bash << EOF

ヒアドキュメントでは変数置換を実行できますが、引用の問題に対処する必要がある場合があることに注意してください。たとえば、「制限文字列」を引用すると(つまりEOF、上記のように)、変数の置換を行うことはできません。ただし、制限文字列を引用しないと、変数が置換されます。たとえば$NAME、シェルスクリプトで上記を定義している場合は、次のようにすることができます。

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

そして、otherhostあなたはあなたが割り当てたものの名前で宛先にファイルを作成します$NAME。シェルスクリプトの引用に関する他のルールも適用されますが、ここでは説明が複雑すぎます。


6
これはまさに私が欲しいもののようです!どうですか?それを説明するページへのリンクがありますか?
Eli

9
+1私は自分自身を考えていただけです
bosmacs

14
また、この出力をローカルに取得します。stdinは端末ではないため、疑似端末は割り当てられません。
Globalz

14
コマンドラインの拡張を防ぐために、単語(つまり、最初の 'EOF')を引用符で囲むことが重要な場合があります。参照:man bash | 少なく+ / 'Here Documents'
アセム

6
Paulさん、私が提案するのは、この質問について20万回近い見解があり、sshでスクリプトを作成するときに多くの人がここに来ているように見えるためです。スクリプト作成時に値を挿入する必要があるのは一般的です。他の質問やコメントの騒ぎがなければ、私は1つだけ作成して途中にいますが、この段階では見られそうにありません。1行の脚注は人々にいくつかの主要な頭痛を救うかもしれません。
koyae

122

スクリプトをローカルで編集してから、sshにパイプします。例:

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

どこcommands-to-execute-remotely.shルックス上記のリストのように:

ls some_folder
./someaction.sh
pwd;

7
これには、リモートスクリプトによって何が実行されているかを正確に把握できるという大きな利点があります。引用の問題はありません。動的コマンドが必要な場合は、シェルスクリプトをサブシェルと一緒に使用して、引き続きsshにパイプすることができます。つまり( echo $mycmd $myvar ; ...) | ssh myhost、catの使用法と同様に、sshコマンドストリームに何が入っているかが正確にわかります。そしてもちろん、スクリプト内のサブシェルは、読みやすくするために、複数行することができます-見linuxjournal.com/content/bash-sub-shells
RichVel

6
これを引数で行うことはできますcommands-to-execute-remotely.shか?
elaRosca 2014

1
これは、paultomblinソリューションよりもはるかに便利だと思います。スクリプトはファイルを開かなくても、任意のsshサーバーにリダイレクトできるためです。また、それははるかに読みやすいです。
Patrick Bassut 14年

3
使用:はい、しかし、エコーやヒアドキュメント(トップの答えを参照)を使用して$localvar、ローカルに定義された変数を解釈するために\$remotevar、遠隔リモートで定義された変数を解釈するために、\$(something with optionnal args)リモートサーバー上で実行さ何かの出力を得るために。1からsshできる例(または、ここに示すように、複数の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
うーん...これは入力リダイレクトを使用することとどのように違うのssh blah_server < commands-to-execute-remotely.shですか?
flow2k 2018

41

サンプルコードと一致させるには、コマンドを単一または二重qoutes内にラップします。例えば

ssh blah_server "
  ls
  pwd
"

私はこの形式が好きですが、悲しいことにstdデータを変数に格納するのには役立ちません。
Signus

1
Signus、「stdデータを変数に格納する」とはどういう意味ですか?
Andrei B

1
@Signusリモートコマンドを二重引用符の代わりに一重引用符の代わりに使用することをお勧めします(またはローカルシェルがインターセプトしないように二重引用符内でエスケープする必要がある演算子をエスケープします)。それらを補間する)。
tripleee

このfileToRemove = $(find。-type f -name 'xxx.war')のようなコマンドを二重引用符で囲むことができません。fileToRemoveにはファイル名が必要ですが、空の文字列が含まれています。何かを脱出する必要がありますか?
jkonst

37

私には2つの方法があります。

まず、次のような制御ソケットを作成します。

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

コマンドを実行します

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

この方法で、実際にサーバーに再接続せずにsshコマンドを書くことができます。

2つ目は、スクリプトを動的に生成し、スクリプトscpを実行して実行することです。


20

これは次のように行うこともできます。コマンドをスクリプトに入れて、commands-inc.shという名前を付けましょう

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

ファイルを保存します

これをリモートサーバーで実行します。

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

私のために失敗したことはありません。


もともと思っていたのと同じ!しかし、なぜbash -s必要なのでしょうか?
flow2k 2018

また、#!/bin/bash本当に使われていますか?
flow2k

2
-sは互換性のためにあります。From man bash -s -sオプションが存在する場合、またはオプション処理後に引数が残っていない場合、コマンドは標準入力から読み取られます。このオプションを使用すると、インタラクティブシェルを呼び出すときに位置パラメータを設定できます。
RJ

1
RJ、ありがとう!私の最初のコメントでは、私たちがやったように見えますssh user@remote < /path/to/commands-inc.sh(私にとってはうまくいくようです)。そうですね、あなたのバージョンではbash、他のシェルではなくシェルを使用することが保証されていると思います。それが目的ですよね。
flow2k 2018

2
ありがとう。うん、私たちの何人かは私たちのノートを持っており、bashや他のシェルの古いバージョンからの習慣を持っています。:-)
RJ

10

すべてのコマンドをスクリプトに追加すると、次のように実行できます

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh

やや奇妙ですが、うまく機能します。また、argsをスクリプトに渡すことができます(リダイレクトの前または後)。例: 'ssh <remote-user> @ <remote-host> "bash -s" arg1 <./ remote-commands.sh arg2' eg 'ssh <remote-user> @ <remote-host> "bash -s"- --arg1 <./ remote-commands.sh arg2 '
16:17にgaoithe

上記の別の答えで同様の質問がありましたが、含めることの目的は何bash -sですか?
flow2k 2018

1
@ flow2k -sは、標準入力からコマンドを読み取るbashを示します。これがmanページの説明ですIf 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、2018

@JaiPrakashありがとうございましたが、実際にbashコマンドを完全に廃止できるかどうか、つまりssh remote-user@remote-host <./remote-commands.sh。私は自分の方法を試しましたが、何らかの方法でそれが不足しているかどうかはわかりませんが、うまくいくように見えました。
flow2k 2018

マニュアルページについての私の理解による@ flow2kでは、そのオプションがなければ欠陥はありません。引数を渡そうとしている場合は必須です。-感謝
Jai Prakash

9

SSHとBashでの複数のコマンドの実行。

エコーに渡される文字列内のコマンドをセミコロンで区切って、すべてsshコマンドにパイプします。例えば:

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

アーナブは、将来、あなたのコードが何をするか簡単に説明してください。次に、それがどのように機能するかについて話し、次にコードを挿入します。次に、コードをコードブロックに入れて、読みやすくします。
Eric Leschinski、2015年

質問から、OPが避けたいことを正確に提供しました。「次のような1行のbashスクリプトは使いたくない...」
jww

6

私のようにここでつまずく人のために-セミコロンと改行をエスケープすることに成功しました:

最初のステップ:セミコロン。このように、sshコマンドを中断しません。

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

リモートホストの/ homeディレクトリ(rootとしてログイン)がリストされているのに対し、

ssh <host> echo test;ls
                    ^ NO backslash

現在の作業ディレクトリをリストしました。

次のステップ:行を分割します。

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

これは再びリモート作業ディレクトリをリストしました-改善されたフォーマット:

ssh <host>\
  echo test\;\
  ls

ここのドキュメントよりも本当に良い場合、または破線で囲まれた引用-ええと、私が決めるのではない...

(bash、Ubuntu 14.04 LTSを使用。)


1
&&と||を使ってさらに良いことは何ですか?この方法でも、エコーテスト\&\&ls
Miro Kropacek

@MiroKropacekそれもいいですね。いい?あなたが望むものに依存します。2番目のコマンドを条件付きで実行してから、はい、無条件に実行します(最初のコマンドが成功したか失敗したかに関係なく)、それから...
アコンカグア

:@MiroKropacekは、私は、コマンドの周りに二重引用符を入れたときに&&をエスケープする必要はありませんでしたssh host "echo test && ls"
not2savvy

5

複数行の文字列と複数のbashスクリプトを使用して投稿された回答が機能しませんでした。

  • 長い複数行の文字列は維持が困難です。
  • 個別のbashスクリプトはローカル変数を維持しません。

ローカルコンテキストを維持しながら、sshを実行して複数のコマンドを実行する機能的な方法を次に示します。

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"

3
あなたはこれを誰かがこれをしたい唯一の理由が彼らがコマンドラインに座っている場合であるかのように考えています。この質問のための他の一般的な理由は、このようななどrundeck、ジェンキンス、などのツールを持つ分散環境全体でコマンドを実行すると、そこにもある
チャールズ・アディス

これは機能しますが、不要なエラーメッセージが表示されます
Annahri

5

長いコマンドで最もクリーンかどうかはわかりませんが、確かに最も簡単です。

ssh user@host "cmd1; cmd2; cmd3"

シンプルで最高!
not2savvy

4

他のファイルを含める必要がないため、これはスクリプトの作成に適しています。

#!/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

"$(which bash)-s"の部分は、リモートマシンではなくローカルマシン上のbashの場所を示します。代わりに '$(which bash)-s'(ローカルパラメータの置換を抑制する単一引用符)が必要だと思います。
jsears 2016年

1
Paul Tomblinが 6年前にBash Here Documentの回答を提供しました。この回答によってどのような価値が追加されますか?
jww 2016年

-sフラグ、私はあなたが最初の行を...に置き換えることで逃れることができるかもしれないと彼に引用します、そして私はぼんやりと覚えているbashを呼び出すだけでは逃れることができませんでした。
sjas

4

デフォルトで多重化を使用して単一のsshセッションを使用するようにシステムを構成する最も簡単な方法。

これは、ソケット用のフォルダーを作成することで実行できます。

mkdir ~/.ssh/controlmasters

次に、以下を.ssh構成に追加します。

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

これで、コードを変更する必要はありません。これにより、複数のセッションを作成せずにsshとscpを複数回呼び出すことができます。これは、ローカルマシンとリモートマシンの間でより多くの対話が必要な場合に役立ちます。

@terminusの回答のおかげで、http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/およびhttps://en.wikibooks.org / wiki / OpenSSH / Cookbook / Multiplexing

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.