Considere o seguinte código:
foo () {
echo $*
}
bar () {
echo $@
}
foo 1 2 3 4
bar 1 2 3 4
Ele 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 de shells específicos, mencione-os.
Encontrei o seguinte na página de manual do Ksh no Solaris:
O significado de $* e $@ é idêntico quando não está entre aspas ou quando usado como valor de atribuição de parâmetro ou como nome de arquivo. Entretanto, quando usado como argumento de comando, $* é equivalente a ``$1d$2d...'', onde d é o primeiro caractere da variável IFS, enquanto $@ é equivalente a $1 $2 ....
Tentei modificar a IFS
variável, mas isso não modifica a saída. Talvez eu esteja fazendo algo errado?
Responder1
Quando não são citados $*
e $@
são iguais. Você não deve usar nenhum deles, pois eles podem quebrar inesperadamente assim que você tiver argumentos contendo espaços ou curingas.
"$*"
expande para uma única palavra "$1c$2c..."
. c
era um espaço no shell Bourne, mas agora é o primeiro caractere IFS
nos shells modernos do tipo Bourne (de ksh e especificado pelo POSIX para sh), então pode ser qualquer coisa¹ que você escolher.
O único bom uso que encontrei para isso é:
juntar argumentos com vírgula(versão simples)
function join1 {
typeset IFS=, # typeset makes a local variable in ksh²
print -r -- "$*" # using print instead of unreliable echo³
}
join1 a b c # => a,b,c
juntar argumentos com o delimitador especificado(melhor versão)
function join2 {
typeset IFS="$1"
shift
print -r -- "$*"
}
join2 + a b c # => a+b+c
"$@"
expande para palavras separadas:"$1"
"$2"
...
Quase sempre é isso 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 de função e depois passá-los para outro comando ou função. E porque se expande usando aspas duplas, significa que as coisas não quebram se, digamos, "$1"
contiverem um espaço ou um asterisco ( *
) 4 .
Vamos escrever um script chamado svim
que rode vim
com 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 servirão para casos simples, por exemplo, um único nome de arquivo que não contenha espaços:
svim1 foo.txt # == sudo vim foo.txt
svim2 foo.txt # == sudo vim "foo.txt"
svim2 foo.txt # == sudo vim "foo.txt"
Mas só $*
funciona "$@"
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 "$*"
funciona "$@"
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"
Então só "$@"
funcionará corretamente o tempo todo.
¹ embora tenha cuidado em alguns shells, ele não funciona para caracteres multibyte.
² typeset
que é usado para definir tipos e atributos de variáveis também torna uma variável local em ksh
4 (em ksh93, isso é apenas para funções definidas com a function f {}
sintaxe Korn, não com a sintaxe Bourne f() ...
). Isso significa que aqui IFS
será restaurado ao valor anterior quando a função retornar. Isso é importante porque os comandos executados posteriormente podem não funcionar conforme o esperado se IFS
estiverem configurados para algo fora do padrão e você se esquecer de citar algumas expansões.
³ echo
irá ou pode falhar ao imprimir seus argumentos corretamente se o primeiro começar com -
ou contiver barras invertidas, print
pode ser instruído a não fazer processamento de barra invertida -r
e a se proteger contra argumentos começando com -
ou +
com o delimitador de opção --
(ou ). seria a alternativa padrão, mas observe que ksh88 e pdksh e alguns de seus derivados ainda não possuem recursos integrados.-
printf '%s\n' "$*"
printf
4 Observe que "$@"
não funcionou corretamente no Bourne Shell e no ksh88 quando $IFS
não continha o caractere de espaço, tão efetivamente foi implementado quanto o parâmetro posicional sendo unido a espaços "sem aspas" e o resultado sujeito a $IFS
divisão. As primeiras versões do shell Bourne também tinham aquele bug que "$@"
era expandido para um argumento vazio quando não havia parâmetro posicional, que é uma das razões pelas quais você às vezes vê ${1+"$@"}
no lugar de "$@"
. Nenhum desses bugs afeta os shells modernos do tipo Bourne.
5 O shell Almquist e bosh
tenha local
para isso. bash
, yash
e zsh
também tem typeset
, alias para local
(também declare
em bash e zsh) com a ressalva de que in bash
, local
só pode ser usado em uma função.
Responder2
Resposta curta:usar"$@"
(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), 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 que você passe 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 é de grep
e não de cvs
), chamar cvssm
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 arrays, existe uma sintaxe semelhante para expandir para a lista de elementos do array: "${array[@]}"
(os colchetes são obrigatórios, exceto em zsh). Novamente, as aspas duplas são um tanto enganosas: elas protegem contra a divisão de campos e a geração de padrões dos elementos do array, mas cada elemento do array termina em seu próprio campo.
Alguns shells antigos tinham o que é indiscutivelmente um bug: quando não havia argumentos posicionais, "$@"
expandiam-se para um único campo contendo uma string vazia, em vez de nenhum campo. Isto levou aoGambiarra${1+"$@"}
(feitofamoso através da documentação Perl). Apenas as versões mais antigas do Bourne Shell real e a implementação do OSF1 são afetadas, nenhum de seus substitutos modernos compatíveis (ash, ksh, bash,…) é. /bin/sh
não é afetado em nenhum sistema lançado no século 21 que eu conheça (a menos que você conte a versão de manutenção Tru64, e mesmo que seja /usr/xpg4/bin/sh
seguro, apenas #!/bin/sh
scripts são afetados, não #!/usr/bin/env sh
scripts, desde que seu PATH esteja configurado para conformidade com POSIX) . Resumindo, 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 entre eles. (Mais geralmente, o separador é o primeiro caractere do valor da IFS
variável. Se o valor de IFS
for a sequência vazia, o separador será a sequência vazia.) Se não houver parâmetros posicionais, então "$*"
será a sequência vazia, se houver dois parâmetros posicionais e IFS
tem seu valor padrão, entã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 é então 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 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`
Responder3
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 array, não há diferença ao usar $*
ou $@
. Só faz sentido quando você os usa com aspas duplas "$*"
e "$@"
.
Responder4
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 wrapper 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 $@
aspas. Isso respeitará os parâmetros e os enviará como estão para useradd
. Se você tirasse aspas $@
(ou usasse $*
também sem aspas), useradd veria5parâ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 inversamente, se você usasse "$*"
, useradd veria apenas um parâmetro: -m -c Carlos Campderrós ccampderros
)
Então, resumindo, se você precisar trabalhar com parâmetros respeitando parâmetros multipalavras, use "$@"
.