[bash] bonne façon de faire une boucle sur un ensemble de fichiers contenu dans un dossier
35 réponses
Francois Lafont
Bonjour à tous,
Assez régulièrement, je suis amené à me faire des petits scripts bash
qui font une boucle sur une liste de fichiers contenus dans un dossier
(et dans ses sous-dossiers etc). La liste de fichiers, qui peut être
assez grande, est obtenue à l'aide de la commande find. Le but est de
faire un même petit traitement sur chacun des fichiers de cette liste
via une boucle (le traitement importe peu ici, ça peut être un
"renommage", un grep etc). La structure de mes petits scripts
ressemblent à ça du coup (c'est un exemple) :
#-------------------------------------
for fichier in $(find /le/dossier/ -type f -iname '*.tex')
do
echo Traitement de $fichier
# etc.
done
#-------------------------------------
Est-ce la bonne façon de procéder ? J'ai des doutes :
1) En effet, finalement le script revient à ça :
#-------------------------------------
for fichier in /le/dossier/f1.tex /le/dossier/f2.tex /le/dossier/f3.tex
do
echo Traitement de $fichier
# etc.
done
#-------------------------------------
Du coup, si la liste est très grande, ça me semble un peu maladroit
comme script et peut-être pas très performant. (Aucune certitude bien
sûr, c'est juste une intuition.)
2) Une autre chose qui me fait penser que ce n'est peut-être pas la
bonne façon de procéder, c'est le problème avec les noms de fichiers qui
contiennent un espace. Si find détecte un fichier qui s'appelle par
exemple "/le/dossier/sous dossier/toto.tex" alors, durant la boucle,
$fichier sera égal à la chaîne "/le/dossier/sous" puis à
"dossier/toto.tex" mais jamais à "/le/dossier/sous dossier/toto.tex".
Bref, quelle est la bonne façon de faire ce genre de boucle ?
Merci d'avance pour votre aide.
0 ./un dossier avec des espaces/titi.tex 0 ./deux dossiers avec des espaces/toto.tex
# find -name "*.tex" -print0 | xargs -0 du
0 ./un dossier avec des espaces/titi.tex 0 ./deux dossiers avec des espaces/toto.tex
=> Le second exemple a l'avantage de fonctionner avec tous les caractères ésotériques que peuvent contenir les noms de fichiers, y compris les sauts de ligne.
Exemple avec les fichiers, eux aussi, avec des noms ayant des espaces:
# find -name "*.tex" -print0 | xargs -0 du 0 ./un dossier avec des espaces/un fichier avec des espaces.tex 0 ./deux dossiers avec des espaces/deux fichiers avec des espaces.tex
Cétipabo ?
Cordialement,
-- Floris Dubreuil
Le 23/05/2011 20:08, Aéris a écrit :
xargs ça sent le paté ?
Avec des espaces dans le nom de fichier, oui?
On doit pas aller chez le même charcutier...
# find -name "*.tex" | xargs -d "n" du
0 ./un dossier avec des espaces/titi.tex
0 ./deux dossiers avec des espaces/toto.tex
# find -name "*.tex" -print0 | xargs -0 du
0 ./un dossier avec des espaces/titi.tex
0 ./deux dossiers avec des espaces/toto.tex
=> Le second exemple a l'avantage de fonctionner avec tous les
caractères ésotériques que peuvent contenir les noms de fichiers, y
compris les sauts de ligne.
Exemple avec les fichiers, eux aussi, avec des noms ayant des espaces:
# find -name "*.tex" -print0 | xargs -0 du
0 ./un dossier avec des espaces/un fichier avec des espaces.tex
0 ./deux dossiers avec des espaces/deux fichiers avec des espaces.tex
0 ./un dossier avec des espaces/titi.tex 0 ./deux dossiers avec des espaces/toto.tex
# find -name "*.tex" -print0 | xargs -0 du
0 ./un dossier avec des espaces/titi.tex 0 ./deux dossiers avec des espaces/toto.tex
=> Le second exemple a l'avantage de fonctionner avec tous les caractères ésotériques que peuvent contenir les noms de fichiers, y compris les sauts de ligne.
Exemple avec les fichiers, eux aussi, avec des noms ayant des espaces:
# find -name "*.tex" -print0 | xargs -0 du 0 ./un dossier avec des espaces/un fichier avec des espaces.tex 0 ./deux dossiers avec des espaces/deux fichiers avec des espaces.tex
Cétipabo ?
Cordialement,
-- Floris Dubreuil
Benoit Izac
Bonjour,
le 23/05/2011 à 20:40, Floris Dubreuil a écrit dans le message <4ddaaa1c$0$4774$ :
# find -name "*.tex" -print0 | xargs -0 du 0 ./un dossier avec des espaces/un fichier avec des espaces.tex 0 ./deux dossiers avec des espaces/deux fichiers avec des espaces.tex
Cétipabo ?
Ça ne répond pas à la question initiale où il était question de lancer plusieurs commandes sur le fichier. Pas besoin de xargs pour faire juste un « du » :
find -name "*.tex" -exec du {} + (qui à l'avantage d'être portable)
-- Benoit Izac
Bonjour,
le 23/05/2011 à 20:40, Floris Dubreuil a écrit dans le message
<4ddaaa1c$0$4774$426a74cc@news.free.fr> :
# find -name "*.tex" -print0 | xargs -0 du
0 ./un dossier avec des espaces/un fichier avec des espaces.tex
0 ./deux dossiers avec des espaces/deux fichiers avec des espaces.tex
Cétipabo ?
Ça ne répond pas à la question initiale où il était question de lancer
plusieurs commandes sur le fichier. Pas besoin de xargs pour faire juste
un « du » :
find -name "*.tex" -exec du {} + (qui à l'avantage d'être portable)
le 23/05/2011 à 20:40, Floris Dubreuil a écrit dans le message <4ddaaa1c$0$4774$ :
# find -name "*.tex" -print0 | xargs -0 du 0 ./un dossier avec des espaces/un fichier avec des espaces.tex 0 ./deux dossiers avec des espaces/deux fichiers avec des espaces.tex
Cétipabo ?
Ça ne répond pas à la question initiale où il était question de lancer plusieurs commandes sur le fichier. Pas besoin de xargs pour faire juste un « du » :
find -name "*.tex" -exec du {} + (qui à l'avantage d'être portable)
-- Benoit Izac
Benoit Izac
Bonjour,
le 23/05/2011 à 23:19, Floris Dubreuil a écrit dans le message <4ddacf4a$0$7168$ :
find -name "*.tex" -exec du {} + (qui à l'avantage d'être portable)
Pas spécialement, le package find n'est pas le même partout et certains systèmes n'ont pas le "full option".
Quand je dis portable, il faut comprendre POSIX. L'option « -0 » de find ou xargs n'est pas défini dans SUSv3 : <http://pubs.opengroup.org/onlinepubs/009695399/utilities/find.html> <http://pubs.opengroup.org/onlinepubs/009695399/utilities/xargs.html>
-- Benoit Izac
Bonjour,
le 23/05/2011 à 23:19, Floris Dubreuil a écrit dans le message
<4ddacf4a$0$7168$426a74cc@news.free.fr> :
find -name "*.tex" -exec du {} + (qui à l'avantage d'être portable)
Pas spécialement, le package find n'est pas le même partout et
certains systèmes n'ont pas le "full option".
Quand je dis portable, il faut comprendre POSIX. L'option « -0 » de
find ou xargs n'est pas défini dans SUSv3 :
<http://pubs.opengroup.org/onlinepubs/009695399/utilities/find.html>
<http://pubs.opengroup.org/onlinepubs/009695399/utilities/xargs.html>
le 23/05/2011 à 23:19, Floris Dubreuil a écrit dans le message <4ddacf4a$0$7168$ :
find -name "*.tex" -exec du {} + (qui à l'avantage d'être portable)
Pas spécialement, le package find n'est pas le même partout et certains systèmes n'ont pas le "full option".
Quand je dis portable, il faut comprendre POSIX. L'option « -0 » de find ou xargs n'est pas défini dans SUSv3 : <http://pubs.opengroup.org/onlinepubs/009695399/utilities/find.html> <http://pubs.opengroup.org/onlinepubs/009695399/utilities/xargs.html>
-- Benoit Izac
Benoit Izac
Bonjour,
le 24/05/2011 à 18:22, Francois Lafont a écrit dans le message <4ddbdb30$0$31973$ :
De plus, savez comment on peut faire pour connaître le contenu de IFS exactement. Si je fais un echo "$IFS" > out.txt, je pense pouvoir deviner comme contenu « ESPACE, TAB puis n ». Mais y a-t-il un moyen de voir son contenu de manière plus lisible ?
od(1)
-- Benoit Izac
Bonjour,
le 24/05/2011 à 18:22, Francois Lafont a écrit dans le message
<4ddbdb30$0$31973$426a74cc@news.free.fr> :
De plus, savez comment on peut faire pour connaître le contenu de IFS
exactement. Si je fais un echo "$IFS" > out.txt, je pense pouvoir
deviner comme contenu « ESPACE, TAB puis n ». Mais y a-t-il un moyen de
voir son contenu de manière plus lisible ?
le 24/05/2011 à 18:22, Francois Lafont a écrit dans le message <4ddbdb30$0$31973$ :
De plus, savez comment on peut faire pour connaître le contenu de IFS exactement. Si je fais un echo "$IFS" > out.txt, je pense pouvoir deviner comme contenu « ESPACE, TAB puis n ». Mais y a-t-il un moyen de voir son contenu de manière plus lisible ?
od(1)
-- Benoit Izac
Benoit Izac
Bonjour,
le 24/05/2011 à 21:12, Francois Lafont a écrit dans le message <4ddc02f4$0$7672$ :
$ cat "$IFS" > out.txt $ od -t c out.txt 0000000 t n n 0000004
Pas besoin de passer par un fichier : % printf $IFS | od -ta 0000000 sp ht nl nul 0000004
$ echo "Salut" > out.txt $ od -t c out.txt 0000000 S a l u t n 0000006
Par contre, que représentent le 0000000 et le 0000006 ?
Le nombre d'octets lu (attention, par défaut c'est en octal). % printf abcdefghijklmnopqrstuvwxyz | od -ta 0000000 a b c d e f g h i j k l m n o p 0000020 q r s t u v w x y z 0000032 % printf abcdefghijklmnopqrstuvwxyz | od -ta -Ad 0000000 a b c d e f g h i j k l m n o p 0000016 q r s t u v w x y z 0000026
-- Benoit Izac
Bonjour,
le 24/05/2011 à 21:12, Francois Lafont a écrit dans le message
<4ddc02f4$0$7672$426a74cc@news.free.fr> :
$ cat "$IFS" > out.txt
$ od -t c out.txt
0000000 t n n
0000004
Pas besoin de passer par un fichier :
% printf $IFS | od -ta
0000000 sp ht nl nul
0000004
$ echo "Salut" > out.txt
$ od -t c out.txt
0000000 S a l u t n
0000006
Par contre, que représentent le 0000000 et le 0000006 ?
Le nombre d'octets lu (attention, par défaut c'est en octal).
% printf abcdefghijklmnopqrstuvwxyz | od -ta
0000000 a b c d e f g h i j k l m n o p
0000020 q r s t u v w x y z
0000032
% printf abcdefghijklmnopqrstuvwxyz | od -ta -Ad
0000000 a b c d e f g h i j k l m n o p
0000016 q r s t u v w x y z
0000026
le 24/05/2011 à 21:12, Francois Lafont a écrit dans le message <4ddc02f4$0$7672$ :
$ cat "$IFS" > out.txt $ od -t c out.txt 0000000 t n n 0000004
Pas besoin de passer par un fichier : % printf $IFS | od -ta 0000000 sp ht nl nul 0000004
$ echo "Salut" > out.txt $ od -t c out.txt 0000000 S a l u t n 0000006
Par contre, que représentent le 0000000 et le 0000006 ?
Le nombre d'octets lu (attention, par défaut c'est en octal). % printf abcdefghijklmnopqrstuvwxyz | od -ta 0000000 a b c d e f g h i j k l m n o p 0000020 q r s t u v w x y z 0000032 % printf abcdefghijklmnopqrstuvwxyz | od -ta -Ad 0000000 a b c d e f g h i j k l m n o p 0000016 q r s t u v w x y z 0000026
-- Benoit Izac
Benoit Izac
Bonjour,
le 24/05/2011 à 21:02, Francois Lafont a écrit dans le message <4ddc00a3$0$27760$ :
Ah bon ? Houston, j'ai un problème. Si je teste ce code (je copie-colle sans modif, promis) :
J'avoue que je ne comprends pas trop pourquoi, d'après mes tests : bash et pdksh : debut--- aaa ---fin dash : debut---aaa ---fin zsh : debut--- aaa ---fin
Par contre si je modifie IFS au début du script tous fonctionnent (mais ça pose d'autre problèmes).
-- Benoit Izac
Bonjour,
le 24/05/2011 à 21:02, Francois Lafont a écrit dans le message
<4ddc00a3$0$27760$426a74cc@news.free.fr> :
Ah bon ? Houston, j'ai un problème. Si je teste ce code (je copie-colle
sans modif, promis) :
J'avoue que je ne comprends pas trop pourquoi, d'après mes tests :
bash et pdksh : debut--- aaa ---fin
dash : debut---aaa ---fin
zsh : debut--- aaa ---fin
Par contre si je modifie IFS au début du script tous fonctionnent (mais
ça pose d'autre problèmes).
J'avoue que je ne comprends pas trop pourquoi, d'après mes tests : bash et pdksh : debut--- aaa ---fin dash : debut---aaa ---fin zsh : debut--- aaa ---fin
Par contre si je modifie IFS au début du script tous fonctionnent (mais ça pose d'autre problèmes).
-- Benoit Izac
Benoit Izac
Bonjour,
le 24/05/2011 à 22:33, Francois Lafont a écrit dans le message <4ddc15e5$0$19682$ :
Pas besoin de passer par un fichier : % printf $IFS | od -ta 0000000 sp ht nl nul 0000004
Ok. Par contre, c'est fou quand même les petites différences qui s'insinuent d'un shell à un autre. Déjà, sur mon bash, ta commande ne passe pas :
$ printf $IFS | od -ta printf: usage: printf [-v var] format [arguments] 0000000
Oui, c'est normal ; depuis le temps que je me dis qu'il faut que j'arrête de faire mes tests directement sous zsh... D'un autre coté sous dash, bien que le plus proche de POSIX, c'est un peu la galère : pas de complétion, pas d'historique.
Mais en revanche ceci passe :
$ printf "$IFS" | od -ta 0000000 sp ht nl 0000003
Et là, même le résultat ne coïncide pas exactement avec le tien. Toi tu as le caractère null, moi pas.
C'est zsh, hein, quand on te dit qu'il est plus fort que les autres, ce n'est pas pour rien. ;)
-- Benoit Izac
Bonjour,
le 24/05/2011 à 22:33, Francois Lafont a écrit dans le message
<4ddc15e5$0$19682$426a74cc@news.free.fr> :
Pas besoin de passer par un fichier :
% printf $IFS | od -ta
0000000 sp ht nl nul
0000004
Ok. Par contre, c'est fou quand même les petites différences qui
s'insinuent d'un shell à un autre. Déjà, sur mon bash, ta commande ne
passe pas :
$ printf $IFS | od -ta
printf: usage: printf [-v var] format [arguments]
0000000
Oui, c'est normal ; depuis le temps que je me dis qu'il faut que
j'arrête de faire mes tests directement sous zsh... D'un autre coté sous
dash, bien que le plus proche de POSIX, c'est un peu la galère : pas de
complétion, pas d'historique.
Mais en revanche ceci passe :
$ printf "$IFS" | od -ta
0000000 sp ht nl
0000003
Et là, même le résultat ne coïncide pas exactement avec le tien. Toi tu
as le caractère null, moi pas.
C'est zsh, hein, quand on te dit qu'il est plus fort que les autres, ce
n'est pas pour rien. ;)
le 24/05/2011 à 22:33, Francois Lafont a écrit dans le message <4ddc15e5$0$19682$ :
Pas besoin de passer par un fichier : % printf $IFS | od -ta 0000000 sp ht nl nul 0000004
Ok. Par contre, c'est fou quand même les petites différences qui s'insinuent d'un shell à un autre. Déjà, sur mon bash, ta commande ne passe pas :
$ printf $IFS | od -ta printf: usage: printf [-v var] format [arguments] 0000000
Oui, c'est normal ; depuis le temps que je me dis qu'il faut que j'arrête de faire mes tests directement sous zsh... D'un autre coté sous dash, bien que le plus proche de POSIX, c'est un peu la galère : pas de complétion, pas d'historique.
Mais en revanche ceci passe :
$ printf "$IFS" | od -ta 0000000 sp ht nl 0000003
Et là, même le résultat ne coïncide pas exactement avec le tien. Toi tu as le caractère null, moi pas.
C'est zsh, hein, quand on te dit qu'il est plus fort que les autres, ce n'est pas pour rien. ;)
-- Benoit Izac
Benoit Izac
Bonjour,
le 24/05/2011 à 22:26, Francois Lafont a écrit dans le message <4ddc145c$0$4781$ :
Dans « IFS=$'n' ; toto », c'est bien une instruction. Dans « IFS=$'n' toto », ça n'en est pas une. ["ça" étant la partie « IFS=$'n' »]
Mais si ce n'est pas une instruction, qu'est-ce donc alors ?
Une modification de l'environnement de la commande qui suit. Un petit exemple :
% cat getenv.c #include <stdio.h> #include <stdlib.h> int main(void) { char *v = getenv("ma_variable"); if (v != NULL) printf("%sn", v); else printf("ma_variable n'existe pasn"); return 0; } % cc -o getenv getenv.c % ./getenv ma_variable n'existe pas % ma_variable«c ./getenv abc % ma_variable=xyz % ./getenv ma_variable n'existe pas % export ma_variable % ./getenv xyz % ma_variable«c ./getenv abc % ./getenv xyz % unset ma_variable % ./getenv ma_variable n'existe pas
-- Benoit Izac
Bonjour,
le 24/05/2011 à 22:26, Francois Lafont a écrit dans le message
<4ddc145c$0$4781$426a74cc@news.free.fr> :
Dans « IFS=$'n' ; toto », c'est bien une instruction.
Dans « IFS=$'n' toto », ça n'en est pas une.
["ça" étant la partie « IFS=$'n' »]
Mais si ce n'est pas une instruction, qu'est-ce donc alors ?
Une modification de l'environnement de la commande qui suit. Un petit
exemple :
% cat getenv.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *v = getenv("ma_variable");
if (v != NULL)
printf("%sn", v);
else
printf("ma_variable n'existe pasn");
return 0;
}
% cc -o getenv getenv.c
% ./getenv
ma_variable n'existe pas
% ma_variable«c ./getenv
abc
% ma_variable=xyz
% ./getenv
ma_variable n'existe pas
% export ma_variable
% ./getenv
xyz
% ma_variable«c ./getenv
abc
% ./getenv
xyz
% unset ma_variable
% ./getenv
ma_variable n'existe pas
Avez-vous des explications ? J'ai l'impression que ça ressemble à une histoire de timing d'expansion de variable, un peu comme on peut en rencontrer en TeX/LaTeX mais je me trompe peut-être.
Bon, je crois que j'ai trouvé l'explication dans la page man de bash sous « Word Splitting » ou dans SUSv3 : <http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05>
En tout cas, je me rappelle qu'Aéris avec pour habitude d'appeler systématiquement ses variables ${variable} et bien je crois que je vais prendre l'habitude de les appeler "$variable".
C'est clairement conseillé.
-- Benoit Izac
Bonjour,
le 30/05/2011 à 14:55, Francois Lafont a écrit dans le message
<4de39384$0$17768$426a74cc@news.free.fr> :
Je viens de remarquer que si on mettait des doubles quotes autour de la
variable fichier ainsi :
#------------------------------------------------
echo $' 3espacesPuis2 n 1espacesPuis4 ' | while IFS=$'n' read
fichier
do
echo debut---"$fichier"---fin
done
#------------------------------------------------
Alors on avait bien le fameux résultat attendu, à savoir :
Avez-vous des explications ? J'ai l'impression que ça ressemble à une
histoire de timing d'expansion de variable, un peu comme on peut en
rencontrer en TeX/LaTeX mais je me trompe peut-être.
Bon, je crois que j'ai trouvé l'explication dans la page man de bash
sous « Word Splitting » ou dans SUSv3 :
<http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05>
En tout cas, je me rappelle qu'Aéris avec pour habitude d'appeler
systématiquement ses variables ${variable} et bien je crois que je vais
prendre l'habitude de les appeler "$variable".
Avez-vous des explications ? J'ai l'impression que ça ressemble à une histoire de timing d'expansion de variable, un peu comme on peut en rencontrer en TeX/LaTeX mais je me trompe peut-être.
Bon, je crois que j'ai trouvé l'explication dans la page man de bash sous « Word Splitting » ou dans SUSv3 : <http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05>
En tout cas, je me rappelle qu'Aéris avec pour habitude d'appeler systématiquement ses variables ${variable} et bien je crois que je vais prendre l'habitude de les appeler "$variable".