tìm -exec một hàm shell trong Linux?


185

Có cách nào để findthực hiện một chức năng mà tôi xác định trong trình bao không? Ví dụ:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

Kết quả của điều đó là:

find: dosomething: No such file or directory

Có cách nào để có được find's -execđể xem dosomething?

Câu trả lời:


262

Vì chỉ shell mới biết cách chạy các hàm shell, nên bạn phải chạy shell để chạy hàm. Bạn cũng cần đánh dấu chức năng của mình để xuất export -f, nếu không, lớp con sẽ không kế thừa chúng:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;

7
Bạn đánh tôi. Bằng cách này, bạn có thể đặt dấu ngoặc trong dấu ngoặc kép thay vì sử dụng $0.
Tạm dừng cho đến khi có thông báo mới.

20
Nếu bạn sử dụng, find . -exec bash -c 'dosomething "$0"' {} \;nó sẽ xử lý khoảng trắng (và các ký tự lạ khác) trong tên tệp ...
Gordon Davisson

3
@alxndr: điều đó sẽ thất bại với tên tập tin với dấu ngoặc kép, backquote, ký hiệu đô la, một số combo thoát, v.v ...
Gordon Davisson

4
Cũng lưu ý rằng mọi chức năng mà chức năng của bạn có thể đang gọi sẽ không khả dụng trừ khi bạn cũng xuất các chức năng đó.
hraban

3
Các export -fchỉ hoạt động trong một số phiên bản của bash. Nó không posix, không crossplatforn, /bin/shsẽ có lỗi với nó
Роман Коптев

120
find . | while read file; do dosomething "$file"; done

10
Giải pháp tốt đẹp. Không yêu cầu xuất hàm hoặc làm rối xung quanh việc thoát các đối số và có lẽ hiệu quả hơn vì nó không sinh ra các chuỗi con để thực thi từng hàm.
Tom

19
Mặc dù vậy, hãy ghi nhớ rằng nó sẽ phá vỡ tên tệp chứa dòng mới.
chepner

3
Đây là "shell'ish" hơn vì các biến và hàm toàn cầu của bạn sẽ có sẵn, mà không tạo ra một shell / môi trường hoàn toàn mới mỗi lần. Đã học được điều này một cách khó khăn sau khi thử phương pháp của Adam và gặp phải tất cả các vấn đề môi trường. Phương pháp này cũng không làm hỏng lớp vỏ người dùng hiện tại của bạn với tất cả các bản xuất và yêu cầu ít dự đoán hơn.
Ray Foss

RẤT NHIỀU hơn câu trả lời được chấp nhận không có vỏ phụ! ĐẸP! Cảm ơn bạn!
Bill Heller

2
tôi cũng đã sửa vấn đề của mình bằng cách thay đổi while readvòng lặp for; for item in $(find . ); do some_function "${item}"; done
dùng5359531

22

Câu trả lời của Jak ở trên rất hay nhưng có một vài cạm bẫy dễ dàng vượt qua:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

Điều này sử dụng null làm dấu phân cách thay vì nguồn cấp dữ liệu, vì vậy tên tệp với nguồn cấp dữ liệu sẽ hoạt động. Nó cũng sử dụng -rcờ vô hiệu hóa dấu gạch chéo ngược, không có dấu gạch chéo ngược trong tên tệp sẽ không hoạt động. Nó cũng xóa IFSđể các khoảng trắng tiềm ẩn trong tên không bị loại bỏ.


1
Nó tốt cho /bin/bashnhưng sẽ không hoạt động /bin/sh. Thật đáng tiếc.
Chúng tôi đã làm việc vào

@ Ооо о о о may may may may may may may may
sdenham

21

Thêm dấu ngoặc kép {}như hình dưới đây:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

Điều này sửa bất kỳ lỗi nào do các ký tự đặc biệt được trả về find, ví dụ các tệp có dấu ngoặc đơn trong tên của chúng.


