Considere el siguiente código:
foo () {
echo $*
}
bar () {
echo $@
}
foo 1 2 3 4
bar 1 2 3 4
Produce:
1 2 3 4
1 2 3 4
Estoy usando Ksh88, pero también estoy interesado en otros shells comunes. Si conoce alguna particularidad de proyectiles específicos, menciónela.
Encontré lo siguiente en la página de manual de Ksh en Solaris:
El significado de $* y $@ es idéntico cuando no se citan o cuando se utilizan como valor de asignación de parámetro o como nombre de archivo. Sin embargo, cuando se usa como argumento de comando, $* es equivalente a ``$1d$2d...'', donde d es el primer carácter de la variable IFS, mientras que $@ es equivalente a $1 $2 ....
Intenté modificar la IFS
variable, pero no modifica la salida. ¿Quizás estoy haciendo algo mal?
Respuesta1
Cuando no están cotizados, $*
y $@
son iguales. No deberías usar ninguno de estos, porque pueden fallar inesperadamente tan pronto como tengas argumentos que contengan espacios o comodines.
"$*"
se expande a una sola palabra "$1c$2c..."
. c
era un espacio en el shell Bourne, pero ahora es el primer carácter IFS
en los shells modernos tipo Bourne (de ksh y especificado por POSIX para sh), por lo que puede ser cualquier cosa¹ que elijas.
El único buen uso que le he encontrado es:
unir argumentos con coma(versión sencilla)
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
unir argumentos con el delimitador especificado(mejor versión)
function join2 {
typeset IFS="$1"
shift
print -r -- "$*"
}
join2 + a b c # => a+b+c
"$@"
se expande para separar palabras:"$1"
"$2"
...
Esto es casi siempre lo que quieres. Expande cada parámetro posicional a una palabra separada, lo que lo hace perfecto para tomar argumentos de línea de comando o función y luego pasarlos a otro comando o función. Y debido a que se expande usando comillas dobles, significa que las cosas no se rompen si, por ejemplo, "$1"
contiene un espacio o un asterisco ( *
) 4 .
Escribamos un script llamado svim
que se ejecute vim
con sudo
. Haremos tres versiones para ilustrar la diferencia.
svim1
#!/bin/sh
sudo vim $*
svim2
#!/bin/sh
sudo vim "$*"
svim3
#!/bin/sh
sudo vim "$@"
Todos ellos estarán bien para casos simples, por ejemplo, un único nombre de archivo que no contenga espacios:
svim1 foo.txt # == sudo vim foo.txt
svim2 foo.txt # == sudo vim "foo.txt"
svim2 foo.txt # == sudo vim "foo.txt"
Pero solo $*
funciona "$@"
correctamente si tiene múltiples 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"
Y solo "$*"
funciona "$@"
correctamente si tiene argumentos que contienen espacios.
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"
Así solo "$@"
funcionará correctamente todo el tiempo.
¹ aunque tenga cuidado con algunos shells, no funciona para caracteres multibyte.
² typeset
que se usa para establecer tipos y atributos de variables también convierte una variable en local en ksh
4 (en ksh93, eso es solo para funciones definidas con la function f {}
sintaxis Korn, no con la sintaxis Bourne f() ...
). Significa que aquí IFS
se restaurará a su valor anterior cuando regrese la función. Esto es importante, porque es posible que los comandos que ejecute después no funcionen como se esperaba si IFS
están configurados en algo no estándar y olvidó citar algunas expansiones.
³ echo
imprimirá o puede no imprimir sus argumentos correctamente si el primero comienza con -
o contiene barras invertidas, print
se le puede indicar que no realice el procesamiento de barras invertidas -r
y que se proteja contra argumentos que comiencen con -
o +
con el delimitador de opción --
(o -
). printf '%s\n' "$*"
sería la alternativa estándar, pero tenga en cuenta que ksh88 y pdksh y algunos de sus derivados aún no están printf
integrados.
4 Tenga en cuenta que "$@"
no funcionó correctamente en el shell Bourne y ksh88 cuando $IFS
no contenía el carácter de espacio, ya que efectivamente se implementó como el parámetro posicional que se unía con espacios "sin comillas" y el resultado estaba sujeto a $IFS
división. Las primeras versiones del shell Bourne también tenían ese error que "$@"
se expandía a un argumento vacío cuando no había ningún parámetro posicional, que es una de las razones por las que a veces se ve ${1+"$@"}
en lugar de "$@"
. Ninguno de esos errores afecta a los shells modernos tipo Bourne.
5 El caparazón de Almquist y lo bosh
tenemos en su lugar. , y también tiene un alias (también en bash y zsh) con la advertencia de que en , solo se puede usar en una función.local
bash
yash
zsh
typeset
local
declare
bash
local
Respuesta2
Respuesta corta:usar"$@"
(tenga en cuenta las comillas dobles). Las otras formas rara vez son útiles.
"$@"
Es una sintaxis bastante extraña. Se reemplaza por todos los parámetros posicionales, como campos separados. Si no hay parámetros posicionales ( $#
es 0), entonces "$@"
se expande a nada (no una cadena vacía, sino una lista con 0 elementos), si hay un parámetro posicional entonces "$@"
es equivalente a "$1"
, si hay dos parámetros posicionales entonces "$@"
es equivalente a "$1" "$2"
, etc.
"$@"
le permite pasar los argumentos de un script o función a otro comando. Es muy útil para contenedores que hacen cosas como configurar variables de entorno, preparar archivos de datos, etc. antes de llamar a un comando con los mismos argumentos y opciones con los que se llamó al contenedor.
Por ejemplo, la siguiente función filtra la salida de cvs -nq update
. Aparte del filtrado de salida y el estado de retorno (que es el de grep
en lugar de cvs
), llamar cvssm
a algunos argumentos se comporta como llamar cvs -nq update
con estos argumentos.
cvssm () { cvs -nq update "$@" | egrep -v '^[?A]'; }
"$@"
se expande a la lista de parámetros posicionales. En shells que admiten matrices, existe una sintaxis similar para expandir la lista de elementos de la matriz: "${array[@]}"
(las llaves son obligatorias excepto en zsh). Nuevamente, las comillas dobles son algo engañosas: protegen contra la división de campos y la generación de patrones de los elementos de la matriz, pero cada elemento de la matriz termina en su propio campo.
Algunos shells antiguos tenían lo que podría decirse que es un error: cuando no había argumentos posicionales, "$@"
se expandían a un solo campo que contenía una cadena vacía, en lugar de ningún campo. Esto llevó a lasolución alterna${1+"$@"}
(hechofamoso a través de la documentación de Perl). Sólo las versiones anteriores del shell Bourne real y la implementación OSF1 se ven afectadas, ninguno de sus reemplazos modernos compatibles (ash, ksh, bash,…). /bin/sh
no se ve afectado en ningún sistema lanzado en el siglo XXI que yo sepa (a menos que cuente la versión de mantenimiento de Tru64, e incluso allí /usr/xpg4/bin/sh
es seguro, por lo que solo #!/bin/sh
se ven afectados los scripts, no #!/usr/bin/env sh
los scripts, siempre que su RUTA esté configurada para cumplir con POSIX) . En definitiva, se trata de una anécdota histórica de la que no debes preocuparte.
"$*"
siempre se expande a una palabra. Esta palabra contiene los parámetros posicionales, concatenados con un espacio entre ellos. (De manera más general, el separador es el primer carácter del valor de la IFS
variable. Si el valor de IFS
es la cadena vacía, el separador es la cadena vacía). Si no hay parámetros posicionales, entonces "$*"
es la cadena vacía, si hay dos parámetros posicionales y IFS
tiene su valor predeterminado, entonces "$*"
es equivalente a "$1 $2"
, etc.
$@
y $*
las comillas exteriores son equivalentes. Se expanden a la lista de parámetros posicionales, como campos separados, como "$@"
; pero cada campo resultante se divide en campos separados que se tratan como patrones comodín de nombre de archivo, como es habitual con las expansiones de variables sin comillas.
Por ejemplo, si el directorio actual contiene tres archivos y bar
, entonces:baz
foo
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`
Respuesta3
Aquí hay un script simple para demostrar la diferencia entre $*
y $@
:
#!/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"
Producción:
% 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<==
En la sintaxis de matrices, no hay diferencia al usar $*
o $@
. Sólo tiene sentido cuando los usas entre comillas dobles "$*"
y "$@"
.
Respuesta4
La diferencia es importante al escribir scripts que deben usar los parámetros posicionales de la manera correcta...
Imagine la siguiente llamada:
$ myuseradd -m -c "Carlos Campderrós" ccampderros
Aquí solo hay 4 parámetros:
$1 => -m
$2 => -c
$3 => Carlos Campderrós
$4 => ccampderros
En mi caso, myuseradd
es solo un contenedor useradd
que acepta los mismos parámetros, pero agrega una cuota para el usuario:
#!/bin/bash -e
useradd "$@"
setquota -u "${!#}" 10000 11000 1000 1100
Observe la llamada a useradd "$@"
, con $@
citado. Esto respetará los parámetros y los enviará tal como están useradd
. Si eliminara las comillas $@
(o usara $*
también las comillas), useradd vería5parámetros, ya que el tercer parámetro que contenía un espacio se dividiría en dos:
$1 => -m
$2 => -c
$3 => Carlos
$4 => Campderrós
$5 => ccampderros
(y a la inversa, si usara "$*"
, useradd solo vería un parámetro: -m -c Carlos Campderrós ccampderros
)
En resumen, si necesita trabajar con parámetros que respeten los parámetros de varias palabras, utilice "$@"
.