Lặp lại đệ quy thông qua các tập tin trong một thư mục


15

Lặp đi lặp lại qua các tệp trong một thư mục có thể dễ dàng được thực hiện bằng cách:

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

Tuy nhiên, ở trên không hoạt động cho những thứ phức tạp hơn, trong đó rất nhiều nhánh có điều kiện, vòng lặp, vv cần phải được thực hiện. Tôi đã từng sử dụng điều này cho ở trên:

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

Tuy nhiên, có vẻ như điều này không hoạt động đối với các tệp chứa các ký tự tối nghĩa:

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

Có một sự thay thế xử lý tốt các nhân vật tối nghĩa như vậy?


1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;là một cách tốt hơn để làm điều đó.
jw013

Bạn có thể giải quyết điều này và giữ thiết kế ban đầu của mình bằng cách thay đổi read linethành IFS= read -r line. Nhân vật duy nhất sẽ phá vỡ nó sau đó là một dòng mới.
Patrick

1
@Patrick, nhưng tên tệp có thể chứa dòng mới. Đó là lý do tại sao -d $'\0'thích hợp hơn.
trời ơi

Câu trả lời:


7

Một cách sử dụng khác cho an toànfind :

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

(Điều này hoạt động với bất kỳ POSIX nào find, nhưng phần vỏ yêu cầu bash. Với * BSD và GNU find, bạn có thể sử dụng -print0thay vì -exec printf '%s\0' {} +, nó sẽ nhanh hơn một chút.)

Điều này cho phép sử dụng đầu vào tiêu chuẩn trong vòng lặp và nó hoạt động với bất kỳ đường dẫn nào .


1
Bởi vì tôi đã phải tra cứu nó: "đọc ... Nếu không có tên nào được cung cấp, dòng đọc được gán cho biến REPLY." Vì vậydo echo "Filename is '$REPLY'"
Andrew

9

Làm điều này đơn giản như:

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

Hoặc là...

find -exec executable_script {} \;

5

Cách tiếp cận đơn giản nhất (nhưng an toàn) là sử dụng shellbing:

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

Để thực hiện các lần lặp lại ở trên thành các thư mục con (trong bash), bạn có thể sử dụng globstartùy chọn; cũng được đặt dotglobđể khớp với các tệp có tên bắt đầu bằng .:

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

Coi chừng rằng bash 4.2, **/đệ quy thành các liên kết tượng trưng đến các thư mục. Kể từ bash 4.3, **/chỉ đệ quy vào các thư mục, như find.

Một giải pháp phổ biến khác là sử dụng find -print0với 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:

Lưu ý rằng điều h:/gnày thực sự đúng vì tên tệp chứa a \r.


4

Có một chút khó khăn để thực hiện vòng lặp đọc của bạn một cách có thể di chuyển, nhưng đối với bash nói riêng, bạn có thể thử một cái gì đó như thế này .

Phần có liên quan:

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

Hướng dẫn findin đầu ra được phân tách bằng các ký tự NUL (0x00) và readtìm nạp các dòng được phân cách bằng NUL ( -d $'\0') mà không xử lý dấu gạch chéo ngược như thoát cho các ký tự khác ( -r) và không thực hiện bất kỳ phân tách từ nào trên các dòng ( IFS=). Vì 0x00 là một byte không thể xuất hiện trong tên tệp hoặc đường dẫn trong Unix, nên điều này sẽ xử lý tất cả các vấn đề về tên tệp kỳ lạ của bạn.


1
-d ''tương đương với -d $'\0'.
l0b0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.