Vuelva a escribir una respuesta ahora eliminada por VonC .
Robert GambleLa respuesta sucinta de trata directamente con la pregunta. Este amplifica algunos problemas con nombres de archivos que contienen espacios.
Ver también: $ {1: + "$ @"} en / bin / sh
Tesis básica: "$@"
es correcta y $*
(sin comillas) casi siempre está mal. Esto se debe a que "$@"
funciona bien cuando los argumentos contienen espacios, y funciona igual que $*
cuando no lo hacen. En algunas circunstancias, también "$*"
está bien, pero "$@"
generalmente (pero no siempre) funciona en los mismos lugares. Sin comillas, $@
y $*
son equivalentes (y casi siempre incorrectos).
Así que, ¿cuál es la diferencia entre $*
, $@
, "$*"
y "$@"
? Todos están relacionados con 'todos los argumentos del shell', pero hacen cosas diferentes. Cuando no se cita, $*
y $@
hacer lo mismo. Tratan cada 'palabra' (secuencia de espacios no en blanco) como un argumento separado. Sin embargo, las formas citadas son bastante diferentes: "$*"
trata la lista de argumentos como una sola cadena separada por espacios, mientras que "$@"
trata los argumentos casi exactamente como eran cuando se especificaban en la línea de comando.
"$@"
se expande a nada cuando no hay argumentos posicionales; "$*"
se expande a una cadena vacía, y sí, hay una diferencia, aunque puede ser difícil de percibir. Consulte más información a continuación, después de la introducción del comando (no estándar) al
.
Tesis secundaria: si necesita procesar argumentos con espacios y luego pasarlos a otros comandos, a veces necesita herramientas no estándar para ayudarlo. (O debe usar matrices, con cuidado: se "${array[@]}"
comporta de manera análoga "$@"
).
Ejemplo:
$ mkdir "my dir" anotherdir
$ ls
anotherdir my dir
$ cp /dev/null "my dir/my file"
$ cp /dev/null "anotherdir/myfile"
$ ls -Fltr
total 0
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir/
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir/
$ ls -Fltr *
my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ ls -Fltr "./my dir" "./anotherdir"
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ var='"./my dir" "./anotherdir"' && echo $var
"./my dir" "./anotherdir"
$ ls -Fltr $var
ls: "./anotherdir": No such file or directory
ls: "./my: No such file or directory
ls: dir": No such file or directory
$
¿Por qué eso no funciona? No funciona porque el shell procesa las cotizaciones antes de expandir las variables. Entonces, para que el shell preste atención a las citas incrustadas $var
, debe usar eval
:
$ eval ls -Fltr $var
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$
Esto se vuelve realmente complicado cuando tiene nombres de archivo como " He said,
"Don't do this!"
" (con comillas y comillas dobles y espacios).
$ cp /dev/null "He said, \"Don't do this!\""
$ ls
He said, "Don't do this!" anotherdir my dir
$ ls -l
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 15:54 He said, "Don't do this!"
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir
$
Los shells (todos ellos) no hacen que sea particularmente fácil manejar tales cosas, por lo que (curiosamente) muchos programas de Unix no hacen un buen trabajo al manejarlos. En Unix, un nombre de archivo (componente único) puede contener cualquier carácter excepto barra diagonal y NUL'\0'
. Sin embargo, los shells no recomiendan espacios, líneas nuevas o pestañas en ningún lugar de los nombres de las rutas. También es la razón por la cual los nombres de archivo estándar de Unix no contienen espacios, etc.
Cuando se trata de nombres de archivos que pueden contener espacios y otros caracteres problemáticos, debe ser extremadamente cuidadoso, y hace mucho tiempo descubrí que necesitaba un programa que no sea estándar en Unix. Lo llamoescape
(la versión 1.1 tenía fecha de 1989-08-23T16: 01: 45Z).
Aquí hay un ejemplo de escape
uso: con el sistema de control SCCS. Es un guión de portada que hace tanto un delta
(pensar registro ) como un
get
(pensar registro ). Varios argumentos, especialmente -y
(la razón por la que realizó el cambio) contendrían espacios en blanco y nuevas líneas. Tenga en cuenta que el script data de 1992, por lo que utiliza ticks de retroceso en lugar de
$(cmd ...)
notación y no se utiliza #!/bin/sh
en la primera línea.
: "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
# Delta and get files
# Uses escape to allow for all weird combinations of quotes in arguments
case `basename $0 .sh` in
deledit) eflag="-e";;
esac
sflag="-s"
for arg in "$@"
do
case "$arg" in
-r*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
-e) gargs="$gargs `escape \"$arg\"`"
sflag=""
eflag=""
;;
-*) dargs="$dargs `escape \"$arg\"`"
;;
*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
esac
done
eval delta "$dargs" && eval get $eflag $sflag "$gargs"
(Probablemente no usaría escape tan a fondo en estos días -e
, por ejemplo, no es necesario con el argumento, pero en general, este es uno de mis scripts más simples que uso escape
).
El escape
programa simplemente genera sus argumentos, al igual que lo echo
hace, pero asegura que los argumentos estén protegidos para su uso con
eval
(un nivel de eval
; Tengo un programa que realizó la ejecución remota de shell, y que necesitaba escapar de la salida de escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Tengo otro programa llamado al
que enumera sus argumentos uno por línea (y es aún más antiguo: la versión 1.1 con fecha 27-01-27T14: 35: 49). Es más útil al depurar scripts, ya que se puede conectar a una línea de comando para ver qué argumentos se pasan realmente al comando.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ Agregado:
Y ahora para mostrar la diferencia entre las diversas "$@"
anotaciones, aquí hay un ejemplo más:
$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh * */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$
Tenga en cuenta que nada conserva los espacios en blanco originales entre *
y */*
en la línea de comando. Además, tenga en cuenta que puede cambiar los 'argumentos de la línea de comandos' en el shell utilizando:
set -- -new -opt and "arg with space"
Esto establece 4 opciones, ' -new
', ' -opt
', ' and
' y 'arg with space
'.
]
Hmm, esa es una respuesta bastante larga , tal vez exégesis es el mejor término. El código fuente está escape
disponible a pedido (correo electrónico a firstname dot lastname en gmail dot com). El código fuente para al
es increíblemente simple:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Eso es todo. Es equivalente a latest.sh
script que mostró Robert Gamble, y podría escribirse como una función de shell (pero las funciones de shell no existían en la versión local de Bourne cuando escribí por primera vez al
).
También tenga en cuenta que puede escribir al
como un simple script de shell:
[ $# != 0 ] && printf "%s\n" "$@"
El condicional es necesario para que no produzca salida cuando no se pasan argumentos. El printf
comando producirá una línea en blanco con solo el argumento de cadena de formato, pero el programa C no produce nada.