Eine Verwirrung zwischen $ {array [*]} und $ {array [@]} im Zusammenhang mit einem Bash-Abschluss


85

Ich versuche zum ersten Mal, eine Bash-Vervollständigung zu schreiben, und bin etwas verwirrt über die beiden Möglichkeiten, Bash-Arrays ( ${array[@]}und ${array[*]}) zu dereferenzieren .

Hier ist der relevante Codeabschnitt (er funktioniert übrigens, aber ich würde ihn gerne besser verstehen):

_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} ) )
}

In der Dokumentation von bash heißt es :

Auf jedes Element eines Arrays kann mit $ {name [tiefgestellt]} verwiesen werden. Die geschweiften Klammern sind erforderlich, um Konflikte mit den Dateinamenerweiterungsoperatoren der Shell zu vermeiden. Wenn der Index '@' oder '*' ist, wird das Wort auf alle Mitglieder des Array-Namens erweitert. Diese Indizes unterscheiden sich nur, wenn das Wort in doppelten Anführungszeichen steht. Wenn das Wort in doppelte Anführungszeichen gesetzt ist, wird $ {name [*]} zu einem einzelnen Wort erweitert, wobei der Wert jedes Array-Mitglieds durch das erste Zeichen der IFS-Variablen getrennt wird, und $ {name [@]} erweitert jedes Element des Namens zu einem separaten Wort.

Jetzt denke ich, dass ich verstehe, compgen -Wdass eine Zeichenfolge erwartet wird, die eine Wortliste möglicher Alternativen enthält, aber in diesem Zusammenhang verstehe ich nicht, was "$ {name [@]} jedes Element des Namens zu einem separaten Wort erweitert" bedeutet.

Lange Rede, kurzer Sinn: ${array[*]}Werke; ${array[@]}nicht. Ich würde gerne wissen warum und ich würde gerne besser verstehen, worauf es genau ${array[@]}ankommt.

Antworten:


112

(Dies ist eine Erweiterung meines Kommentars zu Kaleb Pedersons Antwort - siehe diese Antwort für eine allgemeinere Behandlung von [@]vs. [*])

Wenn bash (oder eine ähnliche Shell) eine Befehlszeile analysiert, wird sie in eine Reihe von "Wörtern" aufgeteilt (die ich später als "Shell-Wörter" bezeichnen werde, um Verwirrung zu vermeiden). Im Allgemeinen werden Shell-Wörter durch Leerzeichen (oder andere Leerzeichen) getrennt, aber Leerzeichen können in ein Shell-Wort eingefügt werden, indem sie maskiert oder in Anführungszeichen gesetzt werden. Der Unterschied zwischen [@]und [*]-erweiterten Arrays in doppelten Anführungszeichen besteht darin, dass "${myarray[@]}"jedes Element des Arrays als separates Shell-Wort behandelt wird, während "${myarray[*]}"ein einzelnes Shell-Wort entsteht, bei dem alle Elemente des Arrays durch Leerzeichen (oder) getrennt sind was auch immer das erste Zeichen von IFSist).

Normalerweise ist das [@]Verhalten das, was Sie wollen. Angenommen, wir haben perls=(perl-one perl-two)und verwenden ls "${perls[*]}"- das entspricht ls "perl-one perl-two", dass nach einer einzelnen Datei mit dem Namen perl-one perl-twogesucht wird, was wahrscheinlich nicht das ist, was Sie wollten. ls "${perls[@]}"ist gleichbedeutend mit ls "perl-one" "perl-two", was viel wahrscheinlicher ist, etwas Nützliches zu tun.

Das Bereitstellen einer Liste von Vervollständigungswörtern (die ich Comp-Wörter nennen werde, um Verwechslungen mit Shell-Wörtern zu vermeiden) compgenist anders. Die -WOption enthält eine Liste von Kompositionswörtern, muss jedoch die Form eines einzelnen Shell-Wortes haben, wobei die Kompositionswörter durch Leerzeichen getrennt sind. Beachten Sie, dass Befehlsoptionen, die immer Argumente annehmen (zumindest soweit ich weiß), ein einzelnes Shell-Wort verwenden. Andernfalls kann nicht festgestellt werden, wann die Argumente für die Option und die regulären Befehlsargumente (/ other) enden Optionsflags) beginnen.

Genauer:

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

ist äquivalent zu:

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

