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

73

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 para conchas específicas, por favor mencionelas.

Encontré lo siguiente en la página de manual de Ksh en Solaris:

El significado de $ * y $ @ es idéntico cuando no se cita o cuando se usa como un valor de asignación de parámetro o como un 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. Tal vez estoy haciendo algo mal?

rahmu
fuente

Respuestas:

96

Cuando no se citan, $*y $@son lo mismo. No debe usar ninguno de estos, ya que pueden romperse inesperadamente tan pronto como tenga argumentos que contengan espacios o comodines.


"$*"se expande a una sola palabra "$1c$2c...". Por clo general, es un espacio, pero en realidad es el primer personaje de IFS, por lo que puede ser cualquier cosa que elija.

El único buen uso que he encontrado es:

unir argumentos con coma (versión simple)

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

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

unir argumentos con el delimitador especificado (mejor versión)

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 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 ( *).


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 estarán bien para casos simples, por ejemplo, un solo 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 $*y "$@"funciona correctamente si tienes 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 "$*"y "$@"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"

Entonces solo "$@"funcionará correctamente todo el tiempo.


typesetes cómo hacer una variable local en ksh( bashy ashusar localen su lugar). Significa IFSque se restaurará a su valor anterior cuando la función regrese. Esto es importante, porque los comandos que ejecute después podrían no funcionar correctamente si IFSestá configurado en algo no estándar.

Mikel
fuente
2
Maravillosa explicación, muchas gracias.
rahmu
Gracias por un ejemplo de uso $*. Siempre lo consideré completamente inútil ... unirme con un delimitador es un buen caso de uso.
anishsane
35

Respuesta corta: uso"$@" (tenga en cuenta las comillas dobles). Las otras formas son muy raramente ú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), se "$@"expande a nada (no es 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 los contenedores que hacen cosas como establecer 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. Además del filtrado de salida y el estado de retorno (que es el de grepmás que el de cvs), invocar cvssmalgunos 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 los shells que admiten matrices, hay una sintaxis similar para expandir a la lista de elementos de la matriz: "${array[@]}"(las llaves son obligatorias excepto en zsh). Una vez más, 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.

Algunas conchas antiguas tenían lo que podría decirse que era 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 a ningún campo. Esto condujo a la solución${1+"$@"} (que se hizo famosa a través de la documentación de Perl ). Solo se ven afectadas las versiones anteriores del shell Bourne real y la implementación de OSF1, ninguno de sus reemplazos modernos compatibles (ash, ksh, bash, ...) se ven afectados. /bin/shno se ve afectado en ningún sistema que se lanzó en el siglo XXI que conozca (a menos que cuente la versión de mantenimiento Tru64, e incluso allí /usr/xpg4/bin/shes seguro, por lo que solo #!/bin/shse ven afectados los #!/usr/bin/env shguiones , no los guiones, siempre que su RUTA esté configurada para el cumplimiento POSIX) . En resumen, esta es una anécdota histórica de la que no debe preocuparse.


"$*"siempre se expande a una palabra. Esta palabra contiene los parámetros posicionales, concatenados con un espacio intermedio. (Más generalmente, 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 externas 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 expansiones de variables sin comillas.

Por ejemplo, si el directorio actual contiene tres archivos bar, bazy foo, a continuación:

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- deja de ser malvado'
fuente
1
Nota histórica: en algunas conchas de Bourne antiguas, de "$@"hecho se expandió a una lista que consta de la cadena vacía: unix.stackexchange.com/questions/68484/…
ninjalj
25

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"

Salida:

% 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 matriz, no hay diferencia cuando se usa $*o $@. Solo tiene sentido cuando los usa con comillas dobles "$*"y "$@".

Cuonglm
fuente
Excelente ejemplo! Sin IFS="^${IFS}"embargo, ¿puedes explicar el uso de ?
Russ
@Russ: muestra cómo el valor es concat con el primer carácter IFS.
Cuonglm
De la misma manera que IFS="^xxxxx"lo haría? El ${IFS}sufijo final me hizo pensar que estaba haciendo algo más complicado, como recuperar de alguna manera automáticamente el IFS original al final (por ejemplo: el primer carácter se desplazó automáticamente o algo así).
Russ
11

El código que proporcionó dará el mismo resultado. Para entenderlo mejor, intente esto:

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

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

La salida ahora debería ser diferente. Esto es lo que obtengo:

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

Esto funcionó para mí bash. Que yo sepa, ksh no debería diferir mucho. Esencialmente, las citas $*tratarán todo como una palabra, y las citas $@tratarán la lista como palabras separadas, como se puede ver en el ejemplo anterior.

Como ejemplo de uso de la IFSvariable con $*, considere esto

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

Obtengo esto como resultado:

$ fooifs 1 2 3 4
1c2c3c4

Además, acabo de confirmar que funciona igual ksh. Ambos bashy kshprobados aquí estaban bajo OSX pero no puedo ver cómo eso importaría mucho.

Wojtek Rzepala
fuente
"cambiado dentro de una función - no afecta a lo global". ¿Probaste eso?
Mikel
Sí, lo he comprobado. Para su tranquilidad, agregaré el unset IFSal final para restablecerlo al original, pero no funcionó para mí sin ningún problema y al hacerlo echo $IFSobtuve el resultado estándar que obtengo. La configuración de los IFScorchetes introduce un nuevo alcance, por lo que, a menos que lo exporte, no afectará el exterior IFS.
Wojtek Rzepala
echo $IFSno prueba nada, porque el shell ve el ,, pero luego divide palabras usando IFS! Tratar echo "$IFS".
Mikel
buen punto. inquietante IFSdebería resolver eso.
Wojtek Rzepala
A menos que IFStenga un valor personalizado diferente antes de llamar a la función. Pero sí, la mayoría de las veces el IFS inquietante funcionará.
Mikel
6

La diferencia es importante al escribir scripts que deberían 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 $@cita. Esto respetará los parámetros y los enviará tal como están useradd. Si tuviera que poner entre comillas $@(o usar $*también sin comillas), useradd vería 5 pará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

(ya la inversa, si se va a utilizar "$*", useradd sería sólo ven un parámetro: -m -c Carlos Campderrós ccampderros)

En resumen, si necesita trabajar con parámetros que respeten parámetros de varias palabras, use "$@".

Carlos Campderrós
fuente
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).

// hombre bash . es ksh, afair, comportamiento similar.

prisa
fuente
2

Hablando de diferencias entre zshy bash:

Con comillas alrededor $@y $*, zshy se bashcomportan igual, y supongo que el resultado es bastante estándar entre todos los 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 +

Sin comillas, los resultados son los mismos para $*y $@, pero diferentes en bashy en zsh. En este caso zshmuestra un comportamiento extraño:

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 generalmente no divide datos textuales usando IFS, a menos que se solicite explícitamente, pero observe que aquí el argumento vacío falta inesperadamente en la lista).

Stéphane Gimenez
fuente
1
En zsh, $@no es especial a este respecto: se $xexpande como máximo a una palabra, pero las variables vacías se expanden a nada (no es una palabra vacía). Prueba print -l a $foo bcon foovacío o indefinido.
Gilles 'SO- deja de ser malvado'
0

Una de las respuestas dice $*(que considero un "splat") rara vez es útil.

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

Dado que las URL a menudo se dividen con un +, pero mi teclado hace que sea   más fácil de alcanzar que +, $*+ $IFSsiento que vale la pena.

isomorfismos
fuente