Qual é a diferença entre $ * e $ @?

52

Considere o seguinte código:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Produz:

1 2 3 4

1 2 3 4

Estou usando o Ksh88, mas também estou interessado em outros shells comuns. Se você conhece alguma particularidade para conchas específicas, por favor mencione-as.

Encontrei o seguinte na página do manual do Ksh no Solaris:

The meaning of $* and $@ is identical when not quoted or when used as a parameter assignment value or as a file name. However, when used as a command argument, $* is equivalent to ''$1d$2d...'', where d is the first character of the IFS variable, whereas $@ is equivalent to $1 $2 ....

Eu tentei modificar a variável IFS , mas ela não modifica a saída. Talvez eu esteja fazendo algo errado?

    
por rahmu 25.06.2012 / 11:51

7 respostas

66

Quando não são citados, $* e $@ são iguais. Você não deve usar nenhum desses, porque eles podem quebrar inesperadamente assim que você tiver argumentos contendo espaços ou curingas.

"$*" expande para uma única palavra "$1c$2c..." . Geralmente c é um espaço, mas na verdade é o primeiro caractere de IFS , então pode ser qualquer coisa que você escolher.

O único bom uso que encontrei para ele é:

associa argumentos com vírgula (versão simples)

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

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

associa argumentos com o delimitador especificado (versão melhorada)

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

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

"$@" se expande para separar palavras: "$1" "$2" ...

Isso é quase sempre o que você quer. Ele expande cada parâmetro posicional para uma palavra separada, o que o torna perfeito para usar argumentos de linha de comando ou de função e depois passá-los para outro comando ou função. E porque ele se expande usando aspas duplas, isso significa que as coisas não quebram se, digamos, "$1" contiver um espaço ou um asterisco ( * ).

Vamos escrever um script chamado svim que executa vim com sudo . Vamos fazer três versões para ilustrar a diferença.

svim1

#!/bin/sh
sudo vim $*

svim2

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

svim3

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

Todos estarão bem para casos simples, por ex. um único nome de arquivo que não contém espaços:

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

Mas apenas $* e "$@" funcionam corretamente se você tiver vários argumentos.

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 somente "$*" e "$@" funcionarão corretamente se você tiver argumentos contendo espaços.

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"

Portanto, apenas "$@" funcionará corretamente o tempo todo.

typeset é como criar uma variável local em ksh ( bash e ash use local ). Isso significa que IFS será restaurado para seu valor anterior quando a função retornar. Isso é importante, porque os comandos executados posteriormente podem não funcionar corretamente se IFS estiver definido como algo fora do padrão.

    
por 25.06.2012 / 17:25
31

Resposta curta: use "$@" (observe as aspas duplas). As outras formas raramente são úteis.

