参考レベル

参考レベル

私はしばしばsshを介して複雑なコマンドを実行します。これには、awkまたはperl行をパイプすることが含まれているため、一重引用符と$。私は、引用を正しくするための厳格で迅速なルールも、良い参考資料も見つかりませんでした。たとえば、次の状況を考えてみましょう。

# what I'd run locally:
CMD='pgrep -fl java | grep -i datanode | awk '{print $1}'
# this works with ssh $host "$CMD":
CMD='pgrep -fl java | grep -i datanode | awk '"'"'{print $1}'"'"

(awkステートメントに追加の引用符があることに注意してください。)

たとえば、どのように機能させることができますかssh $host "sudo su user -c '$CMD'"?このような状況で見積もりを管理する一般的な方法はありますか? ...

答え1

複数レベルの参照(実際には複数レベルの解析/解釈)を処理することは複雑になる可能性があります。次の点を念頭に置いておくと便利です。

  • 各「参照レベル」には、さまざまな言語を含めることができます。
  • 引用規則は言語によって異なります。
  • 1つ以上のネストレベルを処理するときは、「床から上へ」(つまり、最も内側から最も外側のレベルまで)作業するのが最も簡単なことがよくあります。

参考レベル

例のコマンドを見てみましょう。

pgrep -fl java | grep -i datanode | awk '{print $1}'

上記の最初の例のコマンドは、4つの言語、つまりシェル、正規表現を使用します。正規表現、正規表現はgrep(正規表現言語と異なる場合があります。正規表現)、そしてアッ。関連する解釈には2つのレベルがあります。シェルと各関連コマンドのシェル以降の解釈レベル。明示的な引用には1つのレベルしかありません(シェル引用)。アッ)。

ssh host …

次にレベルを追加します。SSH上。これは実際には別のシェルレベルです。SSHコマンド自体は解釈されず(たとえば)リモート側のシェルに渡され、sh -c …そのシェルは文字列を解釈します。

ssh host "sudo su user -c …"

