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

73

Considere o seguinte código:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Emite:

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 reservatórios específicos, mencione-os.

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

O significado de $ * e $ @ é idêntico quando não está entre aspas ou quando é usado como um valor de atribuição de parâmetro ou como um nome de arquivo. No entanto, quando usado como argumento de comando, $ * é equivalente a `` $ 1d $ 2d ... '', em que d é o primeiro caractere da variável IFS, enquanto $ @ é equivalente a $ 1 $ 2 ....

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

rahmu
fonte

Respostas:

96

Quando não estão entre aspas $*e $@são iguais. Você não deve usar nenhum deles, pois eles podem quebrar inesperadamente assim que houver 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, portanto pode ser qualquer coisa que você escolher.

O único bom uso que eu já encontrei para isso é:

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

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

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

juntar 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

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

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


Vamos escrever um script chamado svimque roda vimcom sudo. Faremos 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 eles serão adequados para casos simples, por exemplo, 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 "$@"funcione 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 "$@"funcione corretamente se você tiver argumentos que contenham 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 ksh( bashe ashusar local). Isso significa IFSque será restaurado ao seu valor anterior quando a função retornar. Isso é importante, porque os comandos executados posteriormente podem não funcionar corretamente se IFSestiverem configurados para algo fora do padrão.

Mikel
fonte
2
Explicação maravilhosa, muito obrigado.
rahmu
Obrigado por um exemplo de uso $*. Eu sempre estava considerando que era completamente inútil ... juntar-se ao delimitador é um bom caso de uso.
anishsane
35

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

