しかし、もし…?

しかし、もし…?

unix.stackexchange.comをしばらくフォローしている場合は、echo $varBourne / POSIXシェル(zshは例外)などのリストコンテキストに引用符なしの変数を残すことが非常に特別な意味を持つことに注意する必要があります。妥当な理由があったり、そうしてはいけません。

これについては、ここの多くのQ&Aで詳しく説明します(例:スペースやその他の特殊文字が原因でシェルスクリプトが停止するのはなぜですか?いつ二重引用符が必要ですか?シェル変数の拡張とglobと分割の影響引用符付きの文字列と引用符なしの文字列の拡張)

これは70年代後半のBourneシェルが最初にリリースされて以来、Kornシェル(その1つ)は変更されていません。David Kornの最大の後悔(問題#7))またはbash主にPOSIX / Unixによって指定されたKornシェルを複製します。

今、私たちはまだ多くの答えを見ることができ、時には変数を引用しない公に公開されているシェルコードも見ることができます。今頃なら、人々がそれを学んだと思います。

私の経験によれば、参照変数を省略する3つの主要なタイプの人がいます。

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

  • 物忘れがひどい人。

  • 何度も当たってもまだ確信がない人はこう思います。もちろん、Bourneシェルの作者は、私たちがすべての変数を参照したくありません。

そのような行動に関連するリスクをさらすと、おそらくそれらを説得することができます。

変数を引用するのを忘れた場合に起こりうる最悪の状況は何ですか?本物だそれ悪い?

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

どのような状況で問題が発生する可能性がありますか?

答え1

ヘッダー

まず、これは問題を解決する正しい方法ではないことをお伝えしたいと思います。 「と言うのとちょっと似ています。人を殺してはいけない。そうでなければ刑務所に行きます。」。

同様に、変数を引用するとセキュリティホールが生じるため、変数を引用しません。そうしないのが間違っているので、変数を引用しています(しかし、刑務所への恐怖が役に立つとしたらどうしますか)。

今電車に乗りたい人のための簡単な要約です。

ほとんどのシェルでは、引用符のない変数拡張(およびこの答えの残りの部分はコマンド置換(`...`OR $(...))および算術拡張($((...))OR $[...])にも適用されますが)は非常に特別な意味を持ちます。これを説明する最善の方法は、一種の暗黙の呼び出しと同じです。分割+グローバルオペレータ 1.

cmd $var

他の言語では、次のように書かれています。

cmd(glob(split($var)))

$varまず、関連する特殊パラメータに従って$IFS分ける部分)、その分割による各単語が考慮されます。模様一致するファイルのリストに展開されます(全体的な状況部分)。

