Qual è la differenza tra $ * e $ @?


73

Considera il seguente codice:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Emette:

1 2 3 4

1 2 3 4

Sto usando Ksh88, ma sono interessato anche ad altre shell comuni. Se ti capita di conoscere qualche particolarità per shell specifiche, ti preghiamo di menzionarle.

Ho trovato il seguente nella pagina man di Ksh su Solaris:

Il significato di $ * e $ @ è identico se non quotato o usato come valore di assegnazione dei parametri o come nome di file. Tuttavia, quando usato come argomento di comando, $ * è equivalente a `` $ 1d $ 2d ... '', dove d è il primo carattere della variabile IFS, mentre $ @ è equivalente a $ 1 $ 2 ....

Ho provato a modificare la IFSvariabile, ma non modifica l'output. Forse sto facendo qualcosa di sbagliato?

Risposte:


96

Quando non sono citati $*e $@sono uguali. Non dovresti usare nessuno di questi, perché possono rompersi inaspettatamente non appena hai argomenti che contengono spazi o caratteri jolly.


"$*"si espande in una sola parola "$1c$2c...". Di solito cè uno spazio, ma in realtà è il primo personaggio di IFS, quindi può essere qualsiasi cosa tu scelga.

L'unico buon uso che abbia mai trovato è:

unisci argomenti con virgola (versione semplice)

join1() {
    typeset IFS=,
    echo "$*"
}

join1 a b c   # => a,b,c

unisci argomenti con il delimitatore specificato (versione migliore)

join2() {
    typeset IFS=$1   # typeset makes a local variable in ksh (see footnote)
    shift
    echo "$*"
}

join2 + a b c   # => a+b+c

"$@" si espande in parole separate: "$1" "$2" ...

Questo è quasi sempre quello che vuoi. Espande ogni parametro posizionale in una parola separata, il che lo rende perfetto per accettare argomenti da riga di comando o funzione e poi passarli a un altro comando o funzione. E poiché si espande usando le doppie virgolette, significa che le cose non si rompono se, diciamo, "$1"contiene uno spazio o un asterisco ( *).


Scriviamo uno script chiamato svimche funziona vimcon sudo. Faremo tre versioni per illustrare la differenza.

svim1

#!/bin/sh
sudo vim $*

svim2

#!/bin/sh
sudo vim "$*"

svim3

#!/bin/sh
sudo vim "$@"

Tutti andranno bene per casi semplici, ad esempio un singolo nome di file che non contiene spazi:

svim1 foo.txt             # == sudo vim foo.txt
svim2 foo.txt             # == sudo vim "foo.txt"
svim2 foo.txt             # == sudo vim "foo.txt"

Ma funziona $*e "$@"funziona correttamente solo se hai più argomenti.

svim1 foo.txt bar.txt     # == sudo vim foo.txt bar.txt
svim2 foo.txt bar.txt     # == sudo vim "foo.txt bar.txt"   # one file name!
svim3 foo.txt bar.txt     # == sudo vim "foo.txt" "bar.txt"

E solo "$*"e "$@"funzionare correttamente se si dispone di argomenti contenenti spazi.

svim1 "shopping list.txt" # == sudo vim shopping list.txt   # two file names!
svim2 "shopping list.txt" # == sudo vim "shopping list.txt"
svim3 "shopping list.txt" # == sudo vim "shopping list.txt"

Quindi "$@"funzionerà correttamente solo tutto il tempo.


typesetè come fare una variabile locale in ksh( bashe ashusare localinvece). Significa IFSche verrà ripristinato al suo valore precedente quando la funzione ritorna. Questo è importante, perché i comandi che esegui in seguito potrebbero non funzionare correttamente se IFSimpostato su qualcosa di non standard.


2
Spiegazione meravigliosa, grazie mille.
Rahmu,

Grazie per un esempio di utilizzo $*. Ho sempre considerato che fosse completamente inutile ... unirti al delimitatore è un buon caso d'uso.
anishsane,

35

