Boucler sur une liste de fichier
Boucler sur une liste de fichier sans se soucier des espaces avec bash :
| 1 2 3 4 | while read file do echo "fichier .txt : [$file]" done < <(find . | grep .txt) | 
Ici, find . | grep .txt me donne la liste de fichier qui m'interesse.
Explications
Durant tous les exemples, mon repertoire courant contient les fichiers suivant :
| 1 2 3 4 5 | $ ls -l
total 0
-rw-r--r--  1 debona  staff  0 17 oct 22:51 du_texte.txt
-rw-r--r--  1 debona  staff  0 17 oct 19:58 un nom de merde.txt
-rw-r--r--  1 debona  staff  0 18 oct 20:20 un_autre.html
 | 
Je vais détailler deux manières naïves de parcourir une liste dans un shellscript :
- la boucle for
- la boucle while
La version chiante : la boucle for
En shell, la boucle for utilise le séparateur $IFS (Internal Field Separator) qui n'est pas le retour à la ligne par défaut.
Pour bash, l’$IFS vaut whitespace par défaut. C'est à dire l'espace, la tabulation et le retour à la ligne.
Ça commence déjà à être chiant…
| 1 2 3 4 | for file in `find . | grep .txt` do echo "[$file]" done | 
Va afficher :
| 1 2 3 4 5 | [un] [nom] [de] [merde.txt] [du_texte.txt] | 
Facile ! Il suffit de changer la valeur de $IFS par le retour à la ligne !
| 1 2 3 4 5 6 | IFS=' ' # Si seulement IFS="\n" pouvait marcher... for file in `find . | grep .txt` do echo "[$file]" done | 
Yeah, ça boucle sur chaque fichier :
| 1 2 | [un nom de merde.txt] [du_texte.txt] | 
Du coup, qu'est qu'il y a de chiant ?
Typiquement, changer l’$IFS commence à devenir casse couille quand on a des boucles imbriquées parce que ça implique de changer l’$IFS à l'interieur des boucles : et ça, c'est casse gueule.
La version sympa : la boucle while
| 1 2 3 4 | find . | grep .txt | while read file
do
    echo "[$file]"
done
 | 
La commande “builtin” read de bash lit ligne par ligne depuis l'entrée standard.
(Attention, ça lit les lignes vides alors que for ignore les items vide.)
Par contre, encore un piège, le corps du while est executé dans un subshell.
Comme un subshell est un un fork (un processus fils), il y a deux trucs casse-gueules :
La mémoire du subshell n'est pas partagée avec le reste du script : Tout ce qu'il y a dedans sera inaccessible à la fin de l'execution du subshell.
Si on place un exit dans le subshell : surprise ! On arrête le subshell, et le script principal reprend son execution !
En tant que tel, le mot clef while ne créer pas de subshell par lui même. En vérité, c'est le pipe (le |) qui en créer forcément un !
OK alors comment éviter le pipe ? Comme ça ?
| 1 2 3 4 5 6 7 8 9 10 11 | # redirection de la sortie de la commande dans un fichier find . | grep .txt > tmp.txt # redirection du fichier dans l'entré du while while read file do echo "[$file]" done < tmp.txt # suppression du fichier rm tmp.txt | 
L'avantage de cette methode c'est que pour lire le fichier, le while ne fait pas de subshell. L'inconvenient, c'est qu'il faut gérer le fichier, c'est à dire le créer et le supprimer.
Avec bash, il y a une astuce pour faire ça. Elle s'appelle process substitution.
On s'en sert en utilisant <(ma_commande), exemple :
| 1 | <(find . | grep .txt) | 
Et comment ça marche ça ?
| 1 2 3 4 5 | echo <(find . | grep .txt) # affiche le chemin vers un "fichier" contenant la sortie de la commande # => /dev/fd/63 cat <(find . | grep .txt) # du coup, si on `cat` ce "fichier"... # => un nom de merde.txt # => du_texte.txt | 
Et voila comment on abouti à une ecriture élégante et pas trop criptic d'une boucle qui parcour gentillement une liste de fichier.
Mais bon, c'était juste pour le fun. Il vaut mieux faire ça avec ruby ou n'importe quel autre vrai langage de script ;)