Thực thi chức năng do người dùng xác định trong cuộc gọi find -exec


25

Tôi đang sử dụng Solaris 10 và tôi đã thử nghiệm các điều sau với ksh (88), bash (3.00) và zsh (4.2.1).

Đoạn mã sau không mang lại kết quả nào:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

Tìm kiếm không khớp với một số tệp (như được hiển thị bằng cách thay thế -exec ...bằng -print) và chức năng hoạt động hoàn hảo khi được gọi bên ngoài từ findcuộc gọi.

Đây là những gì man findtrang nói về -exec:

 Lệnh -exec Đúng nếu lệnh được thực thi trả về một
                     không có giá trị như trạng thái thoát. Kết cục của
                     lệnh phải được nhấn mạnh bởi một lối thoát
                     dấu chấm phẩy (;). Đối số lệnh {} là
                     được thay thế bằng tên đường dẫn hiện tại. Nếu
                     đối số cuối cùng cho -exec là {} và bạn
                     chỉ định + thay vì dấu chấm phẩy (;),
                     lệnh được gọi ít lần hơn, với
                     {} được thay thế bằng các nhóm tên đường dẫn. Nếu
                     bất kỳ lệnh gọi nào trả về một
                     giá trị khác không như trạng thái thoát, tìm
                     trả về trạng thái thoát khác không.

Tôi có lẽ có thể thoát khỏi việc làm một cái gì đó như thế này:

for f in $(find somedir); do
    foo
done

Nhưng tôi sợ phải đối phó với các vấn đề phân tách trường.

Có thể gọi một hàm shell (được xác định trong cùng một tập lệnh, chúng ta đừng bận tâm đến các vấn đề phạm vi) từ một find ... -exec ...cuộc gọi?

Tôi đã thử nó với cả hai /usr/bin/find/bin/findvà nhận được kết quả tương tự.


Bạn đã thử xuất hàm sau khi bạn khai báo chưa? export -f foo
h3rrmiller

2
Bạn sẽ cần phải làm cho hàm thành một tập lệnh bên ngoài và đặt nó vào PATH. Ngoài ra, sử dụng sh -c '...'và cả hai định nghĩa VÀ chạy hàm trong ...bit. Nó có thể giúp hiểu được sự khác biệt giữa các chức năng và tập lệnh .
jw013

Câu trả lời:


27

A functionlà cục bộ của một vỏ, vì vậy bạn cần find -execsinh ra một vỏ và có chức năng đó được xác định trong vỏ đó trước khi có thể sử dụng nó. Cái gì đó như:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bashcho phép một người xuất các hàm thông qua môi trường với export -f, vì vậy bạn có thể thực hiện (trong bash):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88typeset -fxchức năng xuất khẩu (không qua môi trường), nhưng điều đó chỉ có thể được sử dụng bởi cô-bang ít kịch bản được thực hiện bởi ksh, vì vậy không có ksh -c.

Một lựa chọn khác là làm:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

Đó là, sử dụng typeset -fđể kết xuất định nghĩa của foohàm bên trong tập lệnh nội tuyến. Lưu ý rằng nếu foosử dụng các chức năng khác, bạn cũng sẽ cần phải loại bỏ chúng.


2
Bạn có thể giải thích tại sao có hai lần xuất hiện kshhoặc bashtrong -execlệnh không? Tôi hiểu lần đầu tiên, nhưng không phải lần thứ hai.
daniel kullmann

6
@danielkullmann Trong bash -c 'some-code' a b c, $0a, $1b..., vì vậy nếu bạn muốn $@trở thành a, b, cbạn cần phải chèn một cái gì đó trước. Bởi vì $0cũng được sử dụng khi hiển thị thông báo lỗi, nên sử dụng tên của shell hoặc một cái gì đó có ý nghĩa trong bối cảnh đó.
Stéphane Chazelas

@ StéphaneChazelas, cảm ơn bạn đã trả lời tốt đẹp như vậy.
Người dùng9102d82

5

