の実装にssh
は、シェルを使用せずにクライアントからサーバーにコマンドを渡すネイティブな方法はないと思います。
これで、特定のインタープリターのみを実行するようにリモートシェルに指示できれば(sh
期待される構文がわかっているのような)、別の手段で実行するコードを与えることができます。
他の手段は、たとえば標準入力または環境変数です。
どちらも使用できない場合は、以下のハッキーな3番目のソリューションを提案します。
stdinを使用する
リモートコマンドにデータを入力する必要がない場合は、これが最も簡単なソリューションです。
リモートホストにオプションxargs
をサポート-0
するコマンドがあり、コマンドが大きすぎないことがわかっている場合は、次の操作を実行できます。
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
そのxargs -0 env --
コマンドラインは、これらすべてのシェルファミリで同じように解釈されます。xargs
stdinの引数のヌル区切りリストを読み取り、それらを引数としてに渡しますenv
。これは、最初の引数(コマンド名)に=
文字が含まれていないことを前提としています。
またはsh
、sh
引用構文を使用して各要素を引用した後、リモートホストで使用できます。
shquote() {
LC_ALL=C awk -v q=\' '
BEGIN{
for (i=1; i<ARGC; i++) {
gsub(q, q "\\" q q, ARGV[i])
printf "%s ", q ARGV[i] q
}
print ""
}' "$@"
}
shquote "${cmd[@]}" | ssh user@host sh
環境変数を使用する
ここで、クライアントからリモートコマンドの標準入力にデータを供給する必要がある場合、上記のソリューションは機能しません。
ssh
ただし、一部のサーバー展開では、クライアントからサーバーへの任意の環境変数の受け渡しが許可されます。たとえば、Debianベースのシステムでの多くのopensshデプロイメントでは、名前がで始まる変数を渡すことができますLC_
。
このような場合、上記のように引用符で囲まれたコードLC_CODE
を含む変数を用意し 、クライアントにその変数を渡すように指示した後、リモートホストでsh
実行sh -c 'eval "$LC_CODE"'
できます(これも、すべてのシェルで同じように解釈されるコマンドラインです)。
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
すべてのシェルファミリと互換性のあるコマンドラインを構築する
上記のオプションがどれも受け入れられない場合(stdinとsshdは変数を受け入れないため、または一般的な解決策が必要なため)、すべてと互換性のあるリモートホスト用のコマンドラインを準備する必要がありますサポートされているシェル。
これらのすべてのシェル(Bourne、csh、rc、es、fish)には独自の構文があり、特に異なる引用メカニズムがあり、それらのいくつかは回避するのが難しい制限があるため、特に注意が必要です。
ここに私が思いついた解決策があります、私はそれをさらに詳しく説明します:
#! /usr/bin/perl
my $arg, @ssh, $preamble =
q{printf '%.0s' "'\";set x=\! b=\\\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\\\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
};
@ssh = ('ssh');
while ($arg = shift @ARGV and $arg ne '--') {
push @ssh, $arg;
}
if (@ARGV) {
for (@ARGV) {
s/'/'\$q\$b\$q\$q'/g;
s/\n/'\$q'\$n'\$q'/g;
s/!/'\$x'/g;
s/\\/'\$b'/g;
$_ = "\$q'$_'\$q";
}
push @ssh, "${preamble}exec sh -c 'IFS=;exec '" . join "' '", @ARGV;
}
exec @ssh;
これはのperl
ラッパースクリプトssh
です。私はそれを呼び出しますsexec
。次のように呼び出します:
sexec [ssh-options] user@host -- cmd and its args
あなたの例では:
sexec user@host -- "${cmd[@]}"
そして、ラッパーはcmd and its args
コマンドラインに変わり、すべてのシェルcmd
がその引数で呼び出すと解釈されます(内容に関係なく)。
制限事項:
- 前文とコマンドの引用方法は、リモートコマンドラインがかなり大きくなることを意味します。これは、コマンドラインの最大サイズの制限に早く到達することを意味します。
- 私がテストしたのは、Bourne shell(家宝ツールチェストから)、dash、bash、zsh、mksh、lksh、yash、ksh93、rc、es、akanga、csh、tcsh、最近のDebianシステムで見つかった魚、および/ Solaris 10ではbin / sh、/ usr / bin / ksh、/ bin / csh、および/ usr / xpg4 / bin / sh
- 場合は
yash
、リモートログインシェルがある、あなたは、その引数が無効な文字が含まれているコマンドを渡すことはできませんが、それはの制限だyash
あなたはとにかく回避することができません。
- cshやbashなどの一部のシェルは、ssh経由で起動されると、いくつかのスタートアップファイルを読み取ります。これらはプリアンブルがまだ機能するように動作を劇的に変更しないと仮定します。
- のほかに
sh
、リモートシステムにprintf
コマンドがあることも想定しています。
それがどのように機能するかを理解するには、異なるシェルで引用がどのように機能するかを知る必要があります
- Bourne:特別な文字
'...'
を含まない強力な引用符です。バックスラッシュでエスケープできる"..."
弱い引用符"
です。
csh
。"
内部でエスケープできないことを除き、Bourneと同じ"..."
です。また、改行文字の前にバックスラッシュを入力する必要があります。そして!
、一重引用符の内側でも問題を引き起こします。
rc
。唯一の引用符は'...'
(強い)です。単一引用符内の単一引用符は''
(など'...''...'
)として入力されます。二重引用符またはバックスラッシュは特別ではありません。
es
。rcと同じですが、引用符の外側でバックスラッシュを使用すると単一引用符をエスケープできます。
fish
:バックスラッシュが'
内部でエスケープすることを除き、Bourneと同じ'...'
です。
これらすべての制約があるため、コマンドライン引数を確実に引用できないため、すべてのシェルで機能することがわかります。
次のように単一引用符を使用します。
'foo' 'bar'
以下を除くすべてで動作します:
'echo' 'It'\''s'
では動作しませんrc
。
'echo' 'foo
bar'
では動作しませんcsh
。
'echo' 'foo\'
では動作しませんfish
。
我々は、バックスラッシュのように、変数にそれらの問題の文字を格納するために管理している場合しかし、私たちは周りのほとんどのこれらの問題の仕事をすることができるはず$b
、で単一引用符$q
では、改行$n
(および!
中$x
シェル独立した方法で、cshの履歴展開のため)。
'echo' 'It'$q's'
'echo' 'foo'$b
すべてのシェルで動作します。csh
ただし、それでも改行では機能しません。$n
改行が含まれている場合は、改行に展開するためにそれをcsh
記述する必要があり$n:q
、他のシェルでは機能しません。したがって、代わりにここで行うことはsh
、sh
それらを呼び出して展開すること$n
です。これは、リモートログインシェル用との2つのレベルの引用を行う必要があることも意味しますsh
。
$preamble
そのコードではトリッキーな部分です。それはちょうどそれらを定義し、それぞれが(それは他人のためにコメントアウトている間)のシェルの一つだけによって解釈コードのいくつかのセクションを持っているすべてのシェルでは、様々な異なる引用規則を使用しています$b
、$q
、$n
、$x
それぞれのシェルの変数。
以下は、リモートユーザーのログインシェルによって解釈されるシェルコードのhost
例です。
printf '%.0s' "'\";set x=\! b=\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
exec sh -c 'IFS=;exec '$q'printf'$q' '$q'<%s>'$b'n'$q' '$q'arg with $and spaces'$q' '$q''$q' '$q'even'$q'$n'$q'* * *'$q'$n'$q'newlines'$q' '$q'and '$q$b$q$q'single quotes'$q$b$q$q''$q' '$q''$x''$x''$q
そのコードは、サポートされているシェルのいずれかによって解釈されると、同じコマンドを実行することになります。
cmd
引数がした/bin/sh -c
我々は、我々はすべてのケースの99%でPOSIXシェルで終わるだろうではないでしょうか?もちろん、この方法で特殊文字をエスケープするのは少し苦痛ですが、最初の問題は解決しますか?