Pregunta:
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 conchas específicas, por favor menciónelas.
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ámetros o como nombre de archivo. Sin embargo, cuando se usa como un 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 variable IFS
, pero no modifica la salida. ¿Quizás estoy haciendo algo mal?
Respuesta:
Cuando no se cotizan, $*
y $@
son lo mismo. No debe usar ninguno de estos, porque pueden romperse inesperadamente tan pronto como tenga argumentos que contengan espacios o comodines.
"$*"
expande a una sola palabra "$1c$2c..."
. Por lo general, c
es un espacio, pero en realidad es el primer carácter de IFS
, por lo que puede ser cualquier cosa que elija.
El único buen uso que le 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
"$@"
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, digamos, "$1"
contiene un espacio o un asterisco ( *
).
Escribamos un script llamado svim
que 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 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 "$@"
funcionan correctamente si tiene varios 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 "$@"
funcionan 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"
Por tanto, sólo "$@"
funcionará correctamente todo el tiempo.
typeset
es cómo hacer una variable local en ksh
( bash
y ash
usan local
lugar). Significa que IFS
se restaurará a su valor anterior cuando la función regrese. Esto es importante, porque es posible que los comandos que ejecute después no funcionen correctamente si IFS
está configurado en algo no estándar.