bash補完のコンテキストでの$ {array [*]}と$ {array [@]}に関する混乱


89

私は初めてbashの補完を書くことに挑戦していますが、bash配列(${array[@]}${array[*]})を逆参照する2つの方法について少し混乱しています。

これが関連するコードのチャンクです(ちなみに、それは機能しますが、もっとよく理解したいと思います):

_switch()
{
    local cur perls
    local ROOT=${PERLBREW_ROOT:-$HOME/perl5/perlbrew}
    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}
    perls=($ROOT/perls/perl-*)
    # remove all but the final part of the name
    perls=(${perls[*]##*/})

    COMPREPLY=( $( compgen -W "${perls[*]} /usr/bin/perl" -- ${cur} ) )
}

bashのドキュメントによると

配列の任意の要素は、$ {name [subscript]}を使用して参照できます。中括弧は、シェルのファイル名展開演算子との競合を避けるために必要です。下付き文字が「@」または「*」の場合、単語は配列名のすべてのメンバーに展開されます。これらの添え字は、単語が二重引用符で囲まれている場合にのみ異なります。単語が二重引用符で囲まれている場合、$ {name [*]}は各配列メンバーの値がIFS変数の最初の文字で区切られた単一の単語に展開され、$ {name [@]}は名前の各要素を展開します別の言葉に。

これでcompgen -W、可能な代替の単語リストを含む文字列が必要であることは理解できたと思いますが、このコンテキストでは、「$ {name [@]}が名前の各要素を個別の単語に展開する」とはどういう意味かわかりません。

短編小説:${array[*]}作品; ${array[@]}そうではありません。その理由を知りたいのですが、正確に何が${array[@]}拡大するのかをよりよく理解したいと思います。

回答:


119

(これはKalebペダーソンの答えに私のコメントの拡張である-のより一般的な治療のためにその答えを参照の[@][*]。)

bash(または同様のシェル)がコマンドラインを解析するとき、コマンドラインを一連の「単語」に分割します(後で混乱を避けるために「シェル単語」と呼びます)。一般に、シェルワードはスペース(または他の空白)で区切られますが、スペースをエスケープまたは引用することでシェルワードに含めることができます。二重引用符で囲まれた[@][*]-expanded配列の違い"${myarray[@]}"は、配列の各要素が個別のシェルワードとして扱われるのに対し"${myarray[*]}"、配列のすべての要素がスペースで区切られた単一のシェルワードになることです(またはの最初の文字が何であれIFS)。

通常、[@]動作はあなたが望むものです。を持っperls=(perl-one perl-two)て使用するとします。これは、という名前の単一のファイルを検索ls "${perls[*]}"するls "perl-one perl-two"、と同等perl-one perl-twoです。これはおそらくあなたが望んでいたものではありません。 はとls "${perls[@]}"同等でありls "perl-one" "perl-two"、何か便利なことをする可能性がはるかに高くなります。

補完語のリストを提供する(シェルワードとの混同を避けるためにcomp-wordsと呼びます) compgenは異なります。この-Wオプションはcomp-wordのリストを取りますが、comp-wordがスペースで区切られた単一のシェルワードの形式である必要があります。常に(少なくとも私が知る限り)引数を取るコマンドオプションは単一のシェルワードを取ることに注意してください-そうでなければ、オプションへの引数と通常のコマンド引数(/ other)がいつ終了するかを知る方法がありませんオプションフラグ)開始。

さらに詳細に:

perls=(perl-one perl-two)
compgen -W "${perls[*]} /usr/bin/perl" -- ${cur}

と同等です:

compgen -W "perl-one perl-two /usr/bin/perl" -- ${cur}

...あなたが望むことをします。一方、

perls=(perl-one perl-two)
compgen -W "${perls[@]} /usr/bin/perl" -- ${cur}

と同等です:

compgen -W "perl-one" "perl-two /usr/bin/perl" -- ${cur}

...これは完全にナンセンスです:「perl-one」は-Wフラグに付加された唯一のcomp-wordであり、最初の実際の引数(compgenが完成する文字列として受け取る)は「perl-two」です。 / usr / bin / perl "。compgenは、追加の引数( "-"および$ curにあるもの)が与えられていると文句を言うと思いますが、どうやらそれは単にそれらを無視しているようです。


