変数拡張で欠落している引用符を削除してエスケープの後に隠されたアイデアは何ですか?

変数拡張で欠落している引用符を削除してエスケープの後に隠されたアイデアは何ですか?

コマンドは変数に保存され、シェルで実行できます(良い方法ではありませんが)。例:

command='ls -l A* "B\" type"'
$command

A、およびで"B\"始まるファイルが一覧表示されますtype"。パラメータの分離とワイルドカードを実行しますが、引用符とエスケープは削除しません。この動作により、配列をサポートしないシェルで変数を使用して任意の引数を渡すことは非常に困難であり、find他のコマンドを安全に結合することは不可能になりますfor(よく議論されています)。引用符のない変数拡張では、多くの文字が制御されないため、ワイルドカードの使用も制限されます(`'"*?\nリテラルを含むワイルドカードシーケンスを保存して正しく再利用できません)。

状況は次のとおりです非常に変数の引用符とエスケープシーケンスが異なる場合は処理できます。しかし、なぜほとんどのシェルは実際にこれをしないのですか?私が気づいていないいくつかのあいまいな考慮事項を考慮して特別に設計されているのか、それとも互換性を維持するために渡されたのでしょうか?同様の質問があることがわかります。Bash変数拡張が引用符を保持するのはなぜですか?そして「変数のコマンド」での引用/エスケープ/拡張の問題この動作は議論されていますが、そこにある回答では原因については説明しません。

答え1

この動作により、変数を使用して任意のパラメータを渡すことは非常に困難です[...]

おそらく。ただし、拡張結果をすべての一般的なコマンドライン処理に適用せずに、任意の引数全体を渡すことは不可能です。

たとえば、どこかからファイル名を取得してコマンドに渡そうとするスクリプトを考えてみましょう。以下でファイル名を取得するとしますread

echo -n "please enter filename: "
read -r filename
some command "$filename"

ユーザーが同様のファイル名を入力すると、単一引用符が原因で構文エラーが発生し、don't stop me now.txt実行が中断されます。some command

同様に、スクリプトが例のように実行され、myscript don*.txtコマンドライン引数からファイル名を取得する場合:

filename=$1
some command "$filename"

もう一度$filename(または$1すでに)一重引用符が含まれています。

さらに悪いことに、ファイル名またはユーザー入力文字列に代替コマンドを含めることができます。ただ変数を使う任意のコマンドを実行してください。スクリプト作成者は、スクリプトの外部から読み取られたすべての文字列にエスケープ文字を厳しく追加する必要があります。さらに、人々はそうしません、そしてシェルはツールとして使用するのが安全ではありません。

(必要に応じて拡張を処理する必要はなく、引用符とバックスラッシュのみを処理できますが、ペアのない引用符の問題はまだ存在します。)

もちろん、read必要なエスケープだけを追加する必要があると言うかもしれませんが、他のすべてのタイプの入力に追加する必要がありますか?文字列操作はどのように機能しますか?引用符も処理する必要がありますか?${#var}可変長と同じくらい単純なものでさえ、実装するのにより多くの費用がかかります。複数の異なる引用符付き文字列を含む変数の長さはどういう意味ですか?

最後に考慮するのが最善です。パスワードスクリプトとスクリプトの違いデータスクリプトはそれを処理し、コードに明示的に設定された方法でのみデータが処理されるように難読化されないように構成します。変数拡張を引用したことを覚えていれば、これがシェルが行うこととほぼ同じです。

変数のデータをそのまま使用することは、他のすべてのプログラミング言語でも同様です。たとえば、このCコードスニペットから印刷された文字列は、引用符"foo bar"でランタイム環境で解析されません。

char *s = "\"foo bar\"";
printf("%s\n", s);

s = "foo()"同様に、逆の場合、printf()呼び出しは関数を呼び出さずにfoo()文字列のみを印刷しますfoo()。 (解釈された言語とコンパイルされた言語について議論したい場合は、例をPerlまたはPythonに変更できます。)


今、これはあなたの提案が2022年に私にとって良い考えではない理由についての議論です。しかし、実際には「理由」と設計の根拠を尋ねています。これは2022年ではなく、1970年代と1980年代頃に起こりました。ウィキペディアで言及Bourne Shellの最初のリリースは1979年に行われました。それはずっと前、従来のコンピューティングの歴史が今よりずっと短かった時でした。これで、シェルアレイなどの他のツールを作成するのに役立つ可能性のある遅れた判断の利点があります。より速いコンピュータとより多くのメモリ。

私はデザインの背後に隠された実際の説明が「これが彼らが最初にすべてを見つけたときに念頭に置いたものですが、何が理由であるのか止まっていました」というフレーズによるものかもしれないという考えを無視しません。以前のバージョンとの互換性は2つの方法で機能します。少なくとも現在、配列を含むシェルとはまったく異なるシェルがあります。

答え2

引用符の削除は、原則としてシェル構文の引用符にのみ適用されるため、コンパイル可能な関数。つまり、置き換えられたシェルで実際にランタイム引用符を削除する必要はありません。このように解釈される抽象化ですが、文法を解析するときに実際に引用符が削除されることがあります。

このようなコマンドラインコンポーネントは、引用符付きの"foo $bar"単位に置き換えることができます。シェルのパーサーはこれが引用されたことを覚えているでしょう。ただし、実際の引用符ではありませんfoo $bar。項目が実行時に処理されると、$bar値はそのまま補間されます。このようなコマンドラインエントリはabc$bar引用符なしの単位にすることができますが、実行時の意味は を挿入してフィールドに分割し、パス名$bar拡張を実行することです。

このモデルで変数の内容を引用することは、シェルが実行時に語彙検索と解析アクティビティも実行する必要があることを意味します。

これは、基本的に構文キーワードが変数から出てこない理由と同じです。たとえば、次のようになります。

thenvar=then
fivar=fi

# nonsense
if command; $thenvar
  echo command succeeded
$fivar

シェルがキーワードの$thenvar内容を認識できないのはなぜですかthen

同じ理由で、変数に格納された引用符を構文引用符として認識しません。

これで、シェルはランタイムと解析時間の間に「レベルを混合」します。拡張に引用符がない場合は、複数のフィールドに分割されます。また、拡張にワイルドカード文字が含まれていると、これらの文字がアクティブになります。

確かに、これらの関数は構文ですが、データから動的に出ることもできます。

残念ながら、シェルプログラミングに混乱とエラーをもたらすのは、これらの機能とレベルの混合です。引用符を忘れた場合、データの蓄積部分*またはスペースが破損する可能性があります。?

データから引用符を処理すると、より多くの混乱とエラーが発生します。たとえば、変数に不均衡引用符が含まれていると、構文エラーが発生します。そうですか?何を待つ?ランタイムデータのために構文エラーが発生しましたか?または、構文の引用符のバランスを取るために変数に引用符を許可しますか?これはうまくいきますか?

quote='"'
echo "foo bar$quote   # does $quote close the open quote?

ご存知のように、それは非常に速く面白いです。

関連情報