Una confusione su $ {array [*]} rispetto a $ {array [@]} nel contesto di un completamento bash


90

Sto provando a scrivere un completamento bash per la prima volta, e sono un po 'confuso riguardo ai due modi di dereferenziare gli array bash ( ${array[@]}e ${array[*]}).

Ecco il pezzo di codice rilevante (funziona, a proposito, ma vorrei capirlo meglio):

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

La documentazione di bash dice :

Qualsiasi elemento di un array può essere referenziato usando $ {name [subscript]}. Le parentesi graffe sono necessarie per evitare conflitti con gli operatori di espansione del nome file della shell. Se il pedice è "@" o "*", la parola si espande a tutti i membri del nome dell'array. Questi pedici differiscono solo quando la parola appare tra virgolette doppie. Se la parola è tra virgolette, $ {name [*]} si espande in una singola parola con il valore di ogni membro dell'array separato dal primo carattere della variabile IFS e $ {name [@]} espande ogni elemento di name in una parola separata.

Ora penso di capire che si compgen -Waspetta una stringa contenente un elenco di parole di possibili alternative, ma in questo contesto non capisco cosa significhi "$ {name [@]} espande ogni elemento del nome in una parola separata".

Per farla breve: ${array[*]}funziona; ${array[@]}non lo fa. Vorrei sapere perché e vorrei capire meglio in cosa si ${array[@]}espande esattamente .

Risposte:


120

(Questa è un'espansione del mio commento sulla risposta di Kaleb Pederson - vedi quella risposta per un trattamento più generale di [@]vs. [*])

Quando bash (o qualsiasi shell simile) analizza una riga di comando, la divide in una serie di "parole" (che chiamerò "parole di shell" per evitare confusione in seguito). Generalmente, le parole della shell sono separate da spazi (o altri spazi bianchi), ma gli spazi possono essere inclusi in una parola della shell mediante l'escape o la citazione. La differenza tra array [@]e [*]-expanded tra virgolette è che "${myarray[@]}"ogni elemento dell'array viene trattato come una parola di shell separata, mentre "${myarray[*]}"risulta in una singola parola di shell con tutti gli elementi dell'array separati da spazi (o qualunque sia il primo carattere di IFS).

Di solito, il [@]comportamento è quello che vuoi. Supponiamo di avere perls=(perl-one perl-two)e di utilizzare ls "${perls[*]}"- è equivalente a ls "perl-one perl-two", che cercherà un singolo file denominato perl-one perl-two, che probabilmente non è quello che volevi. ls "${perls[@]}"è equivalente a ls "perl-one" "perl-two", che è molto più probabile che faccia qualcosa di utile.

Fornire un elenco di parole di completamento (che chiamerò parole-comp per evitare confusione con parole-shell) compgenè diverso; l' -Wopzione accetta un elenco di parole-comp, ma deve essere nella forma di una singola parola-shell con le parole-comp separate da spazi. Nota che le opzioni di comando che accettano argomenti sempre (almeno per quanto ne so) accettano una singola parola della shell - altrimenti non ci sarebbe modo di dire quando gli argomenti dell'opzione finiscono e gli argomenti del comando regolare (/ altro flag di opzione) iniziano.

Più in dettaglio:

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

è equivalente a:

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

... che fa quello che vuoi. D'altra parte,

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

è equivalente a:

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

... che è una totale assurdità: "perl-one" è l'unica parola di comp allegata al flag -W, e il primo vero argomento - che compgen prenderà come stringa da completare - è "perl-two / usr / bin / perl ". Mi aspetto che compgen si lamenti del fatto che gli sono stati dati argomenti extra ("-" e qualunque cosa sia in $ cur), ma a quanto pare li ignora.


3
Questo è eccellente; Grazie. Vorrei davvero che fosse esploso più forte, ma questo almeno chiarisce perché non ha funzionato.
Telemaco

61

Il tuo titolo chiede il ${array[@]}versus ${array[*]}ma poi chiedi il $array[*]versus $array[@]che è un po 'confuso. Risponderò ad entrambi:

Quando si cita una variabile di matrice e si utilizza @come pedice, ogni elemento della matrice viene espanso al suo contenuto completo indipendentemente dagli spazi (in realtà, uno di $IFS) che potrebbero essere presenti all'interno di quel contenuto. Quando si utilizza l'asterisco ( *) come pedice (indipendentemente dal fatto che sia citato o meno), potrebbe espandersi in un nuovo contenuto creato suddividendo il contenuto di ogni elemento dell'array in $IFS.

Ecco lo script di esempio:

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

Ed ecco l'output:

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'

Di solito lo desidero personalmente "${myarray[@]}". Ora, per rispondere alla seconda parte della tua domanda, ${array[@]}versus $array[@].

Citando i documenti bash, che hai citato:

Le parentesi graffe sono necessarie per evitare conflitti con gli operatori di espansione del nome file della shell.

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

Ma quando lo fai $myarray[@], il segno del dollaro è strettamente legato a, myarrayquindi viene valutato prima del [@]. Per esempio:

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

Ma, come notato nella documentazione, le parentesi sono per l'espansione del nome del file, quindi proviamo questo:

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

Ora possiamo vedere che l'espansione del nome del file è avvenuta dopo l' $myarrayespansione.

E un'altra nota, $myarraysenza un pedice si espande al primo valore dell'array:

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

1
Vedi anche questo riguardo a come IFSinfluisce sull'output in modo diverso a seconda di @vs. *e quotato vs.
Dennis Williamson,

Mi scuso, poiché è piuttosto importante in questo contesto, ma ho sempre inteso ${array[*]}o ${array[@]}. La mancanza di parentesi graffe era semplicemente disattenzione. Oltre a ciò, puoi spiegare in cosa ${array[*]}si espanderebbe nel compgencomando? Cioè, in quel contesto cosa significa espandere la matrice in ciascuno dei suoi elementi separatamente?
Telemaco

Per dirla in un altro modo, tu (come quasi tutte le fonti) dici che di ${array[@]}solito è la strada da percorrere. Quello che sto cercando di capire è perché in questo caso funziona solo ${array[*]} .
Telemaco

1
È perché la lista di parole fornita con l'opzione -W deve essere data come una singola parola (che compgen poi si divide in base a IFS). Se è diviso in parole separate prima di essere consegnato a compgen (che è ciò che fa [@]), compgen penserà che solo il primo va con -W, e il resto sono argomenti regolari (e penso che si aspetti solo un argomento, e quindi vomiterà).
Gordon Davisson

@ Gordon: spostalo in una risposta e lo accetterò. Questo è quello che volevo davvero sapere. Grazie. (A proposito, non vomita in modo ovvio. Emette silenziosamente, il che rende difficile sapere cosa è andato storto.)
Telemaco
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.