たとえば、$varContains*.txt,/var/*.xml$IFS Contains,cmd複数のパラメータで呼び出され、最初のパラメータは現在のディレクトリのファイルで、次のcmdパラメータはです。txtxml/var

cmd2つのリテラルパラメータとを使用してcmd 呼び出したい場合は、次のように*.txt,/var/*.xml書くことができます。

cmd "$var"

以下はもう少しおなじみの別の言語です。

cmd($var)

私たちが言ったことシェルの脆弱性

結局、セキュリティに敏感な状況ではシェルスクリプトを使用してはならないことが最初から知られていました。もちろんです。変数を引用符で囲まないままにしておくのは間違いです。しかし、それほど有害ではありません。そうですか?

まあ、一部の人はWeb CGIにシェルスクリプトを使用してはいけません。年9月)では、シェルが使用されてはならない場所(CGI、DHCPクライアントフックスクリプト、sudoersコマンド、呼び出しなど)で依然として広く使用されていることを明らかにしました。渡す(そうでなければ〜のように)setuidコマンド...

時には無意識に。たとえば、// CGIスクリプトsystem('cmd $PATH_INFO') では、シェルは実際にコマンドラインを解釈するために呼び出されます。 (シェルスクリプト自体がシェルスクリプトである可能性があり、作成者がCGIから呼び出されるとはまったく予期しなかった可能性があります。)phpperlpythoncmd

権限上昇のための道があるとき誰か(彼を呼ぼう)攻撃者)彼はしないでください。

常に意味攻撃者ほとんどの場合、バグが原因で実行しないことを誤って実行した特権ユーザー/プロセスによって処理されたデータを提供します。

既定では、障害のあるコードが次の制御下でデータを処理すると問題が発生します。攻撃者

今は必ずしも明確ではありません。データこれは、発生する可能性があり、コードが信頼できないデータを処理するかどうかを言うのが難しいことがよくあります。

変数に関する限り、CGIスクリプトの場合、データはCookie、パス、ホスト...およびその他のパラメータだけでなく、CGI GET / POSTパラメータであることは明らかです。

setuidスクリプト(他のユーザーが呼び出すときに1人のユーザーとして実行)の場合、これはパラメーターまたは環境変数です。

もう一つの非常に一般的なベクトルはファイル名です。ディレクトリからファイルのリストをインポートすると、ファイルがそのディレクトリに植えられた可能性があります。攻撃者

これに関連して、対話型シェルプロンプト(ファイル操作中など)/tmpでも脆弱になる可能性があります~/tmp

a も~/.bashrc脆弱である可能性があります (たとえば、一部の変数がクライアント制御下にあるサーバーのデプロイなどのタスクを実行するために呼び出す場合はbash説明可能)。sshForcedCommandgit

これで、信頼できないデータを処理するためにスクリプトを直接呼び出すことはできませんが、他のコマンドで呼び出すことができます。あるいは、間違ったコードをコピーしてスクリプトに貼り付けることもできます(そして3年後にあなたや同僚がコピーして貼り付けることができます)。特に見落とされやすい部分の一つは批判的コードのコピーがどこにあるかわからないので、Q&Aサイトの回答にあります。

家に近づけばどれくらいいいの?

引用符のない変数(またはコマンドの置き換え)は、シェルコードに関連するセキュリティの脆弱性の最大の原因です。これは、部分的には、これらのエラーがしばしば脆弱性として解釈されるだけでなく、引用されていない変数を見るのが一般的であるためです。

実際に、シェルコードの脆弱性を見つけるときに最初にすべきことは、引用符のない変数を見つけることです。これは見つけやすく、一般的に良い候補であり、通常、攻撃者が制御しているデータを簡単に追跡できます。

引用しない変数は、さまざまな方法で脆弱性になる可能性があります。ここでは、いくつかの一般的な傾向をお知らせします。

情報開示

ほとんどの人は、引用されていない変数に関連するエラーを経験します。分ける(たとえば、ファイル名にスペースが含まれるのが一般的で、スペースはIFSのデフォルトです。)多くの人はそれを無視します。 全体的な状況部分。これ全体的な状況部分的には少なくとも関連があります。 分ける部分。

整理されていない外部入力方式のワイルドカードの完成攻撃者すべてのディレクトリの内容を読むことができます。

存在する:

echo You entered: $unsanitised_external_input

$unsanitised_external_input含まれている場合は、/*次のことを意味します。攻撃者何を見ることができます/。あまりありません。ただし、個別に名前を付けることなく、他の危険な行動を提案し、サービスを有効にするためのシステム /home/*のユーザー名のリストを提供するので、より興味深いものになります。値はファイルシステム全体のみを一覧表示します。/tmp/*/home/*/.forward/etc/rc*/*/* /*/* /*/*/*...

サービス拒否の脆弱性。

前の例はあまりにも遠くに進み、DoSを受けました。

実際には、リストコンテキストで引用されていない変数とクリーンアップされていない入力は、少なくともDoSの脆弱性。

プロのシェルスクリプトでさえ、しばしば以下を引用することを忘れてしまいます。

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

:動作しないコマンドです。どのような問題が発生する可能性がありますか?

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

しかし、$QUERYSTRINGこれは参照されていないため、まだ拡張であり、分割+グローバル交換員に電話してください。

