Réécriture d'une réponse maintenant supprimée par VonC .
La réponse succincte de Robert Gamble traite directement de la question. Celui-ci amplifie certains problèmes avec les noms de fichiers contenant des espaces.
Voir aussi: $ {1: + "$ @"} dans / bin / sh
Thèse de base: "$@"
est correcte et $*
(non citée) est presque toujours fausse. En effet, "$@"
fonctionne correctement lorsque les arguments contiennent des espaces et fonctionne de la même manière que $*
lorsqu'ils n'en contiennent pas. Dans certaines circonstances, "$*"
est OK aussi, mais "$@"
généralement (mais pas toujours) fonctionne aux mêmes endroits. Sans guillemets, $@
et $*
sont équivalents (et presque toujours faux).
Alors, quelle est la différence entre $*
, $@
, "$*"
et "$@"
? Ils sont tous liés à «tous les arguments du shell», mais ils font des choses différentes. Lorsque non cité, $*
et $@
faites la même chose. Ils traitent chaque «mot» (séquence non blanche) comme un argument distinct. Les formes entre guillemets sont cependant assez différentes: "$*"
traite la liste d'arguments comme une chaîne unique séparée par des espaces, tandis que "$@"
traite les arguments presque exactement comme ils étaient lorsqu'ils étaient spécifiés sur la ligne de commande.
"$@"
se développe à rien du tout lorsqu'il n'y a pas d'arguments positionnels; "$*"
se développe en une chaîne vide - et oui, il y a une différence, même s'il peut être difficile de la percevoir. Voir plus d'informations ci-dessous, après l'introduction de la commande (non standard) al
.
Thèse secondaire: si vous devez traiter des arguments avec des espaces, puis les transmettre à d'autres commandes, vous avez parfois besoin d'outils non standard pour vous aider. (Ou vous devez utiliser des tableaux, avec précaution: "${array[@]}"
se comporte de manière analogue à "$@"
.)
Exemple:
$ 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
$
Pourquoi ça ne marche pas? Cela ne fonctionne pas car le shell traite les guillemets avant d'étendre les variables. Donc, pour que le shell prête attention aux citations intégrées $var
, vous devez utiliser 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
$
Cela devient vraiment délicat lorsque vous avez des noms de fichiers tels que " He said,
"Don't do this!"
" (avec des guillemets, des guillemets doubles et des espaces).
$ 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
$
Les shells (tous) ne facilitent pas particulièrement la gestion de ces choses, donc (assez drôle) de nombreux programmes Unix ne font pas un bon travail pour les gérer. Sous Unix, un nom de fichier (composant unique) peut contenir n'importe quel caractère sauf la barre oblique et NUL '\0'
. Cependant, les shells n'encouragent pas fortement les espaces, les sauts de ligne ou les tabulations n'importe où dans les noms de chemin. C'est aussi pourquoi les noms de fichiers Unix standard ne contiennent pas d'espaces, etc.
Lorsque vous traitez des noms de fichiers qui peuvent contenir des espaces et d'autres caractères gênants, vous devez être extrêmement prudent, et j'ai découvert il y a longtemps que j'avais besoin d'un programme qui n'est pas standard sur Unix. Je l'appelle escape
(la version 1.1 était datée du 1989-08-23T16: 01: 45Z).
Voici un exemple d' escape
utilisation - avec le système de contrôle SCCS. C'est un script de couverture qui fait à la fois un delta
(pensez à l' enregistrement ) et un
get
(pensez à un check-out ). Divers arguments, en particulier -y
(la raison pour laquelle vous avez effectué la modification), contiendraient des espaces et des retours à la ligne. Notez que le script date de 1992, donc il utilise des back-ticks au lieu de la
$(cmd ...)
notation et ne l'utilise pas #!/bin/sh
sur la première ligne.
: "@(#)$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"
(Je n'utiliserais probablement pas aussi complètement escape de nos jours - ce n'est pas nécessaire avec l' -e
argument, par exemple - mais dans l'ensemble, c'est l'un de mes scripts les plus simples à utiliser escape
.)
Le escape
programme sort simplement ses arguments, un peu comme le echo
fait, mais il garantit que les arguments sont protégés pour une utilisation avec
eval
(un niveau de eval
; J'ai un programme qui a fait une exécution à distance du shell, et qui devait échapper à la sortie de escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
J'ai un autre programme appelé al
qui répertorie ses arguments un par ligne (et il est encore plus ancien: version 1.1 datée du 1987-01-27T14: 35: 49). Il est très utile lors du débogage de scripts, car il peut être connecté à une ligne de commande pour voir quels arguments sont réellement passés à la commande.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ Ajouté:
Et maintenant pour montrer la différence entre les différentes "$@"
notations, voici un autre exemple:
$ 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
$
Notez que rien ne conserve les blancs d'origine entre le *
et */*
sur la ligne de commande. Notez également que vous pouvez modifier les «arguments de ligne de commande» dans le shell en utilisant:
set -- -new -opt and "arg with space"
Cela définit 4 options, ' -new
', ' -opt
', ' and
' et ' arg with space
'.
]
Hmm, c'est une réponse assez longue - peut-être que l' exégèse est le meilleur terme. Code source escape
disponible sur demande (e-mail au prénom dot nom de famille sur gmail dot com). Le code source de al
est incroyablement simple:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
C'est tout. Il est équivalent au test.sh
script que Robert Gamble a montré, et pourrait être écrit comme une fonction shell (mais les fonctions shell n'existaient pas dans la version locale du Bourne shell lorsque j'ai écrit pour la première fois al
).
Notez également que vous pouvez écrire al
comme un simple script shell:
[ $# != 0 ] && printf "%s\n" "$@"
Le conditionnel est nécessaire pour qu'il ne produise aucune sortie lorsqu'il ne reçoit aucun argument. La printf
commande produira une ligne vierge avec uniquement l'argument de chaîne de format, mais le programme C ne produit rien.