Durchlaufen Sie Dateien in einem Verzeichnis rekursiv

15

Das rekursive Durchlaufen von Dateien in einem Verzeichnis kann auf einfache Weise erfolgen durch:

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

Das oben Genannte funktioniert jedoch nicht für komplexere Dinge, bei denen viele bedingte Verzweigungen, Schleifen usw. ausgeführt werden müssen. Ich habe dies für die oben genannten verwendet:

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

Es sieht jedoch so aus, als ob dies nicht für Dateien funktioniert, die dunkle Zeichen enthalten:

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

Gibt es eine Alternative, die solche dunklen Zeichen gut handhabt?

user2064000
quelle
1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;ist ein besserer Weg, es zu tun.
JW013
Sie können dieses Problem lösen und Ihr ursprüngliches Design beibehalten, indem Sie read lineauf ändern IFS= read -r line. Das einzige Zeichen, das es dann unterbricht, ist eine neue Zeile.
Patrick
1
@Patrick, aber Dateinamen können Zeilenumbrüche enthalten. Deshalb -d $'\0'ist vorzuziehen.
Godlygeek

Antworten:

7

Ein weiterer sicherer Zweckfind :

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

(Dies funktioniert mit jedem POSIX find, aber der Shell-Teil erfordert bash. Mit * BSD und GNU find können Sie -print0stattdessen -exec printf '%s\0' {} +etwas schneller arbeiten.)

Dies ermöglicht die Verwendung der Standardeingabe innerhalb der Schleife und funktioniert mit jedem Pfad.

l0b0
quelle
1
Weil ich nachschlagen musste: "read ... Wenn keine Namen angegeben werden, wird die gelesene Zeile der Variablen REPLY zugewiesen." Alsodo echo "Filename is '$REPLY'"
Andrew
9

Dies zu tun ist so einfach wie:

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

Oder...

find -exec executable_script {} \;
mikeserv
quelle
5

Der einfachste (und dennoch sichere) Ansatz ist die Verwendung von Shell-Globbing:

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

Um die obige Rekursion in Unterverzeichnisse (in Bash) umzuwandeln, können Sie die globstarOption verwenden. Stellen Sie außerdem ein dotglob, dass Dateien gefunden werden, deren Name mit Folgendem beginnt .:

$ 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:

**/Beachten Sie, dass bis Bash 4.2 symbolische Verknüpfungen zu Verzeichnissen erstellt werden. Seit Bash 4.3 **/rekursiv nur in Verzeichnisse, wie find.

Eine andere gebräuchliche Lösung ist die Verwendung find -print0mit 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:

Beachten Sie, dass das h:/gtatsächlich korrekt ist, da der Dateiname a enthält \r.

terdon
quelle
4

Es ist ein bisschen schwierig , Ihre Leseschleife portably zu tun, aber für bash insbesondere kann man so etwas versuchen dies .

Relevanter Teil:

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

Das weist findan, seine Ausgabe mit NUL-Zeichen (0x00) zu drucken und readNUL-getrennte Zeilen ( -d $'\0') abzurufen, ohne Backslashes als Escapezeichen für andere Zeichen ( -r) zu behandeln und keine Worttrennung in den Zeilen ( IFS=) vorzunehmen . Da 0x00 ein Byte ist, das in Dateinamen oder Pfaden in Unix nicht vorkommen kann, sollte dies alle Ihre seltsamen Dateinamenprobleme behandeln.

godlygeek
quelle
1
-d ''ist äquivalent zu -d $'\0'.
l0b0