"$@"é uma sintaxe bastante estranha. É substituído por todos os parâmetros posicionais, como campos separados. Se não houver parâmetros posicionais ( $#é 0), ele se "$@"expande para nada (não é uma sequência vazia, mas uma lista com 0 elementos); se houver um parâmetro posicional, "$@"é equivalente a "$1"; se houver dois parâmetros posicionais, "$@"é 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 definir variáveis ​​de ambiente, preparar arquivos de dados etc. antes de chamar um comando com os mesmos argumentos e opções com os quais 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 grepe não o de cvs), chamar cvssmalguns argumentos se comporta como chamar cvs -nq updatecom esses argumentos.

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

"$@"expande para a lista de parâmetros posicionais. Nos shells que suportam matrizes, existe uma sintaxe semelhante para expandir a lista de elementos da matriz: "${array[@]}"(os colchetes são obrigatórios, exceto no zsh). Novamente, as aspas duplas são um pouco 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 conchas antigas tinham o que é indiscutivelmente um bug: quando não havia argumentos posicionais, "$@"expandíamos para um único campo contendo uma string vazia, em vez de para nenhum campo. Isso levou à solução alternativa${1+"$@"} (que ficou famosa através da documentação do Perl ). Somente versões mais antigas do shell Bourne real e da implementação OSF1 são afetadas, nenhuma de suas substituições compatíveis modernas (ash, ksh, bash,…) são. /bin/shnão é afetado em nenhum sistema lançado no século XXI que eu conheça (a menos que você conte a versão de manutenção do Tru64, e mesmo que /usr/xpg4/bin/shseja seguro, apenas os #!/bin/shscripts serão afetados, e não os #!/usr/bin/env shscripts, desde que seu PATH esteja configurado para conformidade com POSIX) . Em resumo, esta é uma anedota histórica com a qual 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 intermediário. (De maneira mais geral, o separador é o primeiro caractere do valor da IFSvariável. Se o valor de IFSfor a sequência vazia, o separador será a sequência vazia.) Se não houver parâmetros posicionais, "$*"será a sequência vazia, se houver dois parâmetros posicionais e IFSseu valor padrão "$*"é equivalente a "$1 $2"etc.

$@e $*aspas 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 curinga de nome de arquivo, como de costume com expansões de variáveis ​​não citadas.

Por exemplo, se o diretório atual contém três arquivos bar, baze foo, em seguida:

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`
Gilles 'SO- parar de ser mau'
fonte
11
Nota histórica: em algumas conchas antigas Bourne, "$@"de fato, expandir-se para um consisiting lista da cadeia vazia: unix.stackexchange.com/questions/68484/...
ninjalj
25

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"

Resultado:

% 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 da matriz, não há diferença ao usar $*ou $@. Só faz sentido quando você as usa com aspas duplas "$*"e "$@".

cuonglm
fonte
Excelente exemplo! Você pode explicar o uso de IFS="^${IFS}"?
Russ
@ Russ: Mostra como o valor é concatenado com o primeiro caractere IFS.
cuonglm 16/09/16
Da mesma maneira que IFS="^xxxxx"faria? O ${IFS}sufixo à direita me fez pensar que você estava fazendo algo mais complicado, como de alguma forma recuperar automaticamente o IFS original no final (por exemplo: o primeiro caractere mudou automaticamente ou algo assim).
Russ
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 agora deve ser diferente. 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 bash. Até onde eu sei, o ksh não deve diferir muito. Essencialmente, citar $*tratará tudo como uma palavra e citará $@a lista como palavras separadas, como pode ser visto no exemplo acima.

Como um exemplo de uso da IFSvariável with $*, considere isso

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

Eu recebo isso como resultado:

$ fooifs 1 2 3 4
1c2c3c4

Além disso, acabei de confirmar que funciona da mesma maneira ksh. Ambos bashe kshtestados aqui estavam sob OSX, mas não vejo como isso importaria muito.

Wojtek Rzepala
fonte
"alterado dentro de uma função - não afeta o global". Você testou isso?
25412 Mikel
Sim, eu verifiquei isso. Para maior tranquilidade, adicionarei o unset IFSfinal para redefini-lo ao original, mas funcionou para mim sem problemas e echo $IFSresultou na saída padrão que obtive dele. Definir os IFScolchetes dentro de um encargo apresenta um novo escopo; portanto, a menos que você o exporte, isso não afetará o exterior IFS.
Wojtek Rzepala
echo $IFSnão prova nada, porque o shell vê o ,, mas faz a divisão de palavras usando IFS! Tente echo "$IFS".
25412 Mikel
bom ponto. desarmar IFSdeve resolver isso.
Wojtek Rzepala
A menos que IFStenha um valor personalizado diferente antes de chamar a função. Mas sim, na maioria das vezes, a desabilitação do IFS funcionará.
25412 Mikel
6

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

Imagine a seguinte chamada:

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

Aqui existem apenas 4 parâmetros:

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

No meu caso, myuseraddé apenas um invólucro para useraddque 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 $@citado. Isso respeitará os parâmetros e os enviará como estão useradd. Se você tivesse que citar $@(ou usar $*também não citado), o useradd veria 5 parâmetros, pois 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 vice-versa, se você fosse usar "$*", useradd iria ver apenas um parâmetro: -m -c Carlos Campderrós ccampderros)

Portanto, resumindo, se você precisar trabalhar com parâmetros que respeitem parâmetros de várias palavras, use "$@".

Carlos Campderrós
fonte
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, justo, comportamento semelhante.

pressa
fonte
2

Falando sobre diferenças entre zshe bash:

Com aspas ao redor $@e $*, zshe bashse comportam da mesma forma, acho que o resultado é bastante comum entre todas as conchas:

 $ 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 aspas, os resultados são os mesmos para $*e $@, mas diferentes dentro bashe dentro zsh. Neste caso, zshmostra 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+

(O Zsh geralmente não divide dados textuais usando o IFS, a menos que seja solicitado explicitamente, mas observe que aqui o argumento vazio está inesperadamente ausente na lista.)

Stéphane Gimenez
fonte
11
No zsh, $@não é especial nesse sentido: $xexpande para no máximo uma palavra, mas variáveis ​​vazias se expandem para nada (não é uma palavra vazia). Tente print -l a $foo bcom foovazio ou indefinido.
Gilles 'SO- stop be evil'
0

Uma das respostas diz $*(que considero um "splat") raramente é útil.

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

Como os URLs geralmente são divididos com um +, mas meu teclado   facilita o alcance do que +, $*+ $IFSvale a pena.

isomorfismos
fonte