Làm cách nào để thực hiện một hành động trên tất cả các tệp có phần mở rộng cụ thể trong các thư mục con một cách trang nhã?


17

Đặt cược tốt nhất hiện tại của tôi là:

for i in $(find . -name *.jpg); do echo $i; done

Vấn đề: không xử lý khoảng trắng trong tên tệp.

Lưu ý: Tôi cũng thích một cách đồ họa để thực hiện việc này, chẳng hạn như lệnh "cây".


Bạn có thể chỉ đơn giản là đặt dấu ngoặc kép $i? Tôi làm điều đó và nó hoạt động tốt. Có điều gì sai với cách tiếp cận đó?
Umar Farooq Khawaja

Câu trả lời:


26

Cách kinh điển là làm

find . -name '*.jpg' -exec echo {} \;

(thay thế \;bằng +để truyền nhiều hơn một tệp echocùng một lúc)

hoặc (GNU cụ thể, mặc dù hiện tại một số BSD cũng có nó):

find . -name '*.jpg' -print0 | xargs -r0 echo

zsh:

for i (**/*.jpg(D)) echo $i

2
Nếu bạn đang sử dụng xargs -0 , bạn nên kết hợp nó với find -0
itbruce

1
@ MichaelKjorling, không trích dẫn {} không tạo ra sự khác biệt. {} được mở rộng bằng cách tìm, không phải vỏ. Một cải tiến sẽ không được sử dụng echođể mở rộng các chuỗi thoát như \b(ít nhất là các tuân thủ Unix echo).
Stéphane Chazelas

1
@sch Bạn có chắc không? Các findtrang web hiển thị find . -type f -exec file '{}' \;trong EXAMPLESphần. Có thể một số vỏ sẽ điều trị niềng răng đặc biệt.
QuasarDonkey

1
Trích dẫn không gây hại nhưng không có sự khác biệt. Tất cả '{}', \{\}, {}, "{}", '{'}được mở rộng bằng vỏ (bất kể Bourne giống như vỏ) để một đối số để thấy rằng là hai nhân vật "{" và "}". Thay thế "find" bằng "echo find" hoặc printf '<%s>\n' findnếu bạn muốn kiểm tra lại.
Stéphane Chazelas


2

Câu trả lời tốt hơn đã được đưa ra.

Nhưng lưu ý rằng không gian không phải là vấn đề duy nhất trong mã bạn đã đưa ra. tab, dòng mới và ký tự đại diện cũng là một vấn đề.

Với

IFS='
'
set -f
for i in $(find . -name '*.jpg'); do echo "$i"; done

Sau đó, chỉ có các nhân vật dòng mới là một vấn đề.


1

Những gì bạn đã đề cập là một trong những vấn đề cơ bản mà mọi người gặp phải, khi họ cố gắng đọc tên tệp. Đôi khi, những người có kiến ​​thức hạn chế và có quan niệm sai lầm về cấu trúc tệp và thư mục có xu hướng quên rằng " Trong UNIX Mọi thứ đều là tệp ". Vì vậy, họ không hiểu rằng họ cũng cần xử lý khoảng trắng, vì tên tệp có thể bao gồm 2 hoặc nhiều từ có khoảng trắng.

Giải pháp: Vì vậy, một trong những cách được biết đến nhiều hơn để làm điều này là đọc sạch .

Chúng tôi ở đây sẽ đọc tất cả các tệp có mặt và giữ chúng trong một biến, lần tới khi thực hiện xử lý mong muốn, chúng tôi sẽ phải giữ biến đó trong dấu ngoặc kép để giữ tên tệp có dấu cách. Đây là một trong những cách cơ bản để làm điều đó, tuy nhiên, các câu trả lời khác được cung cấp bởi những người khác ở đây cũng hoạt động tốt.

KỊCH BẢN :

 find /path/to/files/ -iname "*jpg" | \
  while read I; do
   cp -v --parent "$I" /backup/dir/
  done

Ở đây tôi đang đọc các tệp bằng cách đưa đường dẫn cho chúng và bất cứ điều gì tôi đang đọc tôi đều giữ chúng bên trong một biến I, mà tôi sẽ trích dẫn ở điểm sau trong khi xử lý nó để giữ khoảng trống để xử lý chính xác.

Hy vọng điều này sẽ giúp bạn một cách nào đó.


1
"Tất cả mọi thứ là một tập tin" đề cập đến một cái gì đó khác. Giải pháp của bạn là cụ thể về GUN và không đối phó với dấu gạch chéo ngược hoặc ký tự dòng mới trong tên tệp. Các vòng lặp "trong khi đọc" trong shell (IMO) thường là một dấu hiệu cho thấy thực hành kịch bản shell xấu.
Stéphane Chazelas

@sch: huh, và tôi đã từng nghĩ, nó ổn thôi. Cảm ơn đã chỉ ra điều đó. Có lẽ tôi cần xem xét nó nhiều hơn.
Hiệp sĩ bóng đêm

xin lỗi, ý tôi là "GNU", không phải "GUN" ở trên (đây là lần đầu tiên tôi nhận ra đó là đảo chữ BTW ...)
Stéphane Chazelas

1

Đơn giản chỉ với findbash:

find . -name '*.jpg' -exec bash -c '
    echo "treating file $1 in bash, from path : ${1%/*}" 
' -- {} \;

Bằng cách này, chúng tôi sử dụng $1trong bash, như trong một kịch bản cơ bản, mở ra những viễn cảnh tốt đẹp để thực hiện bất kỳ nhiệm vụ nâng cao (hoặc không) nào.

Một cách hiệu quả hơn (để tránh phải bắt đầu bash mới cho mỗi tệp):

find . -name '*.jpg' -exec bash -c 'for i do
    echo "treating file $i in bash, from path : ${i%/*}" 
  done' -- {} +

0

Trong phiên bản gần đây của bash, bạn có thể sử dụng globstartùy chọn:

shopt -s globstar
for file in **/*.jpg ; do
    echo "$file"
done

Đối với các hành động đơn giản, bạn thậm chí có thể bỏ qua hoàn toàn vòng lặp:

echo **/*.jpg

Mặc dù nó hoạt động trong zsh (nơi tính năng bắt nguồn từ) và với ksh93 (với set -G), nhưng nó không nên được sử dụng trong bash vì phiên bản bash bị phá vỡ cơ bản (giống như GNU grep -r) khi nó đi vào liên kết tượng trưng đến các thư mục (tương đương find -Lhoặc zsh ***/*.jpg). Cũng lưu ý rằng trái với tìm kiếm, nó sẽ xuất hiện các dotfiles và không rơi xuống dotdirs (theo mặc định).
Stéphane Chazelas
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.