... was macht was du willst. Andererseits,

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

ist äquivalent zu:

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

... was völliger Unsinn ist: "perl-one" ist das einzige Comp-Wort, das an das -W-Flag angehängt ist, und das erste echte Argument - das compgen als zu vervollständigenden String verwendet - ist "perl-two" / usr / bin / perl ". Ich würde erwarten, dass Compgen sich darüber beschwert, dass ihm zusätzliche Argumente gegeben wurden ("-" und was auch immer in $ cur steht), aber anscheinend ignoriert es sie einfach.


3
Das ist ausgezeichnet; Vielen Dank. Ich wünschte wirklich, es würde lauter explodieren, aber das verdeutlicht zumindest, warum es nicht funktioniert hat.
Telemachos

56

Ihr Titel fragt nach ${array[@]}Versus, ${array[*]}aber dann fragen Sie nach $array[*]Versus, $array[@]was etwas verwirrend ist. Ich werde beide beantworten:

Wenn Sie eine Array-Variable zitieren und @als Index verwenden, wird jedes Element des Arrays auf seinen vollständigen Inhalt erweitert, unabhängig von Leerzeichen (tatsächlich eines von $IFS), die in diesem Inhalt vorhanden sein können. Wenn Sie das Sternchen ( *) als Index verwenden (unabhängig davon, ob es in Anführungszeichen steht oder nicht), wird es möglicherweise auf neuen Inhalt erweitert, der durch Aufteilen des Inhalts jedes Array-Elements bei erstellt wird $IFS.

Hier ist das Beispielskript:

#!/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

Und hier ist die Ausgabe:

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'

Ich persönlich möchte normalerweise "${myarray[@]}". Nun, zu beantworten , den zweiten Teil Ihrer Frage, ${array[@]}gegenüber $array[@].

Zitieren der Bash-Dokumente, die Sie zitiert haben:

Die geschweiften Klammern sind erforderlich, um Konflikte mit den Dateinamenerweiterungsoperatoren der Shell zu vermeiden.

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

Wenn Sie dies jedoch tun $myarray[@], ist das Dollarzeichen eng an das Dollarzeichen gebunden, myarraysodass es vor dem ausgewertet wird [@]. Beispielsweise:

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

Wie in der Dokumentation angegeben, dienen die Klammern jedoch zur Erweiterung des Dateinamens. Versuchen wir also Folgendes:

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

Jetzt können wir sehen, dass die Dateinamenerweiterung nach der Erweiterung stattgefunden $myarrayhat.

Und noch eine Anmerkung $myarrayohne Index wird auf den ersten Wert des Arrays erweitert:

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

1
Siehe auch diese darüber , wie IFSwirkt sich unterschiedlich auf die Ausgabe in Abhängigkeit @gegenüber *und gegenüber nicht notierte zitiert.
Bis auf weiteres angehalten.

Ich entschuldige mich, da es in diesem Zusammenhang ziemlich wichtig ist, aber ich meinte immer ${array[*]}oder ${array[@]}. Das Fehlen von Zahnspangen war einfach Nachlässigkeit. Können Sie darüber hinaus erklären, worauf ${array[*]}sich der compgenBefehl auswirken würde ? Was bedeutet es in diesem Zusammenhang, das Array in jedes seiner Elemente separat zu erweitern?
Telemachos

Um es anders auszudrücken, Sie (wie fast jede Quelle) sagen, dass dies ${array[@]}normalerweise der richtige Weg ist. Ich versuche zu verstehen, warum in diesem Fall nur ${array[*]} funktioniert.
Telemachos

1
Dies liegt daran, dass die mit der Option -W gelieferte Wortliste als einzelnes Wort angegeben werden muss (das compgen dann basierend auf IFS aufteilt). Wenn es in separate Wörter aufgeteilt wird, bevor es an compgen übergeben wird (was [@] tut), wird compgen denken, dass nur das erste mit -W übereinstimmt, und der Rest sind reguläre Argumente (und ich denke, es erwartet nur ein Argument). und wird daher barf).
Gordon Davisson

@ Gordon: Verschiebe das zu einer Antwort, und ich werde es akzeptieren. Das wollte ich unbedingt wissen. Vielen Dank. (Übrigens barf es nicht auf offensichtliche Weise. Es barft lautlos - was es schwierig macht zu wissen, was schief gelaufen ist.)
Telemachus
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.