参考レベル

参考レベル

私はしばしば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")"

おすすめ記事