3
これは素晴らしいです。ありがとう。私は本当にそれがもっと大声で爆発したことを望みます、しかしこれは少なくともそれがうまくいかなかった理由を明らかにします。
Telemachus 2010

60

あなたのタイトルは${array[@]}対について尋ね${array[*]}ますが、それからあなたは$array[*]対について尋ねます、$array[@]それは少し混乱しています。私は両方に答えます:

配列変数を引用@して添え字として使用すると、そのコンテンツ$IFS内に存在する可能性のある空白(実際にはの1つ)に関係なく、配列の各要素が完全なコンテンツに展開されます。アスタリスク(*)を添え字として使用すると(引用符で囲まれているかどうかに関係なく)、各配列要素のコンテンツをで分割して作成された新しいコンテンツに展開される場合があり$IFSます。

スクリプトの例を次に示します。

#!/bin/sh

myarray[0]="one"
myarray[1]="two"
myarray[3]="three four"

echo "with quotes around myarray[*]"
for x in "${myarray[*]}"; do
        echo "ARG[*]: '$x'"
done

echo "with quotes around myarray[@]"
for x in "${myarray[@]}"; do
        echo "ARG[@]: '$x'"
done

echo "without quotes around myarray[*]"
for x in ${myarray[*]}; do
        echo "ARG[*]: '$x'"
done

echo "without quotes around myarray[@]"
for x in ${myarray[@]}; do
        echo "ARG[@]: '$x'"
done

そして、これがその出力です:

with quotes around myarray[*]
ARG[*]: 'one two three four'
with quotes around myarray[@]
ARG[@]: 'one'
ARG[@]: 'two'
ARG[@]: 'three four'
without quotes around myarray[*]
ARG[*]: 'one'
ARG[*]: 'two'
ARG[*]: 'three'
ARG[*]: 'four'
without quotes around myarray[@]
ARG[@]: 'one'
ARG[@]: 'two'
ARG[@]: 'three'
ARG[@]: 'four'

私は個人的に通常欲しいです"${myarray[@]}"。さて、あなたの質問の2番目の部分に答えるために、${array[@]}$array[@]

あなたが引用したbashドキュメントを引用します:

中括弧は、シェルのファイル名展開演算子との競合を避けるために必要です。

$ myarray=
$ myarray[0]="one"
$ myarray[1]="two"
$ echo ${myarray[@]}
one two

ただし、そうすると$myarray[@]、ドル記号は厳密にバインドされるmyarrayため、の前に評価され[@]ます。例えば:

$ ls $myarray[@]
ls: cannot access one[@]: No such file or directory

ただし、ドキュメントに記載されているように、角かっこはファイル名を拡張するためのものなので、これを試してみましょう。

$ touch one@
$ ls $myarray[@]
one@

これで、ファイル名の展開が後に行われたことがわかります。 $myarrayわかります。

また$myarray、添え字なしで、配列の最初の値に展開されることに注意してください。

$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]

1
対および引用と非引用に応じて出力にどのように異なる影響を与えるかについても、これを参照してください。IFS@*
追って通知があるまで一時停止します。

この文脈では非常に重要なので、お詫びしますが、私はいつも${array[*]}またはを意味していました${array[@]}。中括弧の欠如は単に不注意でした。それを超えて、コマンド${array[*]}で何が展開されるかを説明できますcompgenか?つまり、そのコンテキストでは、配列をその要素のそれぞれに個別に展開することはどういう意味ですか?
テレマコス2010

別の言い方をすれば、あなたは(ほとんどすべての情報源のように)それ${array[@]}が通常の道だと言います。私が理解しようとしているのは、この場合にのみ ${array[*]}機能する理由です。
テレマコス2010

1
これは、-Wオプションで指定された単語リストを単一の単語として指定する必要があるためです(compgenはIFSに基づいて分割されます)。compgenに渡される前に別々の単語に分割された場合([@]が行うことです)、compgenは最初の1つだけが-Wであり、残りは通常の引数であると見なします(そして、1つの引数のみを期待していると思います。したがって、バーフします)。
ゴードンデイヴィソン2010

@ゴードン:それを答えに移してください、そして私はそれを受け入れます。それが私が本当に知りたかったことです。ありがとう。(ところで、それは明白な方法で吠えません。それは静かに吠えます-それは何が悪かったのかを知るのを難しくします。)
Telemachus 2010
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.