1
Đây không phải là cách chính xác để sử dụng {}. Điều này sẽ phá vỡ cho một tên tệp có chứa dấu ngoặc kép. touch '"; rm -rf .; echo "I deleted all you files, haha. Giáo sư.
gniourf_gniourf

Vâng, điều này là rất xấu. Nó có thể được khai thác bằng cách tiêm. Rất không an toàn. Đừng dùng cái này!
Dominik

1
@kdub: Sử dụng $ 0 (không trích dẫn) trong chuỗi lệnh và truyền tên tệp làm đối số đầu tiên: -exec bash -c 'echo $0' '{}' \;Lưu ý rằng khi sử dụng bash -c, $ 0 là đối số đầu tiên, không phải tên tập lệnh.
sdenham

10

Kết quả xử lý hàng loạt

Để tăng hiệu quả, nhiều người sử dụng xargsđể xử lý kết quả với số lượng lớn, nhưng nó rất nguy hiểm. Do đó, có một phương pháp thay thế được đưa vào findđể thực hiện kết quả hàng loạt.

Lưu ý rằng phương thức này có thể đi kèm với một số cảnh báo như ví dụ một yêu cầu trong POSIX- findphải có {}ở cuối lệnh.

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findsẽ chuyển nhiều kết quả dưới dạng đối số cho một lệnh gọi bashfor-loop lặp lại thông qua các đối số đó, thực thi hàm dosomethingtrên từng đối số đó.

Giải pháp trên bắt đầu các đối số tại $1, đó là lý do tại sao có một _(đại diện $0).

Xử lý kết quả từng cái một

Theo cách tương tự, tôi nghĩ rằng câu trả lời hàng đầu được chấp nhận nên được sửa thành

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

Điều này không chỉ lành mạnh hơn, bởi vì các đối số luôn luôn bắt đầu $1, mà còn sử dụng $0có thể dẫn đến hành vi không mong muốn nếu tên tệp được trả về findcó ý nghĩa đặc biệt đối với trình bao.


9

Có kịch bản gọi chính nó, chuyển từng mục được tìm thấy như một đối số:

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

Khi bạn tự chạy tập lệnh, nó sẽ tìm thấy những gì bạn đang tìm kiếm và gọi chính nó đi qua từng kết quả tìm kiếm làm đối số. Khi tập lệnh được chạy với một đối số, nó thực thi các lệnh trên đối số và sau đó thoát.


ý tưởng tuyệt vời nhưng phong cách xấu: sử dụng cùng một kịch bản cho hai mục đích. nếu bạn muốn giảm số lượng tệp trong thùng của mình / thì bạn có thể hợp nhất tất cả các tập lệnh của mình vào một tập lệnh có mệnh đề trường hợp lớn khi bắt đầu. giải pháp rất sạch sẽ, phải không?
dùng829755

không đề cập đến điều này sẽ thất bại find: ‘myscript.sh’: No such file or directorynếu bắt đầu như bash myscript.sh...
Camusensei

5

Đối với những bạn đang tìm kiếm một hàm bash sẽ thực thi một lệnh đã cho trên tất cả các tệp trong thư mục hiện tại, tôi đã biên dịch một từ các câu trả lời trên:

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

Lưu ý rằng nó ngắt với tên tệp chứa khoảng trắng (xem bên dưới).

Ví dụ, lấy chức năng này:

world(){
    sed -i 's_hello_world_g' "$1"
}

Nói rằng tôi muốn thay đổi tất cả các trường hợp chào sang thế giới trong tất cả các tệp trong thư mục hiện tại. Tôi sẽ làm:

toall world

Để an toàn với bất kỳ ký hiệu nào trong tên tệp, hãy sử dụng:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(nhưng bạn cần một findcái mà xử lý -print0, ví dụ, GNU find).


3

Tôi tìm thấy cách dễ nhất như sau, lặp lại hai lệnh trong một lần làm

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done

3

Để tham khảo, tôi tránh kịch bản này bằng cách sử dụng:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

