$ arrayがkshとbashの配列全体を拡張しないのはなぜですか?

$ arrayがkshとbashの配列全体を拡張しないのはなぜですか?

からインスピレーションを受けるこの最近の質問:

bash$ a=(1 2 3)
bash$ echo $a
1

しかし、

zsh% a=(1 2 3)
zsh% echo $a
1 2 3
zsh% printf '%s\n' $a
1
2
3

(最後の部分は、等しくない"${a[@]}"別々のパラメータに配列を拡張する方法を示しています"${a[*]}"。)

bash(kshと一致)の動作は非常に反直感的です。 「この配列変数の拡張」に対する「最初の要素のみ」はどのように合理的な応答ですか?

zshが分かれている他の領域では、kshとbashが元のBourneシェルに近いためです。ただし、Bourneにはカスタム配列変数はありません。

Bashはなぜこのような奇妙な決定を下したのですか? kshがコピーされると、kshはなぜこのような奇妙な決定を下すのですか?

長いコメントのリストの後に続く。

これはzshを批判したり賞賛したりする問題ではありません。 zshはわかりやすい例です。可能様々なアプローチがとられてきた。

設計決定を説明する可能性の1つは、以前のバージョンとの互換性です。以前のバージョンとの互換性は意見ではありません。これは客観的な事実です。

スクリプトを表示できる場合(フルスクリプト、コンテキストから抜粋したものではなく、Bourneシェルで既知の方法で実行され(つまり、構文エラーで爆弾を爆発させない)、仮想の"完全な$array拡張を含むKornシェル"で異なる動作をします。勝った!これは以前のバージョンとの互換性の問題です。

まだそのようなスクリプトは提供されていません。これは以下ではありません:

a=(1 2 3)
printf '%s\n' $a

これは Bourne シェルの構文エラーだからです。一度構文エラーだったことに新しい意味を与えることは、古いバージョンとの互換性を維持しながら新しい機能を作成する1つの方法です。

私が知っている限り、a=(...)元の構文エラーだったという事実は、配列を使用するスクリプトと未使用のスクリプトを明確に区別します。最初のカテゴリでは、これらのスクリプトは古いシェルで実行されないため、以前のバージョンとの互換性について何らかの理由で言及することはできません。 2番目のカテゴリでは、配列変数の拡張規則に関係なく拡張する配列がないため、以前のバージョンとの互換性が維持されます。

これはない証明する配列をスクリプトにこっそり入れる方法がないと判断するために部分的に直観に頼っていたため、=(互換性のない動作を示すスクリプトはありませんでした。存在しないと主張することの利点は、それを終わらせるには反例だけを提示すればよいということです。

コメントに出てきた内容がa=$@説明に役立つようです。配列変数を生成する場合、aこのスクリプトは次のことを行います。

a=$@
printf '%s\n' $a

違いが表示されるはずです。しかし、私がテストした結果、これは起こりませんでした。すべてのシェル(heirloom sh、modern ksh、bash、zsh)は、最初の行を同じように扱うようです。a配列ではなく、スペースを含む文字列だけです。 (zshはの値をトークン化しないため、2行目から分岐しますが、$aこれは配列変数とは関係ありません)

答え1

回答はできませんが、いくつかの可能な説明を提案します。

実際、kshとそのレプリカ(pdkshと追加の派生エントリとbash)を除いて、配列(、、、、、、、、)cshを持つ他tcshのすべてのシェルは配列のすべてのメンバーに拡張されました。rcesakangafishzshyash$array

ただし、このリストにある2つのBourne様シェルであるyash(エミュレーションzshsh)、拡張はまだ分割+globの影響を受けます(エミュレートしていzshない場合でもshnullを削除します)。したがって、まだ厄介な構文を使用する必要があります"${array[@]}"(または"${(@)array}"簡単に入力できます)、リストを維持"$array[@]"するzshには(同様の問題がcshありますtcsh)、分割+ globとnullの削除はBourneレガシーです(それ自体はマクロ拡張と同様のThompsonシェルレガシーが原因で発生します)$1)。

rcfishBourne手荷物がなく、よりきれいなアプローチをとる最新のシェルの2つの例です。彼らはシェルがコマンドラインソルバーであり、それらが扱う主なタスクはリスト(コマンドのパラメータリスト)なので、リスト/配列が主要なデータ型(タイプは1つだけでありリスト)であることを認識しrcて削除します。 Bourneシェルの分割+ glob-upon-expansionのバグ/エラー機能(デフォルトのタイプは配列なのでもう必要ありません)。

$arrayそれにもかかわらず、David Kornがすべての要素に拡張するのではなく、インデックス0の要素に拡張することを選択した理由は説明されていません。

csh現在、/を除くすべてのシェルは、BourneシェルとUnix V7がリリースされてわずか数年後のtcsh1980年代初頭に開発されたものよりもはるかに最新です。kshUnix V7にも環境が導入されました。これは当時としては非常に斬新なことでした。環境はきれいで便利ですが、何らかの形式のエンコーディングを使用しない限り、環境変数に配列を含めることはできません。

これは推測だけですが、David Kornがこのアプローチを選択した理由の1つは、環境とのインターフェースを変更しないためだと思います。

rcと同様に、ksh88ではすべての変数が配列です(非常に希薄ですが、キーが正の整数に制限されている連想配列に似ています。これは他のシェルやプログラミング言語と比較して別の奇妙な点です。完全には実装されていません)。たとえば、キーリストを検索することは不可能であるため、考えました。新しいデザインではvar=value略語になりますvar[0]=value。それでもすべての変数をエクスポートできますが、export var配列インデックス0の要素を環境にエクスポートできます。

rcすべての変数を環境に配置して配列のエクスポートをサポートしますfishが、複数の要素を持つ配列の場合(少なくとも plan9 の rc から Unix ポートへの場合)、エンコード形式に依存する必要があります。彼らだけが理解しています。

csh、、、は配列のエクスポートをサポートしていません(ただし、現在のところこれは大きな制限のように聞こえないかもしれません)tcshzshから配列をエクスポートすることはできますがyash、配列要素は関連する:(a "" "" b)したがって(a : b)エクスポートされた値と同じ)環境変数としてエクスポートされ、インポート時に配列に再変換されません。