今、いくつかのグローブは、拡張するために特に高価です。これは、/*/*/*/*最大4つのレベルのディレクトリを一覧表示することを意味するので、それは非常に良いことではありません。これは、ディスクおよびCPUアクティビティに加えて、数万のファイルパス(ここでは10,000個のディレクトリを持つ最小のサーバーVMの場合は40,000個)を格納することを意味します。

これ/*/*/*/*/../../../../*/*/*/*は40k x 10kを意味し、 /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*最も強力な機械はひざまずくのに十分です。

自分で試してみてください。ただし、コンピュータがクラッシュしたり停止したりする可能性があることに備えてください。

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

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

echo $QUERYSTRING > /some/file

その後、ディスクを埋めることができます。

ただGoogle検索をしてくださいシェルまたはバッシュCGIまたはクシュケジ、シェルでCGIを作成する方法を示すページを見つけることができます。処理パラメータの半分が脆弱であることに注意してください。

でもデビッド・コーヘンの一つ 脆弱です(クッキーの処理を参照)。

最大ランダムコード実行の脆弱性

ランダムなコード実行は、最も深刻な種類の脆弱性です。攻撃者どのコマンドでも実行でき、実行できる操作に制限はありません。

これは一般的にそうです分けるこれにつながる部分です。このように分割すると、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:)。

これは、特に他のコマンド(awk、、、(GNU one)、... )を実行できるコマンド、特にGNUバリアント(引数の後にオプションを許可する)で問題になります。時には、コマンドが他のコマンド(例えば、'sまたは...)を実行できると疑わないことがあります。envsedperlfindkshbashzsh[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実装(ほとんどの商用Unicesと一部のBSDを含む)を含むファイルを生成する場合、そのシェルがコマンドの数値比較演算子の算術評価を実行するため、そのファイルがsh 実行されます。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 ]

これは私が迷惑に思うものです。引用符を省略できるかどうかを判断するために、特定の拡張子に問題があるかどうか疑問に思う人もいます。

まるで言うように。こんにちは、分割+グローブ演算子で囲むことができないようです$#。シェルで分割+グローブを行うようにします。。またはいや、バグが当たる確率が少ないから、間違ったコードを書こう

今はその可能性はどのくらいですか?いいですね。$#(または$!$?または算術置換)数字(または-一部²)のみを含めることができるため全体的な状況部品はすでに出てきました。 ~のため分ける$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

$1値がある場合、(IFS=-1234567890)この算術評価にはIFS設定の副作用があり、次の[ コマンドは失敗します。パラメータが多すぎます。バイパス。

いつ分割+グローバル交換員が呼び出されませんか?

変数やその他の拡張に引用符が必要な別のケースがあります。パターンとして使用する時です。

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

$aとが等しいかどうかテストしませんが、$b(除くzsh$aifとのパターンと一致します$b$b文字列で比較するには引用符を付ける必要があります(パターンと見なされない場合は、"${a#$b}"or"${a%$b}"またはwhereで"${a##*$b*}"同じ引用符を付ける必要があります)。$b

つまり、両方が異なる[[ $a = $b ]]場合(たとえば、isとisの場合)、trueを返し、同じ場合(たとえば、両方とareの場合)、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]'

今パターンが一致し、分割+グローバル演算子の適用、引用されていない変数のリスクは何ですか?

私は次のように書いたことを認めなければなりません:

a=$b
case $a in...

ここに参照を含めるのは悪いことではありませんが、必ずしも必要ではありません。

ただし、このような状況(Q&Aの回答など)で引用符を省略すると、初心者に誤ったメッセージを送信できるという副作用があります。変数を引用しなくても大丈夫でしょう。

a=$b例えば、彼らはそれが可能であればそうすることができると考え始めることができますexport a=$b(これはそれは多くの殻にありませんexportリストコンテキストで)またはenv a=$b

しかし、提案を受け入れない場所もあります。主なものは多くのシェルでKornスタイルの算術式です。たとえば、引用が許可されていないecho "$(( $1 + 1 ))" "${array[$1 + 1]}" "${var:$1 + 1}"場合$1(リストのコンテキスト - 単純なコマンドの引数 - しかし、完全な拡張には依然として引用が必要です)。

内部的には、シェルはCに触発された完全に独立した言語を理解しています。kshたとえば、AT&TではCと同じように3に拡張されますが、C$(( 'd' - 'a' ))とは異なります。$(( d - a ))ksh93では二重引用符は無視されますが、他の多くのシェルでは構文エラーが発生します。 C では、"d" - "a"C 文字列へのポインタ間の差が返されます。シェルで同じことを行うことは意味がありません。

何についてzsh

zsh実際にはほとんどのデザイン当惑感を解決します。zsh(少なくともsh / kshエミュレーションモードではない場合)必要に応じて分けるまたはワイルドカードまたはパターンマッチング、変数の内容をパターンに$=var分割、グローバル、または処理するなど、明示的に要求する必要があります。$~var

ただし、引用符のないコマンドの置き換えでは、分割(ワイルドカードを除く)はまだ暗黙的に実行されます(図を参照echo $(cmd))。

また、変数を引用しないと、不要な副作用が発生することがあります。削除をクリア。この動作は、ワイルドカード(使用)と分割(使用)zshを完全に無効にして他のシェルで達成したものと似ています。まだの中:set -fIFS=''

cmd $var

ないでしょう。分割+グローバルただし、$var空の場合はcmd空の引数を受け取る代わりに引数を受け取りません。

これはエラーを引き起こす可能性があります(明らかな事実です[ -n $var ])。これにより、スクリプトの期待と仮定が壊れ、脆弱性が発生する可能性があります。

空の変数がパラメータを生成する可能性があるため削除済み、これは、次の引数が無効なコンテキストで解釈される可能性があることを意味します。

例えば、

printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2

$attacker_supplied1空の場合、文字列(for)ではなく$attacker_supplied2算術式(for)として解釈されます。%d%s算術式で使用される未処理データは、zsh などの Korn 様シェルのコマンド注入の脆弱性です。

$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>

大丈夫です。しかし:

$ attacker_supplied1='' attacker_supplied2='psvar[$(uname>&2)0]'
$ printf '[%d] <%s>\n' 1 $attacker_supplied1 2 $attacker_supplied2
Linux
[1] <2>
[0] <>

これuname すべてのコマンド実行されます。

また、代わりにワイルドカードはデフォルトでは行われませんが、zshのワイルドカードは他のシェルのワイルドカードよりもはるかに強力であるため、オプションも有効または無効にせずに誤っていくつかのzsh変数を残すと、より多くのダメージを与える可能性があります。します。引用されていません。globsubstextendedglobbareglobqual

たとえば、さらに:

set -o globsubst
echo $attacker_controlled

eたとえば、glob修飾子の評価を使用してコマンドがglob拡張の一部として実行される可能性があるため、任意のコマンド実行の脆弱性になります。

$ set -o globsubst
$ attacker_controlled='.(e[uname])'
$ echo $attacker_controlled
Linux
.
emulate sh # or ksh
echo $attacker_controlled

bareglobqualsh / kshエミュレーションでは無効になっているため、ACEの脆弱性を引き起こしません(shなどのDoSの脆弱性はまだ存在しますが)。globsubstsh/ksh コードを解釈する場合は、sh/ksh エミュレーションの外部でこれらの機能を有効にする理由はありません。

だからあなたはする必要分割+グローバルオペレーター?

はい、これは通常、変数の引用を解除したい場合です。しかし、その後は必ず調整をしなければなりません。分けるそして全体的な状況ご使用の前に正しくお使いください。欲しいなら分ける代わりに部分全体的な状況セクション(ほとんどの場合)でワイルドカード(set -o noglob/ set -f)を無効にして変更する必要があります$IFS。そうしないと、上記のDavid KornのCGIの例などの脆弱性が発生する可能性があります。

結論として

簡単に言えば、シェルで引用されていない変数(またはコマンド置換または算術拡張)は実際には非常に危険である可能性があります。

これが考慮される理由の一つである。悪い習慣

これまで読んでくれてありがとう。想像以上なら心配しないでください。誰もがコードを書くようにコードを書くことのすべての意味を理解することは期待できません。だから私たちは ベストプラクティスの推奨事項、理由を理解することなく従うことができます。

(まだ明確でない場合は、シェルにセキュリティに敏感なコードを書かないでください。)

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


¹ksh93合計pdkshと導関数で、支柱の拡張ワイルドカードが無効になっていない場合は実行されます(ksh93ksh93u +バージョンでこのbraceexpandオプションが無効になっていても)。

²ksh93および では、算術拡張に、、などもyash含めることができます。 glob演算子を含む多くの機能がありますが、シミュレーションでも算術拡張では分割+globは行われません。1,21e+66infnanzsh#extendedglobzshsh

答え2

[からインスピレーションを受けるこの回答渡すカス.]

しかし、もし…?

しかし、私のスクリプトが変数を使用する前に既知の値に設定した場合はどうなりますか?特に、変数を複数の可能な値のいずれかに設定する場合(ただし、いつも既知の値に設定) すべての値にスペースまたはグローバル文字が含まれていませんか?引用符なしで使用すると安全ではありませんか?この場合

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

もし特定_条件
それから
    大文字と小文字を無視="-i"
その他
    大文字と小文字を無視=""
フィリピン諸島
                                  #上記のコマンドの引用符は次のとおりです。いいえ厳密に要求されます。   
grep $大文字と小文字を無視  その他_grep_args

空の文字列であれば失敗するとは言えません。grep "$ignorecase" other_grep_args$ignorecase

返信:

他の回答で説明したように、IFSaまたはanが含まれていても-失敗しますi。変数に文字が含まれていないことを確認し、変数にグローバル文字が含まれていないことを確認するとIFS安全です。

しかし、より安全な方法があります(やや醜くて非常に直感的ではありませんが)。${ignorecase:+"$ignorecase"}.fromを使用してください。POSIX シェルコマンド言語仕様、下に 2.6.2 パラメータ拡張

${parameter:+[word]}

    代替値を使用してください。  もしparameter設定されていないか null です。それ以外の場合はnullに置き換える必要があります。word (または空の文字列の場合word省略)を交換する必要があります。

ここでの秘訣は私たちがignorecase使うものですparameter"$ignorecase"そして word。だから${ignorecase:+"$ignorecase"}その意味は

$ignorecase設定されていないか、null(つまり空)の場合はnull(つまり、引用符なし)何もない)を交換し、それ以外の場合は"$ignorecase"拡張を交換する必要があります。

これにより、私たちは行きたい場所に行きます。変数が空の文字列に設定されると、「削除」されます(完全な複雑な式は次のように評価されます)。何もない- 空の文字列でもない)変数にnull以外の値がある場合は、引用符で囲まれた値を取得します。


しかし、もし…?

しかし、単語に分割したい変数や分割する必要がある変数がある場合はどうすればよいですか? (これは最初のケースと似ています。私のスクリプトには変数セットがあり、グローバル文字が含まれていないと確信していますが、スペースを含めることができるので、スペースから別のパラメータに分割したいです。削除したい)

など、

もし特定_条件
それから
    標準="-typef"
その他
    標準=""
フィリピン諸島
もしその他の条件
それから
    基準="$criteria -mtime +42"
フィリピン諸島
"$start_directory" $criteriaを探す  その他_探す_args

返信:

を使用していると思うかもしれませんeval  いいえ!evalここで使いたい誘惑を振りかけてください。

IFS同様に、変数に文字(スペースを除く)が含まれていないことを確認し、変数にグローバル文字が含まれていないことを確認すると、上記の内容はおそらく安全です。

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

もし特定_条件
それから
    Criteria=(-type f) # `criteria=("-type" "f")` と言えますが、必ずしも必要ではありません。
                        #しかし欲しくない`criteria=("-type f")` または `criteria="(-type f)"` と言ってください。
その他
    標準=()#欲しくないこのコマンドには引用符を使用してください。
フィリピン諸島
もしその他の条件
それから
    基準+=(-mtime +42) # 注: `=`ではなく`+=`、配列に追加(追加)します。
フィリピン諸島
"$start_directory" "${criteria[@]}"を探す  その他_探す_args

~から大きな打撃(1)

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

したがって、"${criteria[@]}"上記の例では、配列の0、2、または4つの要素に展開され、各criteria要素は参照されます。特に両方がなければ状況 sがtrueで、配列criteriaに内容がなく(criteria=()ステートメントに設定されている)、"${criteria[@]}"次のように評価されます。何もない (不便な空の文字列でもありません)。


これは、複数の単語を処理するときに特に興味深く複雑になります。そのうちのいくつかはあらかじめ知らない動的(ユーザー)入力であり、スペースやその他の特殊文字を含めることができます。考慮する:

printf "検索したいファイル名を入力してください:"
ファイル名を読む
if [ "$fname" != "" ]
それから
    標準 +=(-name "$fname")
フィリピン諸島

$fname引用しました。使用時間。これは、ユーザーがfoo bar同じものを入力しても機能しますfoo*。  "${criteria[@]}"結果は-name "foo bar"またはです-name "foo*"。 (配列の各要素が参照されることを覚えておいてください。)

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

もし特定_条件
それから
    set -- -type f # `set -- "-type" "f"` と言えますが、必ずしもそうではありません。
その他
    置く -
フィリピン諸島
もしその他の条件
それから
    設定 --"$@"-mtime +42
フィリピン諸島
#同様に:set - "$ @" -name "$ fname"
"$start_directory" "$@"を探す  その他_探す_args

この"$@"構成(歴史的に最初のもの)は同じ意味を持ちます。つまり、入力したとおり、各引数(つまり、引数リストの各要素)を別々の単語に展開します。"${name[@]}""$1" "$2" "$3" …

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

@

    1から始めて位置パラメータに展開され、最初は各位置パラメータセットのフィールドが生成されます。 … 、初期フィールドは別のフィールドに維持する必要があります。位置引数がない場合は拡張します。@次の場合でも、ゼロフィールドを生成する必要があります。@二重引用符の中に…

全文は多少曖昧です。キーは、"$@"位置引数がない場合にゼロのフィールドを生成する必要があることを指定することです。歴史:1979年にBourneシェル(bashの全身)に最初に導入されたときに位置引数がないとき、位置引数"$@"が単一の空の文字列に置き換えられるバグがありました。"$@"${1+"$@"}シェルスクリプトでは、これはどういう意味ですか?とどう違いますか"$@"、  伝統的なBourne Shellシリーズ、  ...どういう${1+"$@"}意味ですか?必要な場所はどこですか?"$@"比較的${1+"$@"}


配列は最初の場合にも役立ちます。

もし特定_条件
それから
    ignorecase=(-i) # `ignorecase=("-i")` と言うこともできますが、必ずしもそうではありません。
その他
    大文字と小文字を無視=()#欲しくないこのコマンドには引用符を使用してください。
フィリピン諸島
grep "${ignorecase[@]}"  その他_grep_args

____________________

PS(csh)

これは自明ですが、初心者のためにcsh、tcshなどはBourne / POSIXシェルではありません。彼らは全く別の家族です。異なる色の馬。全く違う野球ゲームです。猫の他の品種。エイリアン。そして、最もユニークにも異なる種類のワームがあります。

たとえば、このページで説明されている内容の一部はcshに適用されます。そうしない妥当な理由がなく、自分が何をしているのかわからない限り、すべての変数を引用するのが最善です。しかし、cshでは、すべての変数が配列です。ほとんどすべての変数が1つの要素のみを含む配列であり、Bourne / POSIXシェルの通常のシェル変数と非常によく似ています。そして構文は非常に異なります(私の言葉は非常に)。したがって、ここではcshシェルファミリーについてもう話しません。

答え3

Stéphaneの答えに疑問がありますが、誤って使用される可能性があります$#

$ set `seq 101`

$ IFS=0

$ printf '%s\n' $#
1
1

$ printf '%s\n' "$#"
101

それとも$? :

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ status=$?

$ printf '%s\n' $status
1
1

$ printf '%s\n' "$status"
101

これは人工的な例ですが、可能性は現実です。

答え4

私のスクリプトまたはSOへの答えのすべてのスクリプトで私の変数をすべて参照しない場合は、私が道徳的に腐敗したか技術的に無能であるという概念を否定することです。

人気のある回答は最後の行にまとめられています。

シェルにセキュリティに敏感なコードを書かないでください。

この投稿は攻撃者を想定しています。私は99%のシェルスクリプトがどんな攻撃にも直面せず、今後も決して直面しないと提案します。ユーザーの便宜/usr/local/binのために存在します。$HOME一種の初期化を制御します。定義によるとランダム入力の影響を受けません。

あなたは~/.profile攻撃者に面していません。ログファイルを解析またはロードするデータベースを準備する30行のawkスクリプトもそうではありません。常に攻撃にさらされているように、シェルスクリプトを作成するという一般的で一般的なアドバイスは、大多数のスクリプトを必要以上に複雑にし安全にできるという考えだけを奨励します。

スクリプトは攻撃者に公開されていますか?さて、注意して、この状況でroot権限でシェルスクリプトを実行するのが良いアイデアだと思います。

編集する

Always Quote Recommendation シェル変数を引用すると、常にエラーが防止されると主張します。しかし、実際にはそうではありません。到着盲目的に入力に意味的にエラーを引き起こすスペースやワイルドカードを含めないでください。分割や拡張を防ぎます。

name=$(ls foo*.cfg)
...
if [ ! -f $name ]
then 
    touch $name
fi

$name複数のファイル名が誤って含まれると、上記のコードは中断されます。予想されるバイナリ演算子。魔法のエラー防止引用符を使用して「正しく」作成すると、自動的に不正確に成功し、新しい偽のファイルが生成されます。疑いなく、後で理解できない方法で問題が発生し、デバッグがさらに困難になります。

を引用しないことによって、$nameスクリプトは内容に空白を含まない名前が含まれることを暗黙的に主張します。これは有用な主張です。複数の名前を使用したり愚かな名前を作成したりすることはありません。実行中(該当する場合)は停止します-e。それ以外の場合、少なくとも何が間違っているかを警告するメッセージが生成されます。

関連情報