bash / POSIXシェルで変数を引用するのを忘れた場合のセキュリティへの影響


206

しばらくunix.stackexchange.comをフォローしている場合はecho $var、Bourne / POSIXシェル(zshは例外)のリストコンテキスト(のように)で変数を引用符で囲まないでおくと、非常に特別な意味があり、正当な理由がない限り、実行しないでください。

ここでいくつかのQ&Aで詳細に議論されています(例:シェルスクリプトが空白または他の特殊文字でチョークするのはなぜですか?二重引用符が必要なのはいつですか?シェル変数の展開とグロブと分割の効果引用符付き引用符なしの文字列展開

これは、70年代後半のBourneシェルの最初のリリース以降のケースであり、Kornシェル(David Kornの最大の後悔の 1つ(質問#7))によって変更されていないかbash、Kornシェルをほとんどコピーしたものです。 POSIX / Unixでどのように指定されているか。

現在、ここにはまだ多くの回答があり、変数が引用されていない公開されているシェルコードもときどき公開されています。あなたは今までに人々が学んだと思っていただろう。

私の経験では、変数を引用するのを省略した人が主に3種類あります。

  • 初心者。確かにそれは完全に直感的でない構文であるため、それらは言い訳することができます。そして、彼らを教育することは、このサイトでの私たちの役割です。

  • 忘れっぽい人。

  • 繰り返しハンマーを打っても納得しない人、Bourneシェルの作者が私たちのすべての変数を引用するつもりはなかったと思う

この種の行動に関連するリスクを明らかにすれば、彼らを説得できるかもしれません。

変数の引用を忘れた場合に起こりうる最悪の事態は何ですか。それは本当にそれが悪いですか?

ここでどのような脆弱性について話しているのですか?

どのような状況で問題になりますか?


8
BashPitfallsは私が思うに好きなものです。
pawel7318 14

私が書いたこの記事からのバックリンク、記事をありがとう
ミラビロス14

5
4番目のグループを追加することをお勧めします。過剰な引用を何度も繰り返して頭をhit られた人、おそらく3番目のグループのメンバーが他の人にフラストレーションを感じている(被害者がいじめっ子になっている)。もちろん悲しいことは、4番目のグループの人々は、それが最も重要なときに物事を引用することに失敗するかもしれないということです。
ack

回答:


201

前文

まず、問題に対処する適切な方法ではないと言います。「そうでなければ、刑務所に行くから人を殺してはいけない」と言っているようなものです

同様に、セキュリティの脆弱性を導入しているため、変数を引用しません。変数を引用するのは間違っていないためです(しかし、刑務所への恐怖が助けになれば、なぜですか)。

電車に乗ったばかりの人のための小さな要約。

ほとんどのシェルでは、変数展開を引用符で囲まずに残します(ただし、この(およびこの回答の残りの部分)はコマンド置換(`...`または$(...))および算術展開($((...))または$[...])にも適用されます)は非常に特別な意味を持ちます。それを記述する最良の方法は、ある種の暗黙的なsplit + glob演算子を呼び出すようなものです¹

cmd $var

別の言語では、次のように記述されます。

cmd(glob(split($var)))

$var最初に$IFS特別なパラメーターを含む複雑なルールに従って単語のリストに分割され(分割部分)、その分割の結果として生じる各単語は、それと一致するファイルのリスト(glob部分)に展開されるパターンと見なされます。

場合の例としては、$var含まれ*.txt,/var/*.xml及び$IFS 含まれ,cmd最初の1があること、引数の数で呼び出されることになるcmdと、次のものがあることtxt 、現在のディレクトリ内のファイルとxmlのファイル/var

cmd2つのリテラル引数cmd とだけで呼び出したい場合は*.txt,/var/*.xml、次のように記述します。

cmd "$var"

あなたのもう一つのより親しみやすい言語であるでしょう:

cmd($var)

シェルの脆弱性とはどういう意味ですか?

結局のところ、シェルスクリプトはセキュリティが重要なコンテキストで使用されるべきではないことは、time明期から知られています。確かに、変数を引用符で囲まずに残しておくのはバグですが、それほど害はありませんよね?

誰もがシェルスクリプトをWeb CGIに使用してはならない、またはありがたいことにほとんどのシステムが最近ではsetuid / setgidシェルスクリプトを許可しないと言っているにもかかわらず、shellshock( )2014年9月中の見出しはどこ彼らはおそらくないはずシェルはまだ広く使用されていることである明らかにした:CGIの中で、DHCPクライアントフックスクリプトでは、呼び出されたのsudoersコマンド、中(ない場合など)のsetuidコマンド...

時々知らずに。たとえばsystem('cmd $PATH_INFO')php/ perl/ pythonCGIスクリプトでは、そのコマンドラインを解釈するためにシェルが呼び出されます(cmdそれ自体がシェルスクリプトであり、作成者がCGIから呼び出されることを予期していなかったという事実は言うまでもありません)。

特権エスカレーションへの道があるとき、つまり誰か(彼を攻撃者と呼びましょう)が彼が意図していないことをすることができるとき、あなたは脆弱性を持っています。

常に、攻撃者がデータを提供することを意味ます。そのデータは、ほとんどの場合、バグのために、誤って本来すべきでないことを行う特権ユーザー/プロセスによって処理されています。

基本的に、バグのあるコードが攻撃者の制御下でデータを処理するときに問題が発生します。

現在、そのデータがどこから来たのかが常に明らかであるとは限らず、コードが信頼できないデータを処理できるようになるかどうかを判断するのは難しい場合があります。

変数に関する限り、CGIスクリプトの場合、データはCGI GET / POSTパラメーターと、Cookie、パス、ホストなどのパラメーターです。

setuidスクリプト(別のユーザーによって呼び出されたときに1人のユーザーとして実行される)の場合、それは引数または環境変数です。

別の非常に一般的なベクトルはファイル名です。ディレクトリからファイルリストを取得している場合、攻撃者によってファイルがそこに植えられている可能性があります。

(内のファイルを処理する際にその点でも、対話型シェルのプロンプトで、あなたが影響を受けます/tmp~/tmp 例えば)。

でも~/.bashrc(例えば、脆弱である可能bashオーバー呼び出されたときにそれを解釈するssh実行するためForcedCommand のようにgit、クライアントの制御下で、いくつかの変数を使用してサーバーの展開を)。

現在、信頼できないデータを処理するためにスクリプトを直接呼び出すことはできませんが、別のコマンドによって呼び出すことができます。または、間違ったコードをコピーするスクリプトにコピーアンドペーストする場合があります(3年後に同僚または同僚の1人によって)。コードのコピーがどこで終わるかわからないので、特に重要なのはQ&Aサイトの回答です。

ビジネスまで。どれくらい悪い?

変数(またはコマンド置換)を引用符で囲まずに残すことは、シェルコードに関連するセキュリティ脆弱性の最も多い原因です。一部には、これらのバグが脆弱性に変換​​されることが多いだけでなく、引用符で囲まれていない変数を見ることが非常に一般的だからです。

実際、シェルコードの脆弱性を探すとき、最初にすることは、引用符で囲まれていない変数を探すことです。簡単に見つけることができ、多くの場合、適切な候補であり、一般に攻撃者が制御するデータに簡単に戻ります。

引用符で囲まれていない変数が脆弱性になる可能性のある方法は無限にあります。ここでは、いくつかの一般的な傾向を説明します。

情報開示

ほとんどの人は、分割された部分のために、引用されていない変数に関連するバグにぶつかります(たとえば、最近ではファイルの名前にスペースがあり、スペースがIFSのデフォルト値になっていることが一般的です)。多くの人々はグロブ部分を見落とし ます。グロブ部分は、少なくともとして危険である スプリット一部。

サニタイズされていない外部入力に対して行われるグロビングは、攻撃者が任意のディレクトリのコンテンツを読み取らせることを意味ます。

に:

echo You entered: $unsanitised_external_input

$unsanitised_external_input含む場合/*、それは攻撃者がのコンテンツを見ることができることを意味/ます。大きな問題ではない。これは、しかし、より興味深いとなって/home/*おり、マシン上であなたのユーザー名のリストを与え/tmp/*/home/*/.forward他の危険な慣行でのヒント、のために/etc/rc*/*有効なサービスのために...必要は個別に名前を付けないように。の値は/* /*/* /*/*/*...、ファイルシステム全体をリストします。

サービス拒否の脆弱性。

前のケースを少し取りすぎて、DoSが発生しました。

実際、サニタイズされていない入力を持つリストコンテキスト内の引用符で囲まれていない変数は、少なくとも DoSの脆弱性です。

熟練したシェルスクリプト作成者でさえ、次のようなものを引用することを忘れます。

#! /bin/sh -
: ${QUERYSTRING=$1}

:no-opコマンドです。何が間違っている可能性がありますか?

設定さ$1れてい$QUERYSTRINGない場合$QUERYSTRINGに割り当てることを意味します。これは、コマンドラインからもCGIスクリプトを呼び出し可能にする簡単な方法です。

しかし、それ$QUERYSTRINGはまだ拡張されており、引用されていないため、split + globオペレーターが呼び出されます。

現在、拡張するのに特に高価ないくつかのグロブがあります。/*/*/*/*一つは、それは4つのレベルまでのリストディレクトリの下の手段として十分に悪いです。ディスクとCPUのアクティビティに加えて、それは数万のファイルパス(ここでは最小のサーバーVMで40k、10kのディレクトリ)を格納することを意味します。

今で/*/*/*/*/../../../../*/*/*/*は40k x 10kを意味し /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*、最も強力なマシンでさえひざまずくのに十分です。

自分で試してみてください(ただし、マシンがクラッシュまたはハングするように準備してください):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

もちろん、コードが次の場合:

echo $QUERYSTRING > /some/file

その後、ディスクをいっぱいにすることができます。

shell cgiまたはbash cgiまたはksh cgiでGoogle検索を実行するだけで、シェルで CGIを作成する方法を示すページがいくつか見つかります。パラメータを処理するものの半分が脆弱であることに注意してください。

David Korn自身のものでさえ 脆弱です(Cookie処理を見てください)。

任意のコード実行の脆弱性まで

攻撃者がコマンドを実行できる場合、攻撃者が実行できることには制限がないため、任意のコードの実行は最悪のタイプの脆弱性です。

それは一般的にそれらにつながる分割された部分です。その分割により、1つだけが予想される場合にコマンドに渡されるいくつかの引数が生成されます。それらの最初のものは予想されるコンテキストで使用されますが、他のコンテキストは異なるコンテキストで使用されるため、異なる解釈の可能性があります。例を挙げてみましょう:

awk -v foo=$external_input '$2 == foo'

ここでは、$external_inputシェル変数の内容を変数に割り当てることが意図され ていましたfoo awk

今:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

の分割の結果として生じる2番目の単語$external_input は割り当てられませんfooが、awkコードと見なされます(ここでは、任意のコマンドを実行します:)uname

それは他のコマンドを実行できるコマンドのために特に問題だ(awkenvsed(GNU 1)、 perlfind...)、特に(引数の後にオプションを受け入れる)GNUバリアントと。時には、あなたは次のように他人を実行することができるようにするためのコマンドを疑うないだろうkshbashあるいはzsh年代[printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

という名前のディレクトリを作成するとx -o yes、評価対象の完全に異なる条件式であるため、テストはポジティブになります。

さらに悪いことに、x -a a[0$(uname>&2)] -gt 1少なくともすべてのksh実装(sh ほとんどの商用Unicesと一部のBSDを含む)でを呼び出すファイルを作成すると、uname これらのシェルが[コマンドの数値比較演算子で算術評価を実行するため実行されます。

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

同じbashようにファイル名のx -a -v a[0$(uname>&2)]

もちろん、攻撃者が任意の実行を取得できない場合、攻撃者はより小さなダメージで解決する可能性があります(これにより、任意の実行が可能になります)。ファイルを書き込んだり、権限や所有権を変更したり、主効果や副作用を引き起こす可能性のあるコマンドが悪用される可能性があります。

あらゆる種類のことをファイル名で行うことができます。

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

そして、あなたは..(再帰的にGNUでchmod)書き込み可能に することになります。

公に書き込み可能な領域でファイルの自動処理を行うスクリプト/tmpは、非常に慎重に作成する必要があります。

どう? [ $# -gt 1 ]

それは私が腹立たしいと思うものです。特定の展開に問題があるのではないかと疑問に思って、引用符を省略できるかどうかを判断する人もいます。

言っているようなものです。ちょっと、それは$#split + glob演算子の対象にならないように見えます。シェルにsplit + glob itを要求しましょう。または、バグがヒットする可能性が低いという理由だけで、間違ったコードを書きましょう

さて、それはどれほどありそうもないのでしょうか?OK $#(または$!$?または算術置換)には数字(または-一部)のみが含まれている可能性があるため、グロブ部分はありません。以下のために、分割部分がが何かをするために、我々が必要とするすべては、ある$IFS数字が含まれている(またはします-)。

一部のシェルで$IFSは、環境から継承される場合がありますが、環境が安全でない場合は、とにかくゲームオーバーです。

次に、次のような関数を記述した場合:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

つまり、関数の動作は、呼び出されるコンテキストに依存するということです。または、言い換えれば、$IFS それへの入力の1つになります。厳密に言えば、関数のAPIドキュメントを作成するときは、次のようになります。

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

また、関数を呼び出すコードで$IFSは、数字が含まれていないことを確認する必要があります。それは、これらの2つの二重引用符を入力する気がなかったからです。

さて、その[ $# -eq 2 ]バグが脆弱性になるには、その値が攻撃者の$IFS制御下になるために何らかの形で必要になるでしょう。おそらく、攻撃者が別のバグを悪用しない限り通常は発生しません。

しかし、それは前代未聞ではありません。一般的なケースは、算術式で使用する前にデータをサニタイズすることを忘れた場合です。いくつかのシェルで任意のコードを実行できることは既に見ましたが、すべてのシェルで、攻撃者は任意の変数に整数値を与えることができ ます。

例えば:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

また、$1with value を使用すると(IFS=-1234567890)、算術評価にIFSの設定の副作用があり、次の[ コマンドが失敗します。これは、引数多すぎる場合のチェックがバイパスされることを意味します。

どのようなときについてのスプリット+グロブ演算子が呼び出されていませんか?

変数やその他の展開の周りに引用符が必要な別のケースがあります。それがパターンとして使用されるときです。

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

$a$bが同じかどうか(を除くzsh)をテストせず、if $aがのパターンに一致する場合$b。そして、あなたが引用する必要があり$bますが、文字列として比較したい場合は(同じもので"${a#$b}""${a%$b}"またはそれがパターンとして解釈すべきではないかどう引用符で囲む必要があります)。"${a##*$b*}"$b

それが意味するのは、異なる[[ $a = $b ]]場合(たとえば、is とisの場合)にtrueを返す場合もあれば、同一の場合(たとえば、両方とandの場合)にfalseを返す場合もあります。$a$b$aanything$b*$a$b[a]

これにより、セキュリティ上の脆弱性が発生しますか?はい、すべてのバグと同じです。ここで、攻撃者はスクリプトの論理コードフローを変更したり、スクリプトが行っている仮定を破ったりすることができます。たとえば、次のようなコードで:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

攻撃者はを渡すことでチェックをバイパスでき'[a]' '[a]'ます。

さて、そのパターンマッチングもsplit + glob演算子も適用されない場合、変数を引用符で囲まないままにしておく危険性は何ですか?

私が書くことを認めなければなりません:

a=$b
case $a in...

そこでは、引用は害を与えませんが、厳密に必要というわけではありません。

ただし、そのような場合(たとえばQ&Aの回答など)で引用符を省略することの1つの副作用は、初心者に間違ったメッセージを送信する可能性があることです

たとえば、もしa=$bOKなら、それexport a=$bも同様だと考え始めるかもしれませんexportリストコンテキストのようにコマンドの引数にあるので、多くのシェルにはありませんenv a=$b

どうzsh

zshそれらのデザインの厄介さのほとんどを修正しました。zshしたい場合(SH / kshのエミュレーションモードでない少なくともとき)、分割、またはグロブ、またはパターンマッチングを、あなたが明示的に要求する必要があります:$=var分割すると、$~varグロブするかのように処理されるべき変数の内容のためにパターン。

ただし、引用符で囲まれていないコマンド置換では、(グロブではなく)分割は暗黙的に行われます(のようにecho $(cmd))。

また、変数を引用しない場合の望ましくない副作用として、空の削除がありますzsh行動は、あなたが(と完全グロブ無効にすることにより、他のシェルで達成できるものに似ているset -f)と分割(とIFS='')。それでも、

cmd $var

split + globはありませんが、$var空の場合、1つの空の引数を受け取る代わりに、cmd引数をまったく受け取りません。

それはバグを引き起こす可能性があります(明らかな[ -n $var ])。それはスクリプトの期待と仮定を破り、脆弱性を引き起こす可能性がありますが、私は今のところあまりにもフェッチされていない例を思い付くことはできません)。

split + glob演算子必要な場合どうですか?

はい、それは通常、変数を引用符で囲まずに残したい場合です。ただし、使用する前に、スプリット演算子とグロブ演算子を正しく調整する必要があります。glob部分ではなく分割部分だけが必要な場合(ほとんどの場合)、グロビング(/ )を無効にして修正する必要があります。そうしないと、脆弱性も発生します(上記のDavid KornのCGIの例のように)。set -o noglobset -f$IFS

結論

要するに、シェルで変数(またはコマンド置換や算術展開)を引用符で囲まないままにすることは、特に間違ったコンテキストで行われた場合、非常に危険な場合があります。

それが悪い習慣とみなされる理由の一つです。

これまで読んでくれてありがとう。それがあなたの頭の上に行く場合、心配しないでください。誰もがコードを書く方法のすべての意味を理解することを期待することはできません。その ため、優れたプラクティスの推奨事項があるため、必ずしも理由を理解しなくてもそれらに従うことができます。

(そして、それがまだ明らかでない場合、シェルでセキュリティに敏感なコードを書くことを避けてください)。

そして、このサイトの回答で変数を引用してください!


¹ ksh93およびpdksh派生物では、グロビングが無効になっていない限り、ブレース展開も実行されます(ksh93ksh93u +までのバージョンの場合、braceexpandオプションが無効になっている場合でも)。


ことに注意してください[[、比較の唯一のRHSを引用符で囲む必要があります:if [[ $1 = "$2" ]]; then
mirabilos

2
@mirabilos、はい、しかしLHSはない必要ない引用されたことを、私たちは意識的な決断取るようにしている場合は(そこを引用しない何の説得力のある理由がないデフォルトで引用することが行うには、最も賢明なものだそうですとしては、 )。また、それは注意してください[[ $* = "$var" ]]と同じではありません[[ "$*" = "$var" ]]の最初の文字があれば$IFSとスペースではありませんbash(ともmkshあれば$IFS、その場合には、私はよく分からないのに空である$*)に等しいですが、私はバグを上げる必要がありますか?)。
ステファンシャゼル

1
ええ、デフォルトで引用できます。すぐにフィールド分割に関するバグを解消してください。これを再評価する前に、まず(あなたや他の人から)知っているものを修正する必要があります。
ミラビロス14

2
@Barmarは、あなたが意図していると仮定するfoo='bar; rm *'と、そうではありませんが、情報開示としてカウントされる可能性のある現在のディレクトリのコンテンツをリストします。print $fooin ksh93(その欠点のいくつかに対処printする代替品はどこにありechoますか)にはコードインジェクションの脆弱性があります(たとえばfoo='-f%.0d z[0$(uname>&2)]')(実際に必要print -r -- "$foo"ですecho "$foo"
ステファンシャゼル14

3
私はbashの専門家ではありませんが、10年以上コーディングしています。引用符を頻繁に使用しましたが、主に埋め込みブランクを処理するために使用しました。今、私はそれらをもっとたくさん使います!誰かがこの答えを拡張して、すべての細かい点を少しでも簡単に吸収できるようにするとよいでしょう。私はそれをたくさん手に入れましたが、私もたくさん見逃しました。すでに長い記事ですが、ここにはもっと学ぶべきことがたくさんあることを知っています。ありがとう!
ジョー14

34

[触発され、この答えによってCAS。]

しかし、どうすれば…?

しかし、スクリプトが変数を使用する前に既知の値に設定したらどうなるでしょうか?特に、2つ以上の可能な値のいずれかに変数を設定し(ただし、常に既知の値に設定する)、値にスペースまたはグロブ文字が含まれていない場合はどうでしょうか?その場合、引用符なしで使用するのは安全ではありませんか?

そして、可能な値の1つが空の文字列で、「空の削除」に依存している場合はどうなりますか?つまり、変数に空の文字列が含まれている場合、コマンドで空の文字列を取得したくありません。何も得たくない。例えば、

some_conditionの場合
それから
    ignorecase = "-i"
他に
    ignorecase = ""
fi
                                        #上記のコマンドの引用符厳密には必要ないことに注意してください。
grep $ ignorecase   other_ grep _args

私には言えません。が空の文字列の場合、失敗します。grep "$ignorecase" other_grep_args$ignorecase

応答:

他の回答で説明したようIFSに、-またはが含まれている場合、これはまだ失敗しiます。IFS変数に文字が含まれていないことを確認した場合(および変数にグロブ文字が含まれていないことが確実な場合)、これはおそらく安全です。

しかし、より安全な方法があります(それはいくぶんくて直感的ではありませんが):use ${ignorecase:+"$ignorecase"}です。POSIXシェルコマンド言語仕様、下の  2.6.2パラメータ展開

${parameter:+[word]}

    代替値を使用します。parameterが未設定またはヌルの  場合、ヌルが置換されます。そうでない場合、word (またはword省略された場合は空の文字列)の展開が置き換えられます。

ここでのコツは、そのignorecaseように、parameter"$ignorecase"として使用していることwordです。だから、${ignorecase:+"$ignorecase"}手段

場合$ignorecase(すなわち、空)未設定またはヌルである、ヌル(すなわち、引用符で囲まれていない何も)に置換しなければなりません。それ以外の場合は、の拡張"$ignorecase"が置き換えられます。

これにより、目的の場所に移動できます。変数が空の文字列に設定されている場合、変数は「削除」されます(この複雑な式全体は空文字列ではなく、何も評価されません)。 -空の値、引用されたその値を取得します。


しかし、どうすれば…?

しかし、単語に分割したい/必要な変数がある場合はどうなりますか?(これは最初の場合と同様です。私のスクリプトは変数を設定しており、グロブ文字は含まれていないと確信しています。ただし、スペースが含まれている可能性があります。
PS 。空の削除がまだ必要です。)

例えば、

some_conditionの場合
それから
    criteria = "-type f"
他に
    criteria = ""
fisome_other_conditionの 
場合
それから
    criteria = "$ criteria -mtime +42"
fi
「$ start_directory」を見つける$ criteria   other_ find _args

応答:

これはを使用する場合だと思うかもしれませんeval  番号!evalここでの  使用を考える誘惑に抵抗してください。

繰り返しますがIFS、変数に文字が含まれていないことを確認した場合(尊重したいスペースを除く)、変数にグロブ文字が含まれていないことが確実な場合、上記はおそらく安全。

ただし、bash(またはksh、zsh、yash)を使用している場合は、より安全な方法があります。配列を使用します。

some_conditionの場合
それから
    criteria =(-type f)# `criteria =("-type "" f ")`と言えますが、それは本当に不要です。
他に
    criteria =()#このコマンドには引用符を使用しないでください!
fisome_other_conditionの 
場合
それから
    criteria + =(-mtime +42)#注: `=`ではなく、配列に追加(追加)するための` + =`。
fi
find "$ start_directory" "$ {criteria [@]}"   other_ find _args

bashの(1)

配列の要素は、を使用して参照できます。なら... であるか  、言葉は、すべてのメンバーに展開されます。これらの添え字は、単語が二重引用符で囲まれている場合にのみ異なります。単語が二重引用符で囲まれている場合、… は各要素を個別の単語に展開します。${name[subscript]}subscript@*name${name[@]}name

だから"${criteria[@]}"のゼロ、1,2、または4つの要素(上記の例では)に展開criteriaアレイは、各引用。特に、条件  sのどちらもtrueでcriteriaない場合、配列には内容がなく(criteria=()ステートメントで設定)、何も"${criteria[@]}"評価されません (不便な空の文字列でさえありません)。


これは、複数の単語を処理するときに特に面白くて複雑になります。単語の一部は動的(ユーザー)入力であり、事前に知らず、スペースまたは他の特殊文字を含む場合があります。考慮してください:

printf "検索するファイル名を入力してください:"
fnameを読む
if ["$ fname"!= ""]
それから
    criteria + =(-name "$ fname")
fi

は使用されるたびに$fname引用されることに注意してください。ユーザーは以下のようなものを入力しても、これは動作しますfoo barfoo*。  またはに"${criteria[@]}"評価されます。(配列の各要素は引用符で囲まれていることに注意してください。)-name "foo bar"-name "foo*"

配列はすべてのPOSIXシェルで機能するわけではありません。配列はksh / bash / zsh / yash-ismです。を除いて... すべてのシェルがサポートする配列が1つあります:引数リスト、別名"$@"。呼び出された引数リストで完了したら(たとえば、すべての「位置パラメータ」(引数)を変数にコピーしたか、処理した場合)、引数リストを配列として使用できます。

some_conditionの場合
それから
    set--type f# `set-" -type "" f "`と言えますが、実際には不要です。
他に
    セットする  - 
fisome_other_conditionの 
場合
それから
    セット-"$ @" -mtime +42
fi
#同様に:set-"$ @" -name "$ fname"
find "$ start_directory" "$ @"   other_ find _args

"$@"(歴史的に、最初に来た、)構築物は、同じ意味を持っているあなたが入力したかのように、別々の単語に各引数(つまり、引数リストの各要素)を展開します- 。"${name[@]}""$1" "$2" "$3" …

抜粋POSIXシェルコマンド言語仕様の下で、2.5.2特殊パラメータ

@

    1から始まる位置パラメータに展開し、最初に設定された位置パラメータごとに1つのフィールドを生成します。…、初期フィールドは個別のフィールドとして保持されます、…。位置パラメータがない@場合@は、二重引用符内にある場合でも、の展開によりゼロフィールドが生成されます。…

全文はやや不可解です。重要な点は、"$@"位置パラメータがない場合にゼロフィールドを生成することを指定することです。歴史的注記:"$@"1979年にBourneシェル(bashの前身)で最初に導入された"$@"とき、位置パラメータがない場合に単一の空の文字列に置き換えられるバグがありました。見ない何を${1+"$@"}シェルスクリプトに意味し、それはどのように異なっていませんか"$@"、  The Traditional Bourne Shell Family、  何を${1+"$@"}意味するのか...そしてそれはどこで必要ですか?、および"$@"${1+"$@"}


配列も最初の状況に役立ちます:

some_conditionの場合
それから
    ignorecase =(-i)#「ignorecase =( "-i")」と言うこともできますが、実際には不要です。
他に
    ignorecase =()#このコマンドには引用符を使用しないでください!
fi
grep "$ {ignorecase [@]}"   other_ grep _args

____________________

PS(csh)

これは言うまでもありませんが、csh、tcshなど、ここに新しい人のために、Bourne / POSIXシェルではありません。彼らは全く異なる家族です。異なる色の馬。他のすべての球技。猫の異なる品種。別の羽の鳥。そして、特に、ワームの異なる缶。

このページで述べたことの一部は、cshに適用されます。など:正当な理由がない限り、すべての変数を引用することをお勧めします。しかし、cshでは、すべての変数は配列です。ほとんどすべての変数は1つの要素のみの配列であり、Bourne / POSIXシェルの通常のシェル変数とほぼ同じように動作します。そして、構文はひどく異なります(そして私はひどく意味します)。したがって、ここではcsh-familyシェルについてこれ以上説明しません。


1
それを中に注意してくださいcsh、あなたはむしろ使用したい$var:qよりも、"$var"後者は改行文字が含まれている変数に対しては動作しませんよう(あるいはあなたが個別に配列の要素を引用するのではなく、単一の引数にスペースでそれらを結合する場合)。
ステファンシャゼラス

bashでは、でbitrate="--bitrate 320"動作し${bitrate:+$bitrate}、コマンドを中断するため動作しbitrate=--bitrate 128ません${bitrate:+"$bitrate"}${variable:+$variable}noで使用しても安全"ですか?
フリード

@Freedo:あなたのコメントは不明瞭です。あなたが私がまだ言っていないことを知りたいと思うことは特にはっきりしない。2番目の「But what if」という見出しを参照してください。「しかし、単語に分割したい/必要な変数がある場合はどうなりますか?」それはあなたの状況です。そこでアドバイスに従うべきです。しかし、(たとえば、bash、ksh、zsh、またはyash以外のシェルを使用していて、他の目的で使用している  $@ため)、または単に他の理由で配列の使用を拒否する場合、 「他の回答で説明したように」。…(続き)
Gマン

(使用していない(続き)...あなたの提案${variable:+$variable}なしでは")IFSが含まれている場合は失敗します  -、  023abeirまたは  t
Gマン

私の変数にはa、e、b、iの2文字と3文字がありますが、それでもうまく動作します${bitrate:+$bitrate}
Freedo

11

私はステファンの答えに懐疑的でしたが、悪用することは可能$#です:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

または$ ?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

これらは不自然な例ですが、可能性はあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.