Điều này không phải lúc nào cũng có thể áp dụng, nhưng khi có, đó là một giải pháp đơn giản. Đặt globstartùy chọn ( set -o globstartheo ksh93, shopt -s globstartrong bash ≥4; theo mặc định là zsh). Sau đó sử dụng **/để khớp với thư mục hiện tại và các thư mục con của nó theo cách đệ quy.

Ví dụ, thay vì find . -name '*.txt' -exec somecommand {} \;, bạn có thể chạy

for x in **/*.txt; do somecommand "$x"; done

Thay vì find . -type d -exec somecommand {} \;, bạn có thể chạy

for d in **/*/; do somecommand "$d"; done

Thay vì find . -newer somefile -exec somecommand {} \;, bạn có thể chạy

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

Khi **/không phù hợp với bạn (vì shell của bạn không có nó hoặc vì bạn cần một findtùy chọn không có tương tự shell), hãy xác định hàm trong find -execđối số .


Tôi dường như không thể tìm thấy một globstartùy chọn trên phiên bản ksh ( ksh88) tôi đang sử dụng.
rahmu

@rahmu Thật vậy, nó mới ở ksh93. Solaris 10 không có ksh93 ở đâu à?
Gilles 'SO- ngừng trở nên xấu xa'

Theo bài đăng trên blog này, ksh93 đã được giới thiệu trong Solaris 11 để thay thế cả Bourne Shell và ksh88 ...
rahmu

@rahmu. Solaris đã có ksh93 trong một thời gian như dtksh(ksh93 với một số phần mở rộng X11), nhưng một phiên bản cũ và có thể là một phần của gói tùy chọn trong đó CDE là tùy chọn.
Stéphane Chazelas

Cần lưu ý rằng Globing đệ quy khác với findở chỗ nó loại trừ các dotfiles và không đi xuống dotdir và nó sắp xếp danh sách tệp (cả hai đều có thể là địa chỉ trong zsh thông qua vòng loại toàn cầu). Ngoài ra, **/*trái ngược với ./**/*tên tập tin có thể bắt đầu bằng -.
Stéphane Chazelas

4

Sử dụng \ 0 làm dấu phân cách và đọc tên tệp vào quy trình hiện tại từ một lệnh được sinh ra, như vậy:

foo() {
  printf "Hello {%s}\n" "$1"
}

while read -d '' filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

Những gì đang xảy ra ở đây:

  • read -d '' đọc cho đến \ 0 byte tiếp theo, vì vậy bạn không phải lo lắng về các ký tự lạ trong tên tệp.
  • tương tự, -print0sử dụng \ 0 để chấm dứt từng tên tệp được tạo thay vì \ n.
  • cmd2 < <(cmd1)là giống như cmd1 | cmd2ngoại trừ cmd2 được chạy trong vỏ chính và không phải là lớp con.
  • cuộc gọi đến foo được chuyển hướng từ /dev/nullđể đảm bảo nó không vô tình đọc được từ đường ống.
  • $filename được trích dẫn để shell không cố tách một tên tệp có chứa khoảng trắng.

Bây giờ, read -d<(...)đang ở zsh, bash và ksh 93u, nhưng tôi không chắc về các phiên bản ksh trước đó.


4

nếu bạn muốn một tiến trình con, sinh ra từ tập lệnh của bạn, để sử dụng hàm shell được xác định trước, bạn cần xuất nó với export -f <function>

LƯU Ý: export -flà bash cụ thể

vì chỉ một shell có thể chạy các hàm shell :

find / -exec /bin/bash -c 'function "$1"' bash {} \;

EDIT: về cơ bản kịch bản của bạn sẽ giống như thế này:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;

1
export -flà một lỗi cú pháp kshvà in định nghĩa hàm lên màn hình zsh. Trong tất cả ksh, zshbash, nó không giải quyết vấn đề.
rahmu

1
find / -exec /bin/bash -c 'function' \;
h3rrmiller

Bây giờ nó hoạt động, nhưng chỉ với bash. Cảm ơn bạn!
rahmu

4
Không bao giờ nhúng {}vào mã shell! Điều đó có nghĩa là tên tệp được hiểu là mã shell nên rất nguy hiểm
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.