Hiểu tùy chọn -exec của `find`


53

Tôi thấy mình liên tục tìm kiếm cú pháp của

find . -name "FILENAME"  -exec rm {} \;

chủ yếu là vì tôi không thấy chính xác -execphần hoạt động như thế nào . Ý nghĩa của niềng răng, dấu gạch chéo ngược và dấu chấm phẩy là gì? Có trường hợp sử dụng khác cho cú pháp đó?


11
@Philippos: Tôi thấy quan điểm của bạn. Xin lưu ý rằng các trang hướng dẫn là một tài liệu tham khảo, tức là hữu ích cho những người có hiểu biết về vấn đề tìm kiếm cú pháp. Đối với một người mới tham gia chủ đề, họ thường để mật mã và trang trọng để trở nên hữu ích. Bạn sẽ thấy rằng câu trả lời được chấp nhận dài hơn khoảng 10 lần so với mục nhập trang người đàn ông và đó là lý do.
Zsolt Szilagy

6
Ngay cả mantrang POSIX cũ cũng đọc Một tiện ích hoặc đối số chỉ chứa hai ký tự "{}" sẽ được thay thế bằng tên đường dẫn hiện tại , điều này dường như gây khó chịu cho tôi. Ngoài ra, nó có một ví dụ với -exec rm {} \;, giống như trong câu hỏi của bạn. Vào thời của tôi, hầu như không có bất kỳ tài nguyên nào khác ngoài "bức tường lớn màu xám", những cuốn sách của mancác trang in (giấy là hơn so với lưu trữ). Vì vậy, tôi biết rằng điều này là đủ cho một người mới đến chủ đề này. Câu hỏi cuối cùng của bạn là công bằng để hỏi ở đây. Đáng tiếc cả @Kusalananda và bản thân tôi đều không có câu trả lời cho điều đó.
Philippos

1
Hãy đến @Philippos. Bạn có thực sự nói với Kusalananda rằng anh ta đã không cải thiện trang này? :-)
Zsolt Szilagy

1
@allo Mặc dù xargsđôi khi tiện dụng, findcó thể truyền nhiều đối số đường dẫn để ra lệnh mà không cần nó. -exec command... {} +( +thay vì \;) vượt qua càng nhiều đường dẫn sau command...sẽ phù hợp (mỗi HĐH có giới hạn riêng về thời gian của một dòng lệnh). Và cũng giống như xargs, các +hình thức -terminated của find's -exechành động cũng sẽ chạy command...nhiều lần trong trường hợp hiếm hoi mà có quá nhiều con đường để phù hợp trong giới hạn.
Eliah Kagan