"$@" é uma sintaxe bastante estranha. Ele é substituído por todos os parâmetros posicionais, como campos separados. Se não houver parâmetros posicionais ( $# é 0), então "$@" se expande para nada (não uma string vazia, mas uma lista com 0 elementos), se houver um parâmetro posicional, então "$@" é equivalente a "$1" , se houver dois parâmetros posicionais, então "$@" é equivalente a "$1" "$2" , etc.

"$@" permite passar os argumentos de um script ou função para outro comando. É muito útil para wrappers que fazem coisas como configurar variáveis de ambiente, preparar arquivos de dados, etc. antes de chamar um comando com os mesmos argumentos e opções que o wrapper foi chamado.

Por exemplo, a função a seguir filtra a saída de cvs -nq update . Além da filtragem de saída e do status de retorno (que é o de grep em vez de cvs ), chamar cvssm em alguns argumentos se comporta como chamar cvs -nq update com esses argumentos.

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

"$@" expande para a lista de parâmetros posicionais. Em shells que suportam matrizes, há uma sintaxe semelhante para expandir para a lista de elementos da matriz: "${array[@]}" (as chaves são obrigatórias, exceto em zsh). Novamente, as aspas duplas são um tanto enganadoras: elas protegem contra a divisão de campos e a geração de padrões dos elementos da matriz, mas cada elemento da matriz termina em seu próprio campo.

Algumas shells antigas tinham o que é discutivelmente um bug: quando não havia argumentos posicionais, "$@" foi expandido para um único campo contendo uma string vazia, ao invés de em nenhum campo. Isso levou à solução alternativa ${1+"$@"} (criada famoso via documentação do Perl ). Apenas as versões mais antigas do Bourne shell e a implementação do OSF1 são afetadas, e nenhuma de suas substituições compatíveis modernas (ash, ksh, bash,…) são. /bin/sh não é afetado em nenhum sistema que foi lançado no século 21 que eu conheça (a menos que você conte a versão de manutenção Tru64, e até mesmo /usr/xpg4/bin/sh é seguro, então apenas #!/bin/sh script são afetados, não #!/usr/bin/env sh scripts contanto que seu PATH esteja configurado para conformidade com POSIX). Em suma, esta é uma anedota histórica que você não precisa se preocupar.

"$*" sempre se expande para uma palavra. Esta palavra contém os parâmetros posicionais, concatenados com um espaço entre eles. (Mais geralmente, o separador é o primeiro caractere do valor da variável IFS . Se o valor de IFS for a sequência vazia, o separador será a sequência vazia.) Se não houver parâmetros posicionais, "$*" é a string vazia, se houver dois parâmetros posicionais e IFS tiver seu valor padrão, então "$*" é equivalente a "$1 $2" , etc.

$@ e $* as cotas externas são equivalentes. Eles se expandem para a lista de parâmetros posicionais, como campos separados, como "$@" ; mas cada campo resultante é dividido em campos separados que são tratados como padrões de caractere curinga de nome de arquivo, como de costume, com expansões de variáveis sem aspas.

Por exemplo, se o diretório atual contiver três arquivos bar , baz e foo , então:

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'
    
por 09.10.2013 / 03:20
24

Aqui está um script simples para demonstrar a diferença entre $* 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"

Saída:

% 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<==

Na sintaxe de matriz, não há diferença ao usar $* ou $@ . Isso só faz sentido quando você os usa com aspas duplas "$*" e "$@" .

    
por 08.10.2013 / 19:18
11

O código que você forneceu dará o mesmo resultado. Para entender melhor, tente o seguinte:

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

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

A saída deve ser diferente agora. Aqui está o que eu recebo:

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

Isso funcionou para mim em bash . Tanto quanto eu sei, o ksh não deve diferir muito. Essencialmente, citando $* tratará tudo como uma palavra, e citando $@ tratará a lista como palavras separadas, como pode ser visto no exemplo acima.

Como exemplo de uso da variável IFS com $* , considere isso

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

Eu entendo isso como resultado:

$ fooifs 1 2 3 4
1c2c3c4

Além disso, confirmei que funciona da mesma forma em ksh . Ambos bash e ksh testados aqui estavam no OSX, mas não vejo como isso importaria muito.

    
por 25.06.2012 / 14:26
6

A diferença é importante ao escrever scripts que devem usar os parâmetros posicionais da maneira correta ...

Imagine a seguinte ligação:

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

Aqui há apenas 4 parâmetros:

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

No meu caso, myuseradd é apenas um wrapper para useradd que aceita os mesmos parâmetros, mas adiciona uma cota para o usuário:

#!/bin/bash -e

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

Observe a chamada para useradd "$@" , com $@ cotado. Isso respeitará os parâmetros e os enviará como estão para useradd . Se você fosse unquotar $@ (ou usar $* também sem aspas), useradd veria 5 parâmetros, já que o terceiro parâmetro que continha um espaço seria dividido em dois:

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

(e, inversamente, se você usar "$*" , useradd verá apenas um parâmetro: -m -c Carlos Campderrós ccampderros )

Então, em suma, se você precisa trabalhar com parâmetros que respeitem parâmetros com várias palavras, use "$@" .

    
por 25.06.2012 / 16:55
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).

// homem bash . é ksh, afair, comportamento similar.

    
por 25.06.2012 / 12:13
2

Falando sobre as diferenças entre zsh e bash :

Com aspas em torno de $@ e $* , zsh e bash se comportam da mesma forma, e acho que o resultado é bastante normal entre todos os shells:

 $ 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 +

Sem as aspas, os resultados são os mesmos para $* e $@ , mas diferentes em bash e em zsh . Nesse caso, zsh mostra algum comportamento estranho:

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 geralmente não divide dados textuais usando o IFS, a menos que explicitamente solicitado, mas observe que aqui o argumento vazio está inesperadamente ausente na lista.)

    
por 25.06.2012 / 14:56