次に、次を使用して、中間に別のシェルレベルを追加する方法を尋ねます。(通過するSudo、コマンド引数を解釈しないので無視できます)。これで3つのレベルのネストが完了しました(アッ→シェル、シェル→シェル(SSH)、シェル→シェル(su ユーザー -c)、したがって「ボトムアップ」アプローチを使用することをお勧めします。あなたのシェルがBourneと互換性があるとします(例:シェン禁煙健康増進協会スプリント変化の多く強く打つ扱いにくい、等。 )。他の種類の殻(RCなど)には別の構文が必要な場合がありますが、アプローチはまだ機能しています。

ボトムアップ

  1. 最も内側のレベルで表示する文字列を指定します。
  2. 下位言語の引用ライブラリから引用メカニズムを選択します。
  3. 選択した引用メカニズムに従って、必要な文字列を引用してください。
    • 参照メカニズムの適用方法にはさまざまなバリエーションがあることがよくあります。手作りの作業には通常練習と経験が必要です。プログラムでこれを行うときは、通常、最も簡単に正しいものを選択するのが最善です(通常、「最も文字通り」(最小エスケープ))。
  4. (オプション)追加のコードで結果の引用符付き文字列を使用します。
  5. まだ必要な引用/説明レベルに達していない場合は、生成された引用文字列(および追加されたコード)を取得し、手順2の開始文字列として使用してください。

参照の意味は様々です

ここで覚えておくべきことは、各言語(引用レベル)が同じ引用文字に対してわずかに異なる意味(またはまったく異なる意味)を提供できることです。

ほとんどの言語には「文字通り」参照メカニズムがありますが、文字通りの程度はさまざまです。 Bourneなどのシェルの一重引用符は実際には文字通りです(つまり、一重引用符文字自体を引用するために使用できないことを意味します)。他の言語(Perl、Ruby)は解釈されるので、あまり簡単ではありません。一部一重引用符領域内のバックスラッシュシーケンスはリテラルではありません(具体的には、銀と\\引き続き、他の\'バックスラッシュシーケンスは実際にはリテラルです)。\'

引用規則と完全な構文を理解するには、各言語のドキュメントを読む必要があります。

あなたの模範

あなたの例の最も内側のレベルは次のとおりです。アップログラム。

{print $1}

これをシェルコマンドラインに含めたいと思います。

pgrep -fl java | grep -i datanode | awk …

(少なくとも)スペースと$内部は保護する必要がありますアップログラム。確実な選択は、シェルのプログラム全体の周りに一重引用符を使用することです。

  • '{print $1}'

ただし、他のオプションもあります。

  • {print\ \$1}宇宙から直接脱出$
  • {print' $'1}一重引用符にはスペースのみが含まれます。$
  • "{print \$1}"二重引用符全体とエスケープ済み$
  • {print" $"1}二重引用符にはスペースのみが含まれているため、$
    ルールはわずかに変更される可能性があります($二重引用符で囲まれた文字列の末尾でエスケープされていないのはリテラルです)、ほとんどのシェルで動作するようです。

プログラムが開く中括弧と閉じ中括弧の間にコンマを使用している場合は、一部のシェルで「分岐拡張」を回避するには、カンマまたは中括弧を引用またはエスケープする必要があります。

これを選択'{print $1}'し、残りのシェル「コード」に含めます。

pgrep -fl java | grep -i datanode | awk '{print $1}'

次に、次のように実行したいと思います。そしてSudo

sudo su user -c …

su user -c …some-shell -c …(他のUIDで実行することを除いて)同様に別のシェルレベルを追加するだけです。Sudoその引数は解釈されないため、参照レベルは追加されません。

コマンド文字列には異なるシェルレベルが必要です。単一引用符をもう一度選択できますが、既存の単一引用符を特別に処理する必要があります。一般的な方法は次のとおりです。

'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'

ここでは、4つの文字列(最初の一重引用符で囲まれた文字列(pgrep … awk)、エスケープされた一重引用符、一重引用符で囲まれた文字列)を解釈して連結します。アップログラムは、別のエスケープされた一重引用符です。

もちろん、次のようなさまざまな選択肢があります。

  • pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}重要なことはすべて避けてください
  • pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}同じですが、余分なスペースはありません(アップログラム! )
  • "pgrep -fl java | grep -i datanode | awk '{print \$1}'"全体を二重引用符で囲み、エスケープします。$
  • 'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"エスケープ(1文字)の代わりに二重引用符(2文字)を使用して、通常より少し長くなりました。

最初のレベルで別の参照を使用すると、このレベルで追加のバリエーションが可能になります。

  • 'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
  • 'pgrep -fl java | grep -i datanode | awk {print\ \$1}'

