Le bash-3.00 a un comportement stupide dans le cas suivant:
bash$ rm -Rf toto && mkdir toto && cd toto && touch toto titi tata
bash$ for FILE in totor/* ; do echo `basename $FILE` && echo 'oops!'; done
tata titi toto
oops!
Il n'y a qu'un 'oops!', donc la commande s'exécute en une fois. Normal,
puisqu'il n'y a qu'une erreur (totor n'existe pas). Mais bash ne renvoie
pas de code d'erreur !
Maintenant :
bash$ for FILE in totor/* ; do echo $FILE ; done
totor/*
Donc basename recevait la chaîne 'totor/*' dans l'exemple précédent.
Et l'expansion de noms a lieu malgré tout, ce qui donne n'importe quoi !!
Basename n'a pas forcément été prévu pour traiter des entrées bidons,
ici on voit qu'il fait comme s'il n'y avait qu'un seul nom avec deux
espaces: "tata titi toto". Un peu gênant quand même parce que c'est pas
du tout un basename unix. Enfin AMHA.
Avec zsh, la commande se termine normalement en erreur :
bash$ zsh
$ for FILE in totor/* ; do echo `basename $FILE` ; echo 'oops!'; done
zsh: no matches found: totor/*
Et là Ok : on a un code d'erreur en cas d'erreur : on peut donc
programmer.
--
Hervé
Cette action est irreversible, confirmez la suppression du commentaire ?
Signaler le commentaire
Veuillez sélectionner un problème
Nudité
Violence
Harcèlement
Fraude
Vente illégale
Discours haineux
Terrorisme
Autre
lhabert
Hervé Autret :
bash$ rm -Rf toto && mkdir toto && cd toto && touch toto titi tata
Tu te retrouves dans le répertoire toto.
bash$ for FILE in totor/* ; do
Il n'y a aucun match, dans ce cas le comportement traditionel du shell est d'exécuter une fois le corps de la boucle, avec FILE valant « totor/* » (c'est un peu grotesque, mais ça permet de détecter dans le corps de la boucle qu'il n'y a aucun match et agir en conséquence).
echo `basename $FILE`
Le shell commence par exécuter la commande « basename $FILE ». Pour ce faire, il commence par expanser la variable « FILE » en « totor/* », puis il essaye de la splitter sur les caractères mentionés dans la variable IFS, qui est probablement à sa valeur par défaut espace, tab et retour à la ligne, donc il ne splitte rien, et, enfin, il essaye de globber, ce qui échoue à nouveau, il laisse donc « totor/* », et appelle basename dessus. Basename va renvoyer « * » sur sa sortie.
Le shell récupère ce « * », essaye de le splitter, en vain, puis de le globber, et là, il peut expanser ce « * » en « toto titi tata » puisqu'il y a ces trois fichiers dans le répertoire courant. Il fait donc un « echo toto titi tata » (et tu vois bien une ligne « toto titi tata » en sortie).
Donc basename recevait la chaîne 'totor/*' dans l'exemple précédent. Et l'expansion de noms a lieu malgré tout, ce qui donne n'importe quoi !! Basename n'a pas forcément été prévu pour traiter des entrées bidons, ici on voit qu'il fait comme s'il n'y avait qu'un seul nom avec deux espaces: "tata titi toto". Un peu gênant quand même parce que c'est pas du tout un basename unix. Enfin AMHA.
Ce n'est pas du tout basename qui fait l'expansion, c'est le shell, comme expliqué plus haut. Ton problème, c'est que tu n'utilises pas correctement les « ` » et les « $ ». La bonne syntaxe pour ce que tu voulais faire est :
for FILE in totor/*; do echo "`basename "$FILE"`" done
, c'est-à-dire qu'il faut toujours mettre les expansions de variables et de « ` » entre « " » si tu ne veux pas explicitement qu'il y ait du word-split et du globbing d'effectués sur le résultat.
Enfin si tu veux t'assurer qu'il y a bien des matchs, il faut faire quelque chose comme :
for FILE in totor/*; do if test -e "$FILE"; then ... # cas normal else ... # $FILE n'existe pas, a priori ça veut dire que le glob a echoué # bon, d'accord, il y a une race condition possible, mais il n'y a # pas moyen de l'éviter (enfin, on peut la restreindre au cas où le # fichier qui a disparu s'appellait « totor/* »)
fi done
Hervé Autret :
bash$ rm -Rf toto && mkdir toto && cd toto && touch toto titi tata
Tu te retrouves dans le répertoire toto.
bash$ for FILE in totor/* ; do
Il n'y a aucun match, dans ce cas le comportement traditionel du shell est
d'exécuter une fois le corps de la boucle, avec FILE valant « totor/* »
(c'est un peu grotesque, mais ça permet de détecter dans le corps de la
boucle qu'il n'y a aucun match et agir en conséquence).
echo `basename $FILE`
Le shell commence par exécuter la commande « basename $FILE ». Pour ce
faire, il commence par expanser la variable « FILE » en « totor/* », puis il
essaye de la splitter sur les caractères mentionés dans la variable IFS, qui
est probablement à sa valeur par défaut espace, tab et retour à la ligne,
donc il ne splitte rien, et, enfin, il essaye de globber, ce qui échoue à
nouveau, il laisse donc « totor/* », et appelle basename dessus. Basename va
renvoyer « * » sur sa sortie.
Le shell récupère ce « * », essaye de le splitter, en vain, puis de le
globber, et là, il peut expanser ce « * » en « toto titi tata » puisqu'il y
a ces trois fichiers dans le répertoire courant. Il fait donc un « echo toto
titi tata » (et tu vois bien une ligne « toto titi tata » en sortie).
Donc basename recevait la chaîne 'totor/*' dans l'exemple précédent.
Et l'expansion de noms a lieu malgré tout, ce qui donne n'importe quoi !!
Basename n'a pas forcément été prévu pour traiter des entrées bidons,
ici on voit qu'il fait comme s'il n'y avait qu'un seul nom avec deux
espaces: "tata titi toto". Un peu gênant quand même parce que c'est pas
du tout un basename unix. Enfin AMHA.
Ce n'est pas du tout basename qui fait l'expansion, c'est le shell, comme
expliqué plus haut. Ton problème, c'est que tu n'utilises pas correctement
les « ` » et les « $ ». La bonne syntaxe pour ce que tu voulais faire est :
for FILE in totor/*; do
echo "`basename "$FILE"`"
done
, c'est-à-dire qu'il faut toujours mettre les expansions de variables et de
« ` » entre « " » si tu ne veux pas explicitement qu'il y ait du word-split
et du globbing d'effectués sur le résultat.
Enfin si tu veux t'assurer qu'il y a bien des matchs, il faut faire quelque
chose comme :
for FILE in totor/*; do
if test -e "$FILE"; then
... # cas normal
else
... # $FILE n'existe pas, a priori ça veut dire que le glob a echoué
# bon, d'accord, il y a une race condition possible, mais il n'y a
# pas moyen de l'éviter (enfin, on peut la restreindre au cas où le
# fichier qui a disparu s'appellait « totor/* »)
bash$ rm -Rf toto && mkdir toto && cd toto && touch toto titi tata
Tu te retrouves dans le répertoire toto.
bash$ for FILE in totor/* ; do
Il n'y a aucun match, dans ce cas le comportement traditionel du shell est d'exécuter une fois le corps de la boucle, avec FILE valant « totor/* » (c'est un peu grotesque, mais ça permet de détecter dans le corps de la boucle qu'il n'y a aucun match et agir en conséquence).
echo `basename $FILE`
Le shell commence par exécuter la commande « basename $FILE ». Pour ce faire, il commence par expanser la variable « FILE » en « totor/* », puis il essaye de la splitter sur les caractères mentionés dans la variable IFS, qui est probablement à sa valeur par défaut espace, tab et retour à la ligne, donc il ne splitte rien, et, enfin, il essaye de globber, ce qui échoue à nouveau, il laisse donc « totor/* », et appelle basename dessus. Basename va renvoyer « * » sur sa sortie.
Le shell récupère ce « * », essaye de le splitter, en vain, puis de le globber, et là, il peut expanser ce « * » en « toto titi tata » puisqu'il y a ces trois fichiers dans le répertoire courant. Il fait donc un « echo toto titi tata » (et tu vois bien une ligne « toto titi tata » en sortie).
Donc basename recevait la chaîne 'totor/*' dans l'exemple précédent. Et l'expansion de noms a lieu malgré tout, ce qui donne n'importe quoi !! Basename n'a pas forcément été prévu pour traiter des entrées bidons, ici on voit qu'il fait comme s'il n'y avait qu'un seul nom avec deux espaces: "tata titi toto". Un peu gênant quand même parce que c'est pas du tout un basename unix. Enfin AMHA.
Ce n'est pas du tout basename qui fait l'expansion, c'est le shell, comme expliqué plus haut. Ton problème, c'est que tu n'utilises pas correctement les « ` » et les « $ ». La bonne syntaxe pour ce que tu voulais faire est :
for FILE in totor/*; do echo "`basename "$FILE"`" done
, c'est-à-dire qu'il faut toujours mettre les expansions de variables et de « ` » entre « " » si tu ne veux pas explicitement qu'il y ait du word-split et du globbing d'effectués sur le résultat.
Enfin si tu veux t'assurer qu'il y a bien des matchs, il faut faire quelque chose comme :
for FILE in totor/*; do if test -e "$FILE"; then ... # cas normal else ... # $FILE n'existe pas, a priori ça veut dire que le glob a echoué # bon, d'accord, il y a une race condition possible, mais il n'y a # pas moyen de l'éviter (enfin, on peut la restreindre au cas où le # fichier qui a disparu s'appellait « totor/* »)
fi done
lhabert
Herve Autret :
Ok. À quel stade le comportement de zsh diffère-t'il de celui du bash ?
Déjà, il fait une erreur sur un glob sans match. Ensuite, il ne fait pas de split&glob sur des trucs non quotés.
Quand je demande au système quelquechose qui n'existe pas, je préfère de loin recevoir un message d'erreur, plutôt qu'être obligé de faire le tri parmi un fatras où, finalement, ne se trouve *pas* ce que je demande.
Eh bien dans ce cas, choisis un autre langage que sh, par exemple zsh.
Herve Autret :
Ok. À quel stade le comportement de zsh diffère-t'il de celui du bash ?
Déjà, il fait une erreur sur un glob sans match. Ensuite, il ne fait pas de
split&glob sur des trucs non quotés.
Quand je demande au système quelquechose qui n'existe pas, je préfère
de loin recevoir un message d'erreur, plutôt qu'être obligé de faire le
tri parmi un fatras où, finalement, ne se trouve *pas* ce que je demande.
Eh bien dans ce cas, choisis un autre langage que sh, par exemple zsh.
Ok. À quel stade le comportement de zsh diffère-t'il de celui du bash ?
Déjà, il fait une erreur sur un glob sans match. Ensuite, il ne fait pas de split&glob sur des trucs non quotés.
Quand je demande au système quelquechose qui n'existe pas, je préfère de loin recevoir un message d'erreur, plutôt qu'être obligé de faire le tri parmi un fatras où, finalement, ne se trouve *pas* ce que je demande.
Eh bien dans ce cas, choisis un autre langage que sh, par exemple zsh.
Herve Autret
Bonjour,
Luc Habert a ecrit :
il essaye de globber, ce qui échoue à nouveau,
Ok. À quel stade le comportement de zsh diffère-t'il de celui du bash ?
il laisse donc « totor/* », et appelle basename dessus. Basename va renvoyer « * » sur sa sortie.
C'est *exactement* ce qui m'embête. Je veux les totor/*, pas les *. Quand je demande au système quelquechose qui n'existe pas, je préfère de loin recevoir un message d'erreur, plutôt qu'être obligé de faire le tri parmi un fatras où, finalement, ne se trouve *pas* ce que je demande. -- Hervé
Bonjour,
Luc Habert a ecrit :
il essaye de globber, ce qui échoue à nouveau,
Ok. À quel stade le comportement de zsh diffère-t'il de celui du bash ?
il laisse donc « totor/* », et appelle basename dessus. Basename va
renvoyer « * » sur sa sortie.
C'est *exactement* ce qui m'embête. Je veux les totor/*, pas les *.
Quand je demande au système quelquechose qui n'existe pas, je préfère
de loin recevoir un message d'erreur, plutôt qu'être obligé de faire le
tri parmi un fatras où, finalement, ne se trouve *pas* ce que je demande.
--
Hervé
Ok. À quel stade le comportement de zsh diffère-t'il de celui du bash ?
il laisse donc « totor/* », et appelle basename dessus. Basename va renvoyer « * » sur sa sortie.
C'est *exactement* ce qui m'embête. Je veux les totor/*, pas les *. Quand je demande au système quelquechose qui n'existe pas, je préfère de loin recevoir un message d'erreur, plutôt qu'être obligé de faire le tri parmi un fatras où, finalement, ne se trouve *pas* ce que je demande. -- Hervé