もう一つの考えられる理由は$@Bourne's /との一貫性です$*(しかし、配列インデックスはなぜ1ではなく0から始まりますか?kshこれはフリーソフトウェアではなく商業企業であり、要件の1つはBourne互換性です。ksh完了したフィールド分割を削除します。すべてリストの文脈で引用されていない単語(Bourneシェルでは明らかに役に立たないため)は拡張のために保存する必要があります(ただし、スクリプトは配列のvar="file1 file2"; cmd $varないBourneシェルのようなものを使用するため"$@")。配列を持つシェルに保存することはあまり意味がありませんが、Kshが依然としてコンシューマーベースのスクリプトを解釈できる場合、Kornにはほとんどオプションがありません。$scalarスプリット+グローブの影響を受ける場合は、一貫性のためである$array必要があるため、"${array[@]}"一般化として理解"$@"することをお勧めします。zsh同様の制約がないため、拡張時に配列を追加しながら分割+グローブを自由に削除できます(ただし、Bourneの下位互換性が損なわれる対価を払うことになります)。

@Arrowが提供するもう1つの説明は、既存の演算子をオーバーロードしてさまざまな型の変数に対して異なる動作をしたくないことです(たとえば、Bourneシェルに、または、が${#var}ない場合でも)。これはユーザーの混乱を招く可能性があります。 (一部の演算子が配列とスカラーを使用する方法は必ずしも明確ではないからです)。${#array}${var-value}${var#pattern}zsh

関連資料:


a=$@編集状況に関しては、実際にkshがBourneシェルとの互換性を破る場合です。

Bourneシェルには、位置引数とスペース文字の連結が含まれています$@。挿入されたスペースと同じように拡張されますが、引用されていないため、引用された場合にのみ$*特別です(最新バージョンでは、空のリストの特殊なケースはSolarisと同様に処理されました)。スペースを削除すると、リストコンテキスト内の1つの引数にのみ拡張されます(0はリスト内の空のリストを意味します)。$@"$*"$IFS"$@"安定上記のバージョン)。引用符がない場合は、他の変数のように動作します(必ず元の位置引数に分割する必要はありませんが、文字に分割$*)。たとえば、Bourne シェルでは次のようになります。$@$IFS

'set' 'a:b'   'c'
IFS=:
printf '<%s>\n' $@
printf '[%s]\n' "$@"

出力されます:

<a>
<b c>
[a:b c]

$@Ksh88は、との最初の文字が$*連結されるように変更しました$IFS。空で"$@"ない場合は、リストコンテキストで位置パラメータを区切ります$IFS

$IFS空白の場合は、区切り文字なしで引用しない限り、スペース$*で囲みます$*

例:

$ set a b
$ IFS=:
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a:b>
<a:b>
<a:b>
<a:b>
<a>
<b>
<a>
<b>
<a>
<b>
<a:b>
$ IFS=
$ a=$@ b=$* c="$@" d="$*"
$ printf '<%s>\n' "$a" "$b" "$c" "$d" $@ $* "$@" "$*"
<a b>
<a b>
<a b>
<ab>
<a b>
<a b>
<a b>
<ab>

ksh93やksh88など、Bourne / Korn様シェルのさまざまなバリエーションを見ることができます。さらに、状況には次のようないくつかの変更があります。

set --
cmd ''"$@"
cmd $empty"$@"

あるいは、$IFSマルチバイト文字や有効な文字を形成しないバイトが含まれている場合です。


yashただし、"$array"動作は"${array[@]}"whilezsh"$array"動作と似ています"${array[*]}"。あまり醜くて恥ずかしいですが、たぶんもっと驚くかもしれません。

答え2

この回答に対するあなたの意見では、zshのパラダイムが「より優れている」ので、すべてのシェルがどのように機能するのか、そして受け入れる答えを期待しました。 「もっと良い」というのはただの意見だけだ。コメントには議論は必要ありません。

  • $aおそらくあなたが期待するのは、これが配列でも機能する理由の詳細です。

    これがzshがやろうとしていることであり、この方向に多くのことをしても、
    場合によっては失敗します。 (まだ)配列をコピーしません。

    $ a=(1 2 3)
    $ b=$a
    $ printf '<%s>' $a ' ' $b; echo
    <1><2><3>< ><1 2 3>
    

  • しかし、この質問は実際にはそれとは遠い。これは言語の問題です。

私たちの言語では、すべてのアイデアに名前を使用します。すべての新しいアイデアは、以前のアイデアとは全く異なります。〜しなければならない自分の名前があります。私たちの言語では、新しいアイデアを表現するために自然に新しい言葉を受け入れます。新しいアイデアの新しい名前である「インターネット」という名前を考えてみましょう。新しい概念を表現するために古い名前を使用すると、常に混乱と誤解が発生します。これが私たちの人間が作られた方法であり、考えると合理的なように聞こえます。

  • シェル(およびすべてのプログラミング言語)では、特定のアイデアごとに特定の構文を使用します。

最初からシェルの変数には、a符号拡張(シェルプロシージャ)の結果である値を持つ名前(仮定)があります$a。変数には文字列または数値を含めることができます(自動変換)。

  • 変数の新しい内容(値の配列)の紹介〜しなければならない新しい構文を使用してください。

@aこれはPerlがusingを使用して表現するのとまったく同じです。リスト$a維持しながらan scalar
それは:上記の2つをそれぞれSCALARおよびLISTコンテキストと呼びます。パールから)。

