Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

[bash] bonne façon de faire une boucle sur un ensemble de fichiers contenu dans un dossier

35 réponses
Avatar
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.


--
François Lafont

10 réponses

1 2 3 4
Avatar
Aéris
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Le 23/05/2011 05:02, Floris Dubreuil a écrit :
Ben, j'ai comme un doute en voyant toutes les réponses avant moi mais, à
tout hasard, xargs ça sent le paté ?



Avec des espaces dans le nom de fichier, oui?

- --
Aeris
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJN2qKWAAoJEK8zQvxDY4P96pgH/01TPvoeaAbCeuTPj4t+pZZn
61/HCkrJILJAA5kufjI/JdgqQ2XLPH0ITY3H2nzrfvnuYHjre68Nk8aK8xhwWbmN
vBaDXW3ONsh7hRQtbTTH2OpO1Vtn45ekSKJvZFy6k3bKKLigtGw+kRXbiFrP8Pe4
xDkhMjbGr/y65IsJT4uwA/omHwTPSWWcIYbYtPhieyCscr2TPt3w7A+oUgZUV0Yp
63SbDqa5g+Evl9ybL3H5TD77bHYKnCJDR/67l0kSZ4aW6HPkLV89JgQw7HNZkWvz
BE5GbzlvfGtGhpjZ2xyeGLkK/7ruTZdzVHep3nUmsi/+RGsvEEFJ72cHofcnIbI =MG1d
-----END PGP SIGNATURE-----
Avatar
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.

=> Voir
http://linuxfr.org/forums/g%C3%A9n%C3%A9ralcherche-logiciel/posts/caract%C3%A8re-sp%C3%A9ciaux-avec-xargs

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
Avatar
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
Avatar
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
Avatar
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
Avatar
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
Avatar
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) :

#---------------------------------------------
#! /bin/bash

echo $' aaa n bbb ' | while IFS=$'n' read fichier
do
echo debut---${fichier}---fin
done
#---------------------------------------------



Au passage, je pense que IFS='n' peut être sans problème remplacé par
IFS= puisque par définition read ne lit qu'une ligne.

j'ai alors ceci (là aussi je copie-colle) :

#---------------------------------------------
$ ./test.bash
debut--- aaa ---fin
debut--- bbb ---fin
#---------------------------------------------



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
Avatar
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
Avatar
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
Avatar
Benoit Izac
Bonjour,

le 30/05/2011 à 14:55, Francois Lafont a écrit dans le message
<4de39384$0$17768$ :

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 :

debut--- 3espacesPuis2 ---fin
debut--- 1espacesPuis4 ---fin

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>

Tests fait avec bash :
$ printf "$IFS" | od -ta
0000000 sp ht nl
0000003
$ a=' abcd '
$ echo ,$a,
, abcd ,
$ IFS=''
$ echo ,$a,
, abcd ,
$ unset IFS
$ echo ,$a,
, abcd ,
$ IFS='b'
$ echo ,$a,
, a cd ,

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
1 2 3 4