最初のバリアントの挿入Sudo/*su* コマンドラインでは、次のようになります。

sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'

たとえば、異なる単一のシェルレベルのコンテキストで同じ文字列を使用できますssh host …

次にレベルを追加します。SSH上。これは実際には別のシェルレベルです。SSHコマンド自体は解釈されませんが、(たとえば)経由でリモート側のシェルに渡され、そのシェルは文字sh -c …列を解釈します。

ssh host …

プロセスは同じです。文字列を取得し、引用方法を選択、使用、挿入します。

二重引用符をもう一度使用してください。

'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

これで、11個の文字列が解釈され連結されます。'sudo su user -c '、エスケープされた一重引用符、、エスケープされた一重引用符'pgrep … awk '、エスケープされたバックスラッシュ、2つのエスケープされた一重引用符、一重引用符アップログラム、一重引用符エスケープ、バックスラッシュエスケープ、最後に一重引用符エスケープ。

最終形態は次のとおりです。

ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

手動で入力するのは少し厄介ですが、シェル一重引用符の文字通りの特性により、微妙な変更を簡単に自動化できます。

#!/bin/sh

sq() { # single quote for Bourne shell evaluation
    # Change ' to '\'' and wrap in single quotes.
    # If original starts/ends with a single quote, creates useless
    # (but harmless) '' at beginning/end of result.
    printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}

# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }

ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"

ssh host "$(sq "$s2")"

答え2

バラよりクリス・ジョンソンの答え明確で詳細な説明と普遍的なソリューションを入手してください。いくつかの一般的な状況で役立ついくつかの追加のヒントを提供します。

一重引用符は、一重引用符を除くすべてをエスケープします。したがって、変数の値に一重引用符が含まれていないことがわかっている場合は、シェルスクリプトで一重引用符の間に変数を安全に挿入できます。

su -c "grep '$pattern' /root/file"  # assuming there is no ' in $pattern

ローカルシェルがksh93またはzshの場合、変数を'\''${foo//pattern/replacement}

su -c "grep '${pattern//'/'\''}' /root/file"  # if the outer shell is zsh
su -c "grep '${pattern//\'/\'\\\'\'}' /root/file"  # if the outer shell is ksh93

ネストされた参照処理を回避するもう1つの方法は、可能な限り環境変数を介して文字列を渡すことです。 sshとsudoはほとんどの環境変数を削除する傾向がありますが、LC_*これらの変数はしばしばユーザビリティに非常に重要であり(ロケール情報を含む)、セキュリティに敏感であると見なされることはほとんどないため、通常は許可されるように構成されています。

LC_CMD='what you would use locally' ssh $host 'sudo su user -c "$LC_CMD"'

これにはLC_CMDシェルの部分が含まれているので、文字通り最も内側のシェルに与えなければならない。したがって、この変数は上記のシェルによって拡張されます。最も内側にありますが、1つのシェルだけがコマンドを表示します"$LC_CMD"

同様の方法を使用して、データをテキスト処理ユーティリティに渡すことができます。シェル補間を使用すると、ユーティリティは変数の値をコマンドとして処理します(たとえば、変数sed "s/$pattern/$replacement/"/sedの代わりに)。したがって、-vオプションまたはENVIRON配列と一緒にawkを使用してシェルからデータを渡します(ENVIRONエクスポート変数を使用します)。

awk -vpattern="$pattern" replacement="$replacement" '{gsub(pattern,replacement); print}'

答え3

〜のようにクリス・ジョンソンはよく説明しています。、ここにはいくつかのレベルの間接指示があります。ローカルにshellパイプラインを実行するように指示する必要があります。この種のコマンドには退屈な作業がたくさん必要です。shellsshsudosushellpgrep -fl java | grep -i datanode | awk '{print $1}'user\'"quote quoting"\'

私のアドバイスを受け入れる場合は、すべての空腹を捨てて、次のことを行います。

% ssh $host <<REM=LOC_EXPANSION <<'REMOTE_CMD' |
> localhost_data='$(commands run on localhost at runtime)' #quotes don't affect expansion
> more_localhost_data="$(values set at heredoc expansion)" #remote shell will receive m_h_d="result"
> REM=LOC_EXPANSION
> commands typed exactly as if located at 
> the remote terminal including variable 
> "${{more_,}localhost_data}" operations
> 'quotes and' \all possibly even 
> a\wk <<'REMOTELY_INVOKED_HEREDOC' |
> {as is often useful with $awk
> so long as the terminator for}
> REMOTELY_INVOKED_HEREDOC
> differs from that of REM=LOC_EXPANSION and
> REMOTE_CMD
> and here you can | pipeline operate on |\
> any output | received from | ssh as |\
> run above | in your local | terminal |\
> however | tee > ./you_wish.result
<desired output>

詳細:

私の(おそらくあまりにも冗談)答えをチェックしてくださいスラッシュ置換のためのさまざまなタイプの引用符付きのパイプパスここでは、それがどのように機能するかについていくつかの理論を議論します。

- マイクロフォン

答え4

もっと二重引用符を使用するのはどうですか?

もしそうなら、ssh $host $CMD以下を使っても大丈夫でしょう。

CMD="pgrep -fl java | grep -i datanode | awk '{print $1}'"

今より複雑なものを見てください:との敏感な文字をssh $host "sudo su user -c \"$CMD\""エスケープするだけです。それで私はこれがうまくいくかどうか試してみます:。CMD$\"echo $CMD | sed -e 's/[$\\"]/\\\1/g'

よかったら、echo + sedをシェル関数で包んで使用できますssh $host "sudo su user -c \"$(escape_my_var $CMD)\""

関連情報