これが私たちがa=(1 2)配列を割り当てる理由です(他の方法もありますが、これが最も一般的です)。古いシェルの構文が正しくありません。

sh(この場合はダッシュ):

$ a=(12)
sh: 2: Syntax error: "(" unexpected

そして、変数の拡張は新しい構文$aまたは同等の${a}新しい構文(shでは無効です) `${a [@]}から来ます。

$ a=(aa bb cc)
$ printf '%s\n' "${a[@]}"
aa
bb
cc

より単純なシェル(この場合はash)から:

$ a=Strin-Of-Text
$ printf '%s\n' "$a"
ash: syntax error: bad substitution

配列を作成するのにこのような複雑な方法を選択したのは残念です。

私が新しい構文を思いついたら、おそらくPerlの指示に従って "@a"(またはおそらくまたは#a%aなどの値のリストを使用します。これは合意に達するための議論の問題でなければなりません。

  • しかし、それは(悲しいことに)実行されたものではなく、選択されたものは次のとおりです${a[@]}

簡単に言えば、以前のバージョンとの互換性のために、単純変数の拡張は次のような$a結果のみをもたらすと予想されます。一つ値のリストではなく、分離された値です。

POSIX では配列が定義されていないため、POSIX で定義を参照することは無効です。パラメータ拡張説明する:

パラメータ値(存在する場合)を交換する必要があります。

そして実際にはほとんどのシェルはスカラーだけを印刷します。$a

bash(4.4)       : <1><====><1><2><3>
lksh            : <1><====><1><2><3>
mksh            : <1><====><1><2><3>
ksh93           : <1><====><1><2><3>
astsh           : <1><====><1><2><3>
zsh/ksh         : <1><====><1><2><3>
zsh             : <1><2><3><====><1><2><3>

したがって、zsh [a]はシェルスクリプトの作成にとって奇妙な選択です。

このスクリプトを使用して以下をテストします。

a=(1 2 3)
printf '<%s>' $a '====';
printf '<%s>' "${a[@]}" ;
echo

[a]ヤッシュ。 csh互換シェルについてはテストされていません。 cshを使用するスクリプトには既知の問題があります。

関連情報