2
@ZsoltSzilagy Tôi không nói điều đó cũng không có nghĩa. Anh ấy đã nuôi bạn rất tốt, tôi chỉ nghĩ bạn đủ tuổi để tự ăn. (-;
Philippos

Câu trả lời:


90

Câu trả lời này có trong các phần sau:

  • Sử dụng cơ bản của -exec
  • Sử dụng -execkết hợp vớish -c
  • Sử dụng -exec ... {} +
  • Sử dụng -execdir

Sử dụng cơ bản của -exec

Các -exectùy chọn có một tiện ích bên ngoài với đối số tùy chọn như là đối số và thực thi nó của nó.

Nếu chuỗi {}có mặt ở bất cứ đâu trong lệnh đã cho, mỗi phiên bản của chuỗi sẽ được thay thế bằng tên đường dẫn hiện đang được xử lý (ví dụ ./some/path/FILENAME). Trong hầu hết các shell, hai ký tự {}không cần trích dẫn.

Lệnh cần được kết thúc bằng một ;for findđể biết nó kết thúc ở đâu (vì có thể có thêm tùy chọn sau đó). Để bảo vệ ;vỏ khỏi vỏ, nó cần được trích dẫn là \;hoặc ';', nếu không, vỏ sẽ xem nó là phần cuối của findlệnh.

Ví dụ ( \ở cuối hai dòng đầu tiên chỉ dành cho tiếp tục dòng):

find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'

Điều này sẽ tìm thấy tất cả các tệp thông thường ( -type f) có tên khớp với mẫu *.txttrong hoặc bên dưới thư mục hiện tại. Sau đó, nó sẽ kiểm tra xem chuỗi có helloxảy ra trong bất kỳ tệp nào được tìm thấy hay grep -qkhông (không tạo ra bất kỳ đầu ra nào, chỉ là trạng thái thoát). Đối với những tệp có chứa chuỗi, catsẽ được thực thi để xuất nội dung của tệp vào thiết bị đầu cuối.

Mỗi cái -execcũng hoạt động như một "phép thử" trên các tên đường được tìm thấy find, giống như -type-namekhông. Nếu lệnh trả về trạng thái thoát không (biểu thị "thành công"), phần tiếp theo của findlệnh sẽ được xem xét, nếu không findlệnh sẽ tiếp tục với tên đường dẫn tiếp theo. Điều này được sử dụng trong ví dụ trên để tìm các tệp có chứa chuỗi hello, nhưng bỏ qua tất cả các tệp khác.

Ví dụ trên minh họa hai trường hợp sử dụng phổ biến nhất là -exec:

  1. Như một thử nghiệm để tiếp tục hạn chế tìm kiếm.
  2. Để thực hiện một số loại hành động trên tên đường dẫn tìm thấy (thông thường, nhưng không nhất thiết, ở cuối findlệnh).

Sử dụng -execkết hợp vớish -c

Lệnh -execcó thể thực thi được giới hạn trong một tiện ích bên ngoài với các đối số tùy chọn. Để sử dụng vỏ tích hợp, chức năng, điều kiện, đường ống, chuyển hướng, vv trực tiếp với -execlà không thể, trừ khi được bọc trong một cái gì đó như sh -cvỏ con.

Nếu bashcác tính năng được yêu cầu, sau đó sử dụng bash -cthay thế sh -c.

sh -cchạy /bin/shvới một tập lệnh được đưa ra trên dòng lệnh, theo sau là các đối số dòng lệnh tùy chọn cho tập lệnh đó.

Một ví dụ đơn giản về việc sử dụng sh -cchính nó, không có find:

sh -c 'echo  "You gave me $1, thanks!"' sh "apples"

Điều này chuyển hai đối số cho tập lệnh shell con:

  1. Chuỗi sh. Điều này sẽ có sẵn như $0bên trong tập lệnh và nếu shell bên trong xuất ra một thông báo lỗi, nó sẽ thêm tiền tố vào chuỗi này.

  2. Đối số applescó sẵn như $1trong kịch bản và đã có nhiều đối số hơn, sau đó những đối số này sẽ có sẵn $2, $3v.v. Chúng cũng sẽ có sẵn trong danh sách "$@"(ngoại trừ $0không có trong đó "$@").

Điều này rất hữu ích khi kết hợp với -execnó vì nó cho phép chúng ta tạo các tập lệnh phức tạp tùy ý hoạt động trên các tên đường dẫn được tìm thấy bởi find.

Ví dụ: Tìm tất cả các tệp thông thường có hậu tố tên tệp nhất định và thay đổi hậu tố tên tệp đó thành một số hậu tố khác, trong đó các hậu tố được giữ trong các biến:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'

Bên trong tập lệnh nội bộ, $1sẽ là chuỗi text, $2sẽ là chuỗi txt$3sẽ là bất kỳ tên đường dẫn findnào được tìm thấy cho chúng ta. Việc mở rộng tham số ${3%.$1}sẽ lấy tên đường dẫn và loại bỏ hậu tố .textkhỏi nó.

Hoặc, sử dụng dirname/ basename:

find . -type f -name "*.$from" -exec sh -c '
    mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'

hoặc, với các biến được thêm vào trong tập lệnh nội bộ:

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2; pathname=$3
    mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'

Lưu ý rằng trong biến thể cuối cùng này, các biến fromtotrong vỏ con khác biệt với các biến có cùng tên trong tập lệnh bên ngoài.

Trên đây là cách chính xác để gọi một tập lệnh phức tạp tùy ý từ -execvới find. Sử dụng findtrong một vòng lặp như

for pathname in $( find ... ); do

là dễ bị lỗi và không phù hợp (ý kiến ​​cá nhân). Nó đang phân tách tên tập tin trên các khoảng trắng, gọi tên tập tin toàn cầu và cũng buộc trình bao mở rộng kết quả hoàn chỉnh findtrước khi chạy vòng lặp đầu tiên của vòng lặp.

Xem thêm:


Sử dụng -exec ... {} +

Các ;cuối cùng có thể được thay thế bằng +. Điều này gây ra findviệc thực thi lệnh đã cho với càng nhiều đối số (tên đường dẫn được tìm thấy) càng tốt chứ không phải một lần cho mỗi tên đường dẫn được tìm thấy. Chuỗi {} phải xảy ra ngay trước khi +điều này hoạt động .

find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +

Tại đây, findsẽ thu thập các tên đường dẫn kết quả và thực hiện catcàng nhiều trong số chúng càng tốt.

find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +

Tương tự như vậy ở đây, mvsẽ được thực hiện ít nhất có thể. Ví dụ cuối cùng này yêu cầu GNU mvtừ coreutils (hỗ trợ -ttùy chọn).

Sử dụng -exec sh -c ... {} +cũng là một cách hiệu quả để lặp qua một tập hợp các tên đường dẫn với một tập lệnh phức tạp tùy ý.

Những điều cơ bản giống như khi sử dụng -exec sh -c ... {} ';', nhưng tập lệnh hiện có một danh sách các đối số dài hơn nhiều. Chúng có thể được lặp lại bằng cách lặp "$@"bên trong tập lệnh.

Ví dụ của chúng tôi từ phần cuối thay đổi hậu tố tên tệp:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2
    shift 2  # remove the first two arguments from the list
             # because in this case these are *not* pathnames
             # given to us by find
    for pathname do  # or:  for pathname in "$@"; do
        mv "$pathname" "${pathname%.$from}.$to"
    done' sh "$from" "$to" {} +

Sử dụng -execdir

Ngoài ra còn có -execdir(được thực hiện bởi hầu hết findcác biến thể, nhưng không phải là một tùy chọn tiêu chuẩn).

Điều này hoạt động giống như -execsự khác biệt mà lệnh shell đã cho được thực thi với thư mục của tên đường dẫn tìm thấy là thư mục làm việc hiện tại của nó và {}sẽ chứa tên cơ sở của tên đường dẫn tìm thấy mà không có đường dẫn của nó (nhưng GNU findvẫn sẽ đặt tiền tố cho tên cơ sở ./trong khi BSD findsẽ không làm điều đó).

Thí dụ:

find . -type f -name '*.txt' \
    -execdir mv {} done-texts/{}.done \;

Điều này sẽ di chuyển từng tệp được tìm thấy vào thư mục con *.txttồn tại trong cùng thư mục với nơi tìm thấy tệp . Tập tin cũng sẽ được đổi tên bằng cách thêm hậu tố vào nó.done-texts.done

Điều này sẽ phức tạp hơn một chút -execvì chúng ta sẽ phải lấy tên cơ sở của tệp tìm thấy ra {}để tạo tên mới của tệp. Chúng tôi cũng cần tên thư mục từ {}để xác định vị trí done-textsthư mục đúng.

Với -execdir, một số điều như thế này trở nên dễ dàng hơn.

Các hoạt động tương ứng bằng cách sử dụng -execthay vì -execdirsẽ phải sử dụng một vỏ con:

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
    done' sh {} +

hoặc là,

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "${name%/*}/done-texts/${name##*/}.done"
    done' sh {} +

7
-execlấy một chương trình và lập luận và chạy nó; một số lệnh shell chỉ bao gồm một chương trình và đối số nhưng nhiều lệnh thì không. Một lệnh shell có thể bao gồm chuyển hướng và đường ống; -execkhông thể (mặc dù toàn bộ findcó thể được chuyển hướng). Một lệnh shell có thể sử dụng ; && ifvv; -execkhông thể, mặc dù -a -ocó thể làm một số. Lệnh shell có thể là hàm bí danh hoặc hàm shell hoặc hàm dựng sẵn; -execkhông thể. Một lệnh shell có thể mở rộng vars; -execkhông thể (mặc dù vỏ ngoài chạy lon find). Một lệnh shell có thể thay thế $(command)khác nhau mỗi lần; -execkhông thể. ...
dave_thndry_085

... Một lệnh shell có thể toàn cầu, -execkhông thể - mặc dù findcó thể lặp lại các tệp theo cách giống như hầu hết các quả cầu, vì vậy điều đó hiếm khi muốn.
dave_thndry_085

@ dave_thndry_085 Tất nhiên, lệnh shell có thể là shchính nó, hoàn toàn có khả năng thực hiện tất cả những việc đó
Tavian Barnes

2
Nói rằng đó là một lệnh shell là sai ở đây, find -exec cmd arg \;không gọi shell để diễn giải một dòng lệnh shell, nó chạy execlp("cmd", "arg")trực tiếp, không phải execlp("sh", "-c", "cmd arg")(mà shell sẽ kết thúc tương tự execlp("cmd", "arg")nếu cmdkhông được dựng sẵn).
Stéphane Chazelas

2
Bạn có thể làm rõ rằng tất cả các findđối số sau -execvà lên ;hoặc +tạo lệnh để thực thi cùng với các đối số của nó, với mỗi phiên bản của {}đối số được thay thế bằng tệp hiện tại (với ;) và {}là đối số cuối cùng trước khi được +thay thế bằng danh sách tệp như các đối số riêng biệt (trong {} +trường hợp). IOW -execmột số đối số, chấm dứt bởi một ;hoặc {} +.
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.