Bashのコマンドの単一引用符内の変数の展開


393

一重引用符と、一重引用符と変数の中に他のいくつかのコマンドがあるbashスクリプトからコマンドを実行したいと思います。

例えば repo forall -c '....$variable'

この形式で$は、エスケープされ、変数は展開されません。

次のバリエーションを試しましたが、拒否されました。

repo forall -c '...."$variable" '

repo forall -c " '....$variable' "

" repo forall -c '....$variable' "

repo forall -c "'" ....$variable "'"

変数の代わりに値を代入すると、コマンドは正常に実行されます。

どこがいけないのか教えてください。


33
repo forall -c ' ...before... '"$variable"' ...after...'
n。「代名詞」m。

2
@nm一重引用符はrepoコマンドの一部です。これはうまくいくとは思い
ません

1
bash一重引用符を食べる。にいないかbash、単一引用符がrepoコマンドの一部ではありません。
n。「代名詞」m。

わかりませんが、一重引用符が必要な場合に備えて、forall -c "'... before ..." $ variable "... after ...'"をリポしませんか?
ecv

bashシェルで実行されるbashスクリプトとrepo forallコマンドは、パラメーターを一重引用符で囲みます。実際の値を代入すると、コマンドは正常に実行されます。
Rachit

回答:


621

一重引用符の内側では、例外なくすべてが文字通り保持されます。

つまり、引用符を閉じて何かを挿入してから、再度入力する必要があります。

'before'"$variable"'after'
'before'"'"'after'
'before'\''after'

単語の連結は単純に並置によって行われます。確認できるように、上記の各行はシェルにとって単一の単語です。引用符(状況に応じて一重引用符または二重引用符)は単語を分離しません。彼らは唯一、空白のような様々な特殊文字の無効な解釈に使用されている$;マーク・リードの回答を参照してください引用の良いチュートリアル...。また関連:bashでエスケープする必要がある文字はどれですか?

シェルによって解釈された文字列を連結しません

変数を連結してシェルコマンドを構築することは絶対に避けてください。これは、SQLフラグメントの連結(SQLインジェクション!)に似た悪い考えです。

通常、コマンドにプレースホルダーを含め、コマンドを変数と一緒に指定して、呼び出し先が呼び出し引数リストからそれらを受け取ることができるようにすることができます。

たとえば、次は非常に安全ではありません。これをしないでください

script="echo \"Argument 1 is: $myvar\""
/bin/sh -c "$script"

の内容$myvarが信頼できない場合、ここにエクスプロイトがあります。

myvar='foo"; echo "you were hacked'

上記の呼び出しの代わりに、位置引数を使用します。次の呼び出しの方が優れています-悪用できません:

script='echo "arg 1 is: $1"'
/bin/sh -c "$script" -- "$myvar"

への割り当てで単一のティックを使用していることに注意してくださいscript。これは、変数の展開や他の形式の解釈なしに、文字どおりに解釈されることを意味します。


@ Jo.SOは機能しません。最初の引用符が閉じられるまで、repoコマンドはパラメーターのみを取るためです。それ以降は、リポジトリでは無視され、別のエラーが生成されます。
Rachit

8
@Rachit-シェルはそのように機能しません。 "some"thing' like'thisシェルへのすべての単語です。引用符は、解析に関する限り、何も終わらせません。また、シェルによって呼び出さたコマンド、どのように引用されたかを通知する方法はありません。
Mark Reed

3
その2番目の文(「単一引用符をエスケープすることもできない」)は、単一引用符をシェルのブラックホールにします。

@エバート:それをよりよく言う方法がわからない。文章を削除しました。
Jo So

@JoSoそれは残念です:冗談は平らになりません。

96

repoコマンドは、それを取得引用符の種類を気にすることはできません。パラメータの展開が必要な場合は、二重引用符を使用してください。それはあなたが多くのものをバックスラッシュする必要があることを意味するなら、それの大部分に一重引用符を使用し、それからそれらを抜け出して、展開が起こる必要がある部分のために二重に行きます。

repo forall -c 'literal stuff goes here; '"stuff with $parameters here"' more literal stuff'

興味があれば説明します。

シェルからコマンドを実行する場合、そのコマンドが引数として受け取るのは、ヌル終了ストリングの配列です。これらの文字列は絶対に含まれていてもよい任意の null以外の文字が。

ただし、シェルがコマンドラインからその文字列の配列を構築している場合、一部の文字は特別に解釈されます。これは、コマンドをより簡単に(実際に)入力できるように設計されています。たとえば、スペースは通常、配列内の文字列間の境界を示します。そのため、個々の引数は「単語」と呼ばれることもあります。しかし、それでも引数にはスペースが含まれる場合があります。必要なことをシェルに伝える方法が必要なだけです。

任意の文字(スペース、または別のバックスラッシュを含む)の前にバックスラッシュを使用して、シェルにその文字を文字どおりに処理するように指示できます。しかし、あなたはこのようなことをすることができますが:

echo \"Thank\ you.\ \ That\'ll\ be\ \$4.96,\ please,\"\ said\ the\ cashier

...面倒になる可能性があります。したがって、シェルは代わりに引用符を提供します。これらには2つの主要な種類があります。

二重引用符は「グループ化引用符」と呼ばれます。ワイルドカードやエイリアスが展開されないようにしますが、ほとんどの場合、単語にスペースを含めるためのものです。パラメータやコマンドの展開(aによって通知される種類のもの$)などの他のことはまだ起こります。そしてもちろん、二重引用符内にリテラルの二重引用符が必要な場合は、バックスラッシュする必要があります。

echo "\"Thank you. That'll be \$4.96, please,\" said the cashier"

一重引用符はより厳格です。それらの間のすべては、バックスラッシュを含め、完全に文字どおりに解釈されます。一重引用符の中にリテラル一重引用符を取得する方法は絶対にありません。

幸い、シェル内の引用符は単語の区切り文字ではありません。単独では、単語を終了しません。異なるタイプの引用符間を含め、同じ単語内で引用符を出入りして、目的の結果を得ることができます。

echo '"Thank you. That'\''ll be $4.96, please," said the cashier'

つまり、バックスラッシュははるかに少なくなりますが、閉じる単一引用符、バックスラッシュ付きリテラル単一引用符、開く単一引用符のシーケンスは、慣れるまでに時間がかかります。

最近のシェルでは、POSIX標準では指定されていない別の引用スタイルが追加されています。このスタイルでは、先頭の単一引用符の前にドル記号が付いています。このように引用された文字列は、ANSI Cプログラミング言語の文字列リテラルと同様の規則に従うため、「ANSI文字列」および$'... 'ペア「ANSI引用符」と呼ばれることもあります。そのような文字列内では、バックスラッシュが文字どおりに使用されるという上記のアドバイスは適用されなくなります。だけでなく、あなたはそれにバックスラッシュを付加することで、リテラル単一引用符やバックスラッシュを含めることができますが、シェルはまた、のような(ANSI Cの文字エスケープを拡大-代わりに、彼らは再び特別なっ\n改行、\tタブのため、そして\xHH持つ文字の16進コードHH)。ただし、それ以外の場合は、単一引用符で囲まれた文字列として動作します。パラメーターやコマンドの置換は行われません。

echo $'"Thank you.  That\'ll be $4.96, please," said the cashier'

注意すべき重要な点は、echoコマンドへの引数として受け取った単一の文字列は、これらのすべての例でまったく同じであることです。シェルがコマンドラインの解析を完了すると、実行されているコマンドが引用された方法を知る方法がなくなります。それがしたかったとしても。


2
はい。今、私は分かる。それは完全に動作します。あなたの助けに感謝します!
Rachit

6
この返信は素晴らしかった。
ヴィニシウスFerrão

1
ある$'string'フォーマットPOSIXに準拠しますか?また、名前はありますか?
ワイルドカード2016年

2
@Wildcard $'string'はPOSIX以外の拡張機能であり、「ANSI文字列」と呼ばれています。これらの事実を回答に組み込んだ。このような文字列は、最新のBourne互換シェルで機能します。bash、dash、ksh(AT&TとPDの両方)、およびzshはすべてこれらをサポートしています。
マークリード


3

以下は私のために働いたものです-

QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"

2
TBL_HDFS_DIR_PATHがユーザー提供の入力ではないことを願っています。これにより、素晴らしいSQLインジェクションが可能になります...
domenukk

1
QUOTE別の変数を入れることは完全に不必要です。二重引用符内の単一引用符は、単なる通常の文字です。(また、プライベート変数に大文字を使用しないでください。)
tripleee '23年

2

編集:(問題のコメントに従って:)

それ以来、私はこれについて調査してきました。私は幸運にもレポを敷設することができました。それでも、コマンドを強制的に一重引用符で囲む必要があるかどうかは明確ではありません。私はレポ構文を調べましたが、あなたがする必要はないと思います。コマンドを二重引用符で囲み、二重引用符をエスケープする場合は、内部で必要な一重引用符と二重引用符を使用できます。


ご協力いただきありがとうございます。
Rachit

2

printfを使用するだけ

の代わりに

repo forall -c '....$variable'

printfを使用して、変数トークンを拡張変数に置き換えます。

例えば:

template='.... %s'

repo forall -c $(printf "${template}" "${variable}")

これは壊れています。printfここでは実際には何も購入しません。また、コマンドの置換を引用しないと、新しい引用の問題が発生します。
tripleee

0

変数には一重引用符を含めることができます。

myvar=\'....$variable\'

repo forall -c $myvar

それらはできますが、これは正しい方法ではありません- "$myvar"二重引用符で囲む必要があります。
tripleee

-2

これはうまくいきますか?

eval repo forall -c '....$variable'

8
それはあなたのためですか?この質問は5年前のものであり、すでにいくつかの回答があり、受け入れられた質問も含まれています...
ニコ・ハーセ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.