私はしばしばsshを介して複雑なコマンドを発行することになります。これらのコマンドには、awkまたはperlの1行へのパイプ処理が含まれ、その結果、一重引用符と$が含まれます。私は、引用を適切に行うための厳格な規則を理解することができず、それについての良い参考資料も見つけられませんでした。たとえば、次のことを考慮してください。
# 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つまたは2つ以上のネストされたレベルを処理する場合、通常は「下から上へ」(つまり、最も内側から最も外側へ)作業するのが最も簡単です。
引用のレベル
コマンドの例を見てみましょう。
pgrep -fl java | grep -i datanode | awk '{print $1}'
最初のコマンド例(上記)では、シェル、 pgrepの正規表現の4つの言語を使用しています。 、 grepの正規表現 ( pgrepの正規表現言語とは異なる場合があります )、および awk 。関係する解釈には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 、ダッシュ 、 ksh 、 bash 、 zsh など)。他の種類の貝殻(魚 、 rc など)別の構文が必要になる場合がありますが、その方法は引き続き適用されます。
下、上
- 最も内側のレベルで表現する文字列を作成します。
- 次に高い言語の引用レパートリーから引用メカニズムを選択します。
- 選択した引用メカニズムに従って、目的の文字列を引用します。
- 多くの場合、どの引用メカニズムを適用するかには多くのバリエーションがあります。手でそれをすることは通常練習と経験の問題です。プログラムでそれを行うときは、通常、最も簡単に正しく実行できるものを選択するのが最善です(通常は「最も文字通りの」(最も少ないエスケープ))。
- オプションで、結果の引用符で囲まれた文字列を追加のコードとともに使用します。
- 希望する引用/解釈のレベルにまだ達していない場合は、結果の引用文字列(および追加されたコード)を取得し、手順2の開始文字列として使用します。
セマンティクスの引用は異なります
ここで覚えておくべきことは、各言語(引用レベル)が同じ引用文字にわずかに異なるセマンティクス(または大幅に異なるセマンティクス)を与える可能性があるということです。
ほとんどの言語には「リテラル」引用メカニズムがありますが、それらは正確にどの程度リテラルであるかによって異なります。ボーンのようなシェルの一重引用符は実際には文字通りです(つまり、一重引用符自体を引用するために使用することはできません)。他の言語(Perl、Ruby)は、一部を解釈するという点で、文字通りではありません。 文字通りではない一重引用符で囲まれた領域内のバックスラッシュシーケンス(具体的には、\
および'
結果は および
'
、ただし、他のバックスラッシュシーケンスは実際には文字通りです。
引用規則と全体的な構文を理解するには、各言語のドキュメントを読む必要があります。
あなたの例
例の最も内側のレベルはawk プログラム。
{print $1}
これをシェルコマンドラインに埋め込みます:
pgrep -fl java | grep -i datanode | awk …
スペースと$
を(少なくとも)保護する必要があります awk プログラム。明らかな選択は、プログラム全体のシェルで一重引用符を使用することです。
-
'{print $1}'
ただし、他にも選択肢があります:
-
{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 '%sn' "$*" | 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")"