Risposta breve: usa"$@" (nota le doppie virgolette). Le altre forme sono molto raramente utili.

"$@"è una sintassi piuttosto strana. È sostituito da tutti i parametri posizionali, come campi separati. Se non ci sono parametri posizionali ( $#è 0), quindi si "$@"espande a nulla (non una stringa vuota, ma un elenco con 0 elementi), se esiste un parametro posizionale allora "$@"è equivalente a "$1", se ci sono due parametri posizionali allora "$@"è equivalente a "$1" "$2", eccetera.

"$@"consente di passare gli argomenti di uno script o di una funzione a un altro comando. È molto utile per i wrapper che eseguono operazioni come l'impostazione di variabili di ambiente, la preparazione di file di dati, ecc. Prima di chiamare un comando con gli stessi argomenti e opzioni con cui è stato chiamato il wrapper.

Ad esempio, la seguente funzione filtra l'output di cvs -nq update. A parte il filtro di output e lo stato di ritorno (che è quello greppiuttosto che quello di cvs), chiamare cvssmalcuni argomenti si comporta come chiamare cvs -nq updatecon questi argomenti.

cvssm () { cvs -nq update "$@" | egrep -v '^[?A]'; }

"$@"si espande nell'elenco dei parametri posizionali. Nelle shell che supportano gli array, esiste una sintassi simile da espandere all'elenco di elementi dell'array: "${array[@]}"(le parentesi graffe sono obbligatorie tranne in zsh). Ancora una volta, le doppie virgolette sono in qualche modo fuorvianti: proteggono dalla divisione del campo e dalla generazione di schemi degli elementi dell'array, ma ogni elemento dell'array finisce nel suo campo.

Alcune conchiglie antiche avevano probabilmente un bug: quando non c'erano argomenti posizionali, si "$@"espandevano in un singolo campo contenente una stringa vuota, anziché in nessun campo. Ciò ha portato alla soluzione alternativa${1+"$@"} (resa famosa dalla documentazione Perl ). Sono interessate solo le versioni precedenti dell'attuale shell Bourne e l'implementazione OSF1, nessuna delle sue moderne sostituzioni compatibili (ash, ksh, bash, ...). /bin/shnon è influenzato da alcun sistema che è stato rilasciato nel 21 ° secolo di cui sono a conoscenza (a meno che non contiate la versione di manutenzione di Tru64 e anche se /usr/xpg4/bin/shè sicuro, quindi #!/bin/shsono interessati solo gli script, non gli #!/usr/bin/env shscript finché il PATH è impostato per la conformità POSIX) . In breve, questo è un aneddoto storico di cui non devi preoccuparti.


"$*"si espande sempre in una parola. Questa parola contiene i parametri posizionali, concatenati con uno spazio in mezzo. (Più in generale, il separatore è il primo carattere del valore della IFSvariabile. Se il valore di IFSè la stringa vuota, il separatore è la stringa vuota.) Se non ci sono parametri posizionali, allora "$*"è la stringa vuota, se ci sono due parametri posizionali e IFSha il suo valore predefinito quindi "$*"è equivalente a "$1 $2", ecc.

$@e le $*virgolette esterne sono equivalenti. Si espandono all'elenco dei parametri posizionali, come campi separati, come "$@"; ma ogni campo risultante viene quindi suddiviso in campi separati che vengono trattati come pattern jolly dei nomi di file, come al solito con espansioni variabili non quotate.

Ad esempio, se la directory corrente contiene tre file bar, baze foo, quindi:

set --         # no positional parameters
for x in "$@"; do echo "$x"; done  # prints nothing
for x in "$*"; do echo "$x"; done  # prints 1 empty line
for x in $*; do echo "$x"; done    # prints nothing
set -- "b* c*" "qux"
echo "$@"      # prints `b* c* qux`
echo "$*"      # prints `b* c* qux`
echo $*        # prints `bar baz c* qux`
for x in "$@"; do echo "$x"; done  # prints 2 lines: `b* c*` and `qux`
for x in "$*"; do echo "$x"; done  # prints 1 lines: `b* c* qux`
for x in $*; do echo "$x"; done    # prints 4 lines: `bar`, `baz`, `c*` and `qux`

1
Nota storica: su alcune antiche conchiglie Bourne, "$@"si è effettivamente espanso in un elenco consistente nella stringa vuota: unix.stackexchange.com/questions/68484/…
ninjalj,

25

Ecco un semplice script per dimostrare la differenza tra $*e $@:

#!/bin/bash

test_param() {
  echo "Receive $# parameters"
  echo Using '$*'

  echo
  for param in $*; do
    printf '==>%s<==\n' "$param"
  done;

  echo
  echo Using '"$*"'
  for param in "$*"; do
    printf '==>%s<==\n' "$param"
  done;

  echo
  echo Using '$@'
  for param in $@; do
    printf '==>%s<==\n' "$param"
  done;

  echo
  echo Using '"$@"';
  for param in "$@"; do
  printf '==>%s<==\n' "$param"
  done
}

IFS="^${IFS}"

test_param 1 2 3 "a b c"

Produzione:

% cuonglm at ~
% bash test.sh
Receive 4 parameters

Using $*
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==

Using "$*"
==>1^2^3^a b c<==

Using $@
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==

Using "$@"
==>1<==
==>2<==
==>3<==
==>a b c<==

Nella sintassi dell'array, non vi è alcuna differenza quando si utilizza $*o $@. Ha senso solo quando li usi con virgolette doppie "$*"e "$@".


Ottimo esempio! Puoi spiegare l'uso di IFS="^${IFS}", però?
Russ,

@Russ: ti mostra come valore è in accordo con il primo carattere in IFS.
cuonglm,

Allo stesso modo che IFS="^xxxxx"farebbe? Il ${IFS}suffisso finale mi ha fatto pensare che stavi facendo qualcosa di più complicato, come in qualche modo recuperare automaticamente l'IFS originale alla fine (ad esempio: il primo carattere si è spostato automaticamente fuori o qualcosa del genere).
Russ,

11

Il codice che hai fornito darà lo stesso risultato. Per capirlo meglio, prova questo:

foo () {
    for i in "$*"; do
        echo "$i"
    done
}

bar () {
    for i in "$@"; do
        echo "$i"
    done
}

L'output ora dovrebbe essere diverso. Ecco cosa ottengo:

$ foo() 1 2 3 4
1 2 3 4
$ bar() 1 2 3 4
1
2
3
4

Questo ha funzionato per me bash. Per quanto ne so, ksh non dovrebbe differire molto. In sostanza, la citazione $*tratterà tutto come una parola e la citazione $@tratterà l'elenco come parole separate, come si può vedere nell'esempio sopra.

Come esempio dell'uso della IFSvariabile con $*, considerare questo

fooifs () {
    IFS="c"            
    for i in "$*"; do
        echo "$i"
    done
    unset IFS          # reset to the original value
}

Ottengo questo come risultato:

$ fooifs 1 2 3 4
1c2c3c4

Inoltre, ho appena confermato che funziona allo stesso modo in ksh. Entrambi bashe kshtestati qui erano sotto OSX ma non riesco a vedere come questo avrebbe molto importanza.


"cambiato all'interno di una funzione - non influenza il globale". L'hai provato?
Mikel,

Sì, l'ho verificato. Per la massima tranquillità, aggiungerò unset IFSalla fine il comando per ripristinarlo sull'originale, ma ha funzionato per me senza problemi e facendo un echo $IFSrisultato nell'output standard che ottengo da esso. L'impostazione del IFSwithing tra parentesi graffe introduce un nuovo ambito, quindi a meno che non lo si esporti, non influirà sull'esterno IFS.
Wojtek Rzepala,

echo $IFSnon prova nulla, perché la shell vede il ,, ma poi usa la divisione delle parole usando IFS! Prova echo "$IFS".
Mikel,

buon punto. il disinserimento IFSdovrebbe risolverlo.
Wojtek Rzepala,

A meno che non IFSavesse un valore personalizzato diverso prima di chiamare la funzione. Ma sì, la maggior parte delle volte l'IFS disordinato funzionerà.
Mikel,

6

La differenza è importante quando si scrivono script che dovrebbero usare i parametri posizionali nel modo giusto ...

Immagina la seguente chiamata:

$ myuseradd -m -c "Carlos Campderrós" ccampderros

Qui ci sono solo 4 parametri:

$1 => -m
$2 => -c
$3 => Carlos Campderrós
$4 => ccampderros

Nel mio caso, myuseraddè solo un wrapper useraddche accetta gli stessi parametri, ma aggiunge una quota per l'utente:

#!/bin/bash -e

useradd "$@"
setquota -u "${!#}" 10000 11000 1000 1100

Si noti la chiamata a useradd "$@", tra $@virgolette. Questo rispetterà i parametri e li invierà come sono useradd. Se dovessi annullare la quotazione $@(o utilizzare $*anche quelli non quotati ), useradd visualizzerebbe 5 parametri, in quanto il 3o parametro che conteneva uno spazio verrebbe diviso in due:

$1 => -m
$2 => -c
$3 => Carlos
$4 => Campderrós
$5 => ccampderros

(e viceversa, se si sceglie di usare "$*", useradd vedrebbe un solo parametro: -m -c Carlos Campderrós ccampderros)

Quindi, in breve, se devi lavorare con parametri che rispettano parametri multi-parola, usa "$@".


4
   *      Expands  to  the positional parameters, starting from one.  When
          the expansion occurs within double quotes, it expands to a  sin
          gle word with the value of each parameter separated by the first
          character of the IFS special variable.  That is, "$*" is equiva
          lent to "$1c$2c...", where c is the first character of the value
          of the IFS variable.  If IFS is unset, the parameters are  sepa
          rated  by  spaces.   If  IFS  is null, the parameters are joined
          without intervening separators.
   @      Expands to the positional parameters, starting from  one.   When
          the  expansion  occurs  within  double  quotes,  each  parameter
          expands to a separate word.  That is, "$@" is equivalent to "$1"
          "$2"  ...   If the double-quoted expansion occurs within a word,
          the expansion of the first parameter is joined with  the  begin
          ning  part  of  the original word, and the expansion of the last
          parameter is joined with the last part  of  the  original  word.
          When  there  are no positional parameters, "$@" and $@ expand to
          nothing (i.e., they are removed).

// man bash . è ksh, infair, comportamento simile.


2

Parlando di differenze tra zshe bash:

Con le virgolette intorno $@e $*, zshe bashcomportarsi allo stesso modo, e immagino che il risultato sia abbastanza standard tra tutte le shell:

 $ f () { for i in "$@"; do echo +"$i"+; done; }; f 'a a' 'b' ''
 +a a+
 +b+
 ++
 $ f () { for i in "$*"; do echo +"$i"+; done; }; f 'a a' 'b' ''
 +a a b +

Senza virgolette, i risultati sono gli stessi per $*e $@, ma diversi in bashe in zsh. In questo caso zshmostra alcuni comportamenti strani:

bash$ f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''
+a+
+a+
+b+
zsh% f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''  
+a a+
+b+

(Zsh di solito non divide i dati testuali usando IFS, a meno che non sia esplicitamente richiesto, ma nota che qui l'argomento vuoto è inaspettatamente mancante nell'elenco.)


1
In zsh, $@non è speciale sotto questo aspetto: si $xespande al massimo in una parola, ma le variabili vuote si espandono in nulla (non una parola vuota). Prova print -l a $foo bcon foovuoto o non definito.
Gilles 'SO- smetti di essere malvagio' il

0

Una delle risposte dice $*(che io considero uno "splat") è raramente utile.

Cerco google con G() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }

Dato che gli URL sono spesso divisi con un +, ma la mia tastiera rende   più facile da raggiungere di +, $*+ $IFSvale la pena.

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.