Nhận kết quả tìm kiếm trong tệp tập lệnh hiện tại và lặp lại kết quả đầu ra như bạn muốn. Tôi đồng ý với câu trả lời được chấp nhận, nhưng tôi không muốn để lộ chức năng bên ngoài tệp tập lệnh của mình.


cái này có giới hạn kích thước
Richard

2

Không thể thực thi một chức năng theo cách đó.

Để khắc phục điều này, bạn có thể đặt chức năng của mình trong tập lệnh shell và gọi nó từ find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

Bây giờ sử dụng nó trong tìm như:

find . -exec dosomething.sh {} \;

Đã cố tránh nhiều tập tin hơn trong ~ / bin của tôi. Cảm ơn mặc dù!
alxndr

Tôi đã xem xét hạ cấp nhưng bản thân giải pháp không tệ. Vui lòng chỉ sử dụng trích dẫn chính xác: dosomething $1=> dosomething "$1"và bắt đầu tệp của bạn một cách chính xác vớifind . -exec bash dosomething.sh {} \;
Camusensei

2

Đặt hàm vào một tệp riêng và nhận find thực hiện điều đó.

Các hàm Shell là nội bộ của shell mà chúng được xác định trong; findsẽ không bao giờ có thể nhìn thấy chúng.


Gotcha; có ý nghĩa. Đã cố gắng tránh nhiều tập tin hơn trong ~ / bin của tôi.
alxndr

2

Để cung cấp các bổ sung và làm rõ cho một số câu trả lời khác, nếu bạn đang sử dụng tùy chọn hàng loạt cho exechoặc execdir( -exec command {} +) và muốn truy xuất tất cả các đối số vị trí, bạn cần xem xét xử lý $0với bash -c. Cụ thể hơn, hãy xem xét lệnh bên dưới, sử dụng bash -cnhư được đề xuất ở trên và chỉ đơn giản lặp lại các đường dẫn tệp kết thúc bằng '.wav' từ mỗi thư mục mà nó tìm thấy:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

Hướng dẫn bash nói:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

Ở đây, 'check $@'là chuỗi lệnh và _ {}là các đối số sau chuỗi lệnh. Lưu ý rằng đó $@là một tham số vị trí đặc biệt trong bash mở rộng cho tất cả các tham số vị trí bắt đầu từ 1 . Cũng lưu ý rằng với -ctùy chọn, đối số đầu tiên được gán cho tham số vị trí $0. Điều này có nghĩa là nếu bạn cố gắng truy cập tất cả các tham số vị trí với $@, bạn sẽ chỉ nhận được các tham số bắt đầu từ $1và lên. Đó là lý do tại sao câu trả lời của Dominik có _, đó là một đối số giả để điền tham số $0, vì vậy tất cả các đối số chúng tôi muốn có sẵn sau này nếu chúng tôi sử dụng$@ mở rộng tham số, hoặc vòng lặp for như trong câu trả lời đó.

Tất nhiên, tương tự như câu trả lời được chấp nhận, bash -c 'shell_function $0 $@'cũng sẽ hoạt động bằng cách vượt qua một cách rõ ràng $0, nhưng một lần nữa, bạn sẽ phải nhớ rằng $@sẽ không hoạt động như mong đợi.


0

Không trực tiếp, không. Tìm là thực thi trong một quy trình riêng biệt, không phải trong vỏ của bạn.

Tạo một tập lệnh shell thực hiện công việc tương tự như chức năng của bạn và có thể tìm thấy -execđiều đó.


Đã cố tránh nhiều tập tin hơn trong ~ / bin của tôi. Cảm ơn mặc dù!
alxndr

-2

Tôi sẽ tránh sử dụng -exechoàn toàn. Tại sao không sử dụng xargs?

find . -name <script/command you're searching for> | xargs bash -c

Vào thời điểm đó, IIRC đã cố gắng giảm lượng tài nguyên được sử dụng. Hãy suy nghĩ tìm hàng triệu tệp trống và xóa chúng.
alxndr
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.