¿Cuál es la diferencia entre $* y $@?

¿Cuál es la diferencia entre $* y $@?

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 IFSvariable, 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...". cera un espacio en el shell Bourne, pero ahora es el primer carácter IFSen 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 svimque se ejecute vimcon 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.

² typesetque se usa para establecer tipos y atributos de variables también convierte una variable en local en ksh4 (en ksh93, eso es solo para funciones definidas con la function f {}sintaxis Korn, no con la sintaxis Bourne f() ...). Significa que aquí IFSse 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 IFSestán configurados en algo no estándar y olvidó citar algunas expansiones.

³ echoimprimirá o puede no imprimir sus argumentos correctamente si el primero comienza con -o contiene barras invertidas, printse le puede indicar que no realice el procesamiento de barras invertidas -ry 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 printfintegrados.

4 Tenga en cuenta que "$@"no funcionó correctamente en el shell Bourne y ksh88 cuando $IFSno 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 $IFSdivisió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 boshtenemos 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.localbashyashzshtypesetlocaldeclarebashlocal

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 grepen lugar de cvs), llamar cvssma algunos argumentos se comporta como llamar cvs -nq updatecon 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/shno 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/shes seguro, por lo que solo #!/bin/shse ven afectados los scripts, no #!/usr/bin/env shlos 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 IFSvariable. Si el valor de IFSes 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 IFStiene 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:bazfoo

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, myuseraddes solo un contenedor useraddque 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 "$@".

información relacionada