Itérer récursivement à travers les fichiers d'un répertoire

15

L'itération récursive de fichiers dans un répertoire peut facilement être effectuée par:

find . -type f -exec bar {} \;

Cependant, ce qui précède ne fonctionne pas pour les choses plus complexes, où beaucoup de branches conditionnelles, de boucles, etc. doivent être effectuées. J'ai utilisé cela pour ce qui précède:

while read line; do [...]; done < <(find . -type f)

Cependant, il semble que cela ne fonctionne pas pour les fichiers contenant des caractères obscurs:

$ touch $'a\nb'
$ find . -type f
./a?b

Existe-t-il une alternative qui gère bien ces personnages obscurs?

user2064000
la source
1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;est une meilleure façon de le faire.
jw013
Vous pouvez résoudre ce problème, et de garder votre conception originale, en changeant votre read lineà IFS= read -r line. Le seul personnage qui le cassera alors est une nouvelle ligne.
Patrick
1
@Patrick, mais les noms de fichiers peuvent contenir des retours à la ligne. C'est pourquoi -d $'\0'est préférable.
godlygeek

Réponses:

7

Encore une autre utilisation pour la sécuritéfind :

while IFS= read -r -d '' -u 9
do
    [Do something with "$REPLY"]
done 9< <( find . -type f -exec printf '%s\0' {} + )

(Cela fonctionne avec n'importe quel POSIX find, mais la partie shell nécessite bash. Avec * BSD et GNU find, vous pouvez utiliser à la -print0place de -exec printf '%s\0' {} +, ce sera légèrement plus rapide.)

Cela permet d'utiliser une entrée standard dans la boucle, et cela fonctionne avec n'importe quel chemin.

l0b0
la source
1
Parce que je devais le chercher: "lire ... Si aucun nom n'est fourni, la ligne lue est affectée à la variable REPLY." Doncdo echo "Filename is '$REPLY'"
Andrew
9

Cela est aussi simple que:

find -exec sh -c 'inline script "$0"' {} \;

Ou...

find -exec executable_script {} \;
mikeserv
la source
5

L'approche la plus simple (mais sûre) consiste à utiliser le globbing shell:

$ for f in *; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
h:

Pour faire la récurrence ci-dessus dans des sous-répertoires (en bash), vous pouvez utiliser l' globstaroption; également défini dotglobpour correspondre aux fichiers dont le nom commence par .:

$ shopt -s globstar dotglob
$ for f in **/*; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
:foo:
:foo/file1:
:foo/file two:
h:

Attention, jusqu'à bash 4.2, se **/reproduit en liens symboliques vers des répertoires. Depuis bash 4.3, **/récursif uniquement dans les répertoires, comme find.

Une autre solution courante consiste à utiliser find -print0avec xargs -0:

$ touch -- 'a b' $'c\nd' $'e\tf' $'g\rh' '-e'
$ find . -type f -print0 | xargs -0 -I{} printf ":%s:\n" {}
h:/g
:./e    f:
:./a b:
:./-e:
:./c
d:

Notez que le h:/gest en fait correct car le nom de fichier contient un \r.

terdon
la source
4

C'est un peu difficile de faire votre boucle de lecture de manière portable, mais pour bash en particulier, vous pouvez essayer quelque chose comme ça .

Portion pertinente:

while IFS= read -d $'\0' -r file ; do
        printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)

Cela indique findd'imprimer sa sortie délimitée par des caractères NUL (0x00) et readde récupérer les lignes délimitées NUL ( -d $'\0') sans gérer les barres obliques inverses comme des échappements pour les autres caractères ( -r) et de ne pas séparer les mots sur les lignes ( IFS=). Étant donné que 0x00 est un octet qui ne peut pas se produire dans les noms de fichiers ou les chemins d'accès sous Unix, cela devrait gérer tous vos problèmes de noms de fichiers étranges.

godlygeek
la source
1
-d ''est équivalent à -d $'\0'.
l0b0