Rekurencyjnie iteruj po plikach w katalogu

15

Rekurencyjne iterowanie plików w katalogu można łatwo wykonać:

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

Powyższe nie działa jednak w przypadku bardziej skomplikowanych rzeczy, w których należy wykonać wiele rozgałęzień warunkowych, zapętlenie itp. Użyłem tego do powyższych celów:

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

Wydaje się jednak, że nie działa to w przypadku plików zawierających niejasne znaki:

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

Czy istnieje alternatywa, która dobrze radzi sobie z tak mało znanymi postaciami?

user2064000
źródło
1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;jest lepszym sposobem na zrobienie tego.
jw013
Możesz rozwiązać ten problem i zachować oryginalny projekt, zmieniając go read linena IFS= read -r line. Jedyną postacią, która go wtedy złamie, jest nowa linia.
Patrick
1
@Patrick, ale nazwy plików mogą zawierać znaki nowej linii. Dlatego -d $'\0'lepiej.
godlygeek

Odpowiedzi:

7

Jeszcze jedno zastosowanie dla bezpieczeństwafind :

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

(Działa to z każdym POSIX-em find, ale część powłoki wymaga bash. Z * BSD i GNU find, możesz użyć -print0zamiast -exec printf '%s\0' {} +, będzie to nieco szybsze.)

Umożliwia to użycie standardowego wejścia w pętli i działa z dowolną ścieżką.

l0b0
źródło
1
Ponieważ musiałem to sprawdzić: „czytaj… Jeśli nie podano żadnych nazw, odczyt linii jest przypisywany do zmiennej REPLY”. Więcdo echo "Filename is '$REPLY'"
Andrew,
9

Wykonanie tego jest tak proste, jak:

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

Lub...

find -exec executable_script {} \;
mikeserv
źródło
5

Najprostszym (ale bezpiecznym) podejściem jest użycie globowania powłoki:

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

Aby zmienić powyższe rekordy w podkatalogi (w bash), możesz użyć globstaropcji; ustawia także dotglobdopasowanie plików, których nazwa zaczyna się od .:

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

Strzeż się, że do wersji bash 4.2 **/powtarza się w symboliczne linki do katalogów. Od wersji bash 4.3 **/rekursywne są tylko katalogi find.

Innym popularnym rozwiązaniem jest używanie find -print0z 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:

Zauważ, że h:/gjest to poprawne, ponieważ nazwa pliku zawiera \r.

terdon
źródło
4

Jest to trochę trudne do zrobienia lektury pętlę przenośnie, ale bash w szczególności można spróbować coś jak ten .

Odpowiednia część:

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

To instruuje, findaby wydrukować wynik rozdzielany znakami NUL (0x00) i readpobrać linie rozdzielane NUL ( -d $'\0') bez obsługi odwrotnych ukośników jako znaków ucieczki dla innych znaków ( -r) i nie dzielenia słów na wiersze ( IFS=). Ponieważ 0x00 jest bajtem, który nie może występować w nazwach plików lub ścieżkach w systemie Unix, powinno to obsłużyć wszystkie problemy z dziwnymi nazwami plików.

godlygeek
źródło
1
-d ''jest równoważne z -d $'\0'.
l0b0