複数レベルの引用(実際には、複数レベルの解析/解釈)を扱うことは複雑になる可能性があります。いくつかのことを覚えておくと役立ちます。
- 各「引用のレベル」には、異なる言語が関係する可能性があります。
- 引用ルールは言語によって異なります。
- 1つまたは2つ以上のネストされたレベルを処理する場合、通常は「下から上」(つまり、最も内側から最も外側)に作業するのが最も簡単です。
引用のレベル
コマンドの例を見てみましょう。
pgrep -fl java | grep -i datanode | awk '{print $1}'
最初のコマンド例(上記)では、シェル、pgrepの正規表現、grepの正規表現(pgrepの正規表現言語とは異なる場合があります)、およびawkの 4つの言語を使用しています。解釈には2つのレベルがあります。シェルと、関連する各コマンドのシェルの後の1つのレベルです。クォートの明示的なレベルは1つだけです(シェルクォートをawkにクォートする)。
ssh host …
次に、レベルのsshを追加しました。これは事実上別のシェルレベルです。sshはコマンド自体を解釈せず、リモートエンドのシェルに(たとえば)経由で渡しsh -c …
、そのシェルは文字列を解釈します。
ssh host "sudo su user -c …"
次に、suを使用して中間に別のシェルレベルを追加することを求めました(コマンド引数を解釈しないsudoを使用して、無視できます)。この時点で、3つのレベルのネストが行われています(awk →シェル、シェル→シェル(ssh)、シェル→シェル(su user -c)。シェルはBourne互換です(sh、ash、dash、ksh、bash、zshなど)。他の種類のシェル(fish、rcなど)異なる構文が必要になる場合がありますが、この方法は引き続き適用されます。
一気飲み
- 最も内側のレベルで表現する文字列を作成します。
- 次に高い言語の引用レパートリーから引用メカニズムを選択します。
- 選択した引用メカニズムに従って、目的の文字列を引用します。
- 多くの場合、どの引用メカニズムを適用するかには多くのバリエーションがあります。手作業で行うことは、通常、実践と経験の問題です。プログラムで実行する場合、通常、最も簡単に正しい方法を選択するのが最善です(通常、「最もリテラル」(少ないエスケープ))。
- 必要に応じて、追加のコードで結果の引用文字列を使用します。
- 希望するレベルの引用/解釈にまだ到達していない場合は、結果の引用文字列(および追加されたコード)を取得し、手順2で開始文字列として使用します。
引用セマンティクスは異なります
ここで心に留めておくべきことは、各言語(引用レベル)が同じ引用文字に対してわずかに異なるセマンティクス(または大幅に異なるセマンティクス)を与える場合があるということです。
ほとんどの言語には「リテラル」引用メカニズムがありますが、文字通り正確に異なります。Bourneのようなシェルの単一引用符は実際にはリテラルです(つまり、単一引用符文字自体を引用するために使用することはできません)。他の言語(PerlやRubyは)それほど彼らは解釈していることでリテラルあるいくつかの非文字通り単一引用符で囲まれた領域内のバックスラッシュシーケンスを(具体的には、\\
そして\'
、その結果\
と'
、それ以外のバックスラッシュシーケンスは、実際にはリテラルです)。
引用規則と全体的な構文を理解するには、各言語のドキュメントを読む必要があります。
あなたの例
例の最も内側のレベルはawkプログラムです。
{print $1}
これをシェルコマンドラインに埋め込みます。
pgrep -fl java | grep -i datanode | awk …
我々は(最低でも)スペースおよび保護するために必要$
でawkのプログラムを。明らかな選択は、プログラム全体のシェルで一重引用符を使用することです。
ただし、他の選択肢もあります。
{print\ \$1}
スペースを直接脱出し、 $
{print' $'1}
スペースのみの一重引用符と $
"{print \$1}"
全体を二重引用符で囲み、エスケープします $
{print" $"1}
スペースのみを二重引用符で囲み、$
これは少し規則を曲げる可能性があります($
二重引用符で囲まれた文字列の最後でエスケープされないのはリテラルです)が、ほとんどのシェルで機能するようです。
プログラムが開き中括弧と閉じ中括弧の間にコンマを使用した場合、一部のシェルで「括弧の展開」を避けるために、コンマまたは中括弧のいずれかを引用またはエスケープする必要があります。
それを選択'{print $1}'
して、シェルの「コード」の残りの部分に埋め込みます。
pgrep -fl java | grep -i datanode | awk '{print $1}'
次に、suとsudoを介してこれを実行したいと考えました。
sudo su user -c …
su user -c …
同じようであるsome-shell -c …
ので、(他のいくつかのUIDの下で実行されている除く)SUはちょうど別のシェルレベルが追加されます。sudoは引数を解釈しないため、引用レベルを追加しません。
コマンド文字列には別のシェルレベルが必要です。単一引用符を再度選択できますが、既存の単一引用符に特別な処理を与える必要があります。通常の方法は次のようになります。
'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
ここには、シェルが解釈および連結する4つの文字列があります。最初の単一引用符付き文字列(pgrep … awk
)、エスケープされた単一引用符、単一引用符付きawkプログラム、エスケープされた単一引用符です。
もちろん、多くの選択肢があります。
pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}
重要なすべてを逃れる
pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}
同じですが、余分な空白はありません(awkプログラムでも!)
"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は単一引用符をエスケープ、単一引用符で囲まれたエスケープのawkプログラム、単一引用符、エスケープバックスラッシュをエスケープし、そして最終的には単一引用符をエスケープ。
最終的なフォームは次のようになります。
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")"