Chuyển đổi toàn cầu sang `find`


11

Tôi đã nhiều lần gặp vấn đề này: Tôi có một quả cầu, khớp chính xác với các tệp chính xác, nhưng nguyên nhân Command line too long. Mỗi lần tôi đã chuyển đổi nó thành một số kết hợp findgrephoạt động cho tình huống cụ thể, nhưng không tương đương 100%.

Ví dụ:

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

Có một công cụ để chuyển đổi các khối u thành các findbiểu thức mà tôi không biết? Hoặc có một tùy chọn findđể khớp với quả cầu mà không khớp với một quả cầu giống nhau trong một thư mục con (ví dụ: foo/*.jpgkhông được phép khớp bar/foo/*.jpg)?


Mở rộng dấu ngoặc nhọn và bạn sẽ có thể sử dụng các biểu thức kết quả với -pathhoặc -ipath. find . -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg'nên làm việc - ngoại trừ rằng nó sẽ phù hợp /fooz/blah/bar/quuxA/pic1234d.jpg. Đó sẽ là một vấn đề?
muru

Vâng, đó sẽ là một vấn đề. Nó phải tương đương 100%.
Ole Tange

Vấn đề là chúng ta không có ý tưởng, sự khác biệt chính xác là gì. Mẫu của bạn khá ổn.
peterh - Phục hồi Monica

Tôi đã thêm bài viết mở rộng của bạn như là một câu trả lời cho câu hỏi. Tôi hy vọng nó không quá tệ.
peterh - Phục hồi Monica

Bạn không thể làm gì echo <glob> | cat, giả sử kiến ​​thức của tôi về bash, echo là tích hợp và do đó không có giới hạn lệnh tối đa
Ferrybig

Câu trả lời:


15

Nếu vấn đề là bạn gặp phải một lỗi đối số danh sách quá dài, hãy sử dụng một vòng lặp hoặc một vỏ được tích hợp sẵn. Trong khi command glob-that-matches-too-muchcó thể lỗi, for f in glob-that-matches-too-muchkhông, vì vậy bạn chỉ có thể làm:

for f in foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
do
    something "$f"
done

Vòng lặp có thể rất chậm, nhưng nó sẽ hoạt động.

Hoặc là:

printf "%s\0" foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg |
  xargs -r0 something

( printfđược tích hợp sẵn trong hầu hết các shell, các công việc trên xoay quanh giới hạn của lệnh execve()gọi hệ thống)

$ cat /usr/share/**/* > /dev/null
zsh: argument list too long: cat
$ printf "%s\n" /usr/share/**/* | wc -l
165606

Cũng hoạt động với bash. Tôi không chắc chắn chính xác nơi này được ghi lại.


Cả Vim glob2regpat()và Python đều fnmatch.translate()có thể chuyển đổi các khối thành regex, nhưng cả hai cũng sử dụng .*cho *, khớp với nhau /.


Nếu đó là sự thật, sau đó thay thế somethingbằng echophải làm điều đó.
Ole Tange

1
@OleTange Đó là lý do tại sao tôi đề xuất printf- nó sẽ nhanh hơn gọi echohàng ngàn lần và mang lại sự linh hoạt hơn.
muru

4
Có một giới hạn đối với các đối số có thể được truyền qua exec, áp dụng cho các lệnh bên ngoài như cat; nhưng giới hạn đó không áp dụng cho các lệnh dựng sẵn shell như printf.
Stephen Kitt

1
@OleTange Dòng này không quá dài vì printfđược tích hợp sẵn và các shell có thể sử dụng cùng một phương thức để cung cấp các đối số cho nó mà chúng sử dụng để liệt kê các đối số cho for. catkhông phải là nội dung.
muru

1
Về mặt kỹ thuật, có những cái vỏ như mkshnơi printfkhông được dựng sẵn và vỏ như ksh93nơi catđược xây dựng (hoặc có thể). Xem thêm zargstrong zshcông việc xung quanh nó mà không cần phải dùng đến xargs.
Stéphane Chazelas

9

find(đối với các vị từ -name/ -pathtiêu chuẩn) sử dụng các mẫu ký tự đại diện giống như các khối (lưu ý rằng đó {a,b}không phải là toán tử toàn cục; sau khi mở rộng, bạn nhận được hai khối). Sự khác biệt chính là việc xử lý các dấu gạch chéo (và các tệp dấu chấm và thư mục không được xử lý đặc biệt trong find). *trong ảm đạm sẽ không mở rộng một số thư mục. */*/*sẽ gây ra tới 2 cấp độ của các thư mục được liệt kê. Việc thêm -path './*/*/*'sẽ khớp với bất kỳ tệp nào sâu ít nhất 3 cấp và sẽ không dừng findviệc liệt kê nội dung của bất kỳ thư mục nào ở bất kỳ độ sâu nào.

Đối với đặc biệt đó

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

đôi chút, thật dễ dịch, bạn muốn thư mục ở độ sâu 3, vì vậy bạn có thể sử dụng:

find . -mindepth 3 -maxdepth 3 \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

(hoặc -depth 3với một số findtriển khai). Hoặc POSIXly:

find . -path './*/*/*' -prune \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

Mà sẽ đảm bảo rằng những người *?không thể phù hợp với /nhân vật.

( find, trái với các thông tin sẽ đọc nội dung của các thư mục khác với các thư mục trong thư foo*barmục hiện tại¹ và không sắp xếp danh sách các tệp. Nhưng nếu chúng ta bỏ qua vấn đề phù hợp với [A-Z]hoặc hành vi của */ ?liên quan đến các ký tự không hợp lệ là không xác định, bạn sẽ nhận được cùng một danh sách các tệp).

Nhưng trong mọi trường hợp, như @muru đã chỉ ra , không cần phải dùng đến findnếu chỉ để chia danh sách các tệp thành nhiều lần chạy để vượt qua giới hạn của lệnh execve()gọi hệ thống. Một số shell như zsh(với zargs) hoặc ksh93(với command -x) thậm chí có hỗ trợ dựng sẵn cho điều đó.

Ví dụ, với zsh(các quả cầu cũng có tương đương -type fvà hầu hết các findvị từ khác ), ví dụ:

autoload zargs # if not already in ~/.zshrc
zargs ./foo*bar/quux[A-Z](|.bak)/pic[0-9][0-9][0-9][0-9]?.jpg(.) -- cmd

( (|.bak)Là một trái điều hành glob tới {,.bak}, các (.)glob vòng loại là tương đương với find's -type f, thêm oNvào đó để bỏ qua sắp xếp giống như với find, Dbao gồm dot-file (không áp dụng cho glob này))


Để findthu thập dữ liệu cây thư mục như các quả cầu, bạn sẽ cần một cái gì đó như:

find . ! -name . \( \
  \( -path './*/*' -o -name 'foo*bar' -o -prune \) \
  -path './*/*/*' -prune -name 'pic[0-9][0-9][0-9][0-9]?.jpg' -exec cmd {} + -o \
  \( ! -path './*/*' -o -name 'quux[A-Z]' -o -name 'quux[A-Z].bak' -o -prune \) \)

Đó là prune tất cả các thư mục ở cấp 1, ngoại trừ foo*barnhững người thân, và tất cả ở cấp 2 trừ quux[A-Z]hoặc quux[A-Z].baknhững người thân, và sau đó chọn pic...những người ở mức 3 (và prune tất cả các thư mục ở cấp đó).


3

Bạn có thể viết một biểu thức chính để tìm phù hợp với yêu cầu của bạn:

find . -regextype egrep -regex './foo[^/]*bar/quux[A-Z](\.bak)?/pic[0-9][0-9][0-9][0-9][^/]?\.jpg'

Có một công cụ thực hiện chuyển đổi này để tránh lỗi của con người?
Ole Tange

Không, nhưng chỉ thay đổi tôi đã bị thoát ra ., thêm trận đấu tùy chọn cho .bakvà thay đổi *để [^/]*không phù hợp với những con đường như / foo / foo / bar, vv
sebasth

Nhưng ngay cả chuyển đổi của bạn là sai. ? không được thay đổi thành [^ /]. Đây chính xác là loại lỗi con người tôi muốn tránh.
Ole Tange

1
Tôi nghĩ với egrep, bạn có thể rút ngắn [0-9][0-9][0-9][0-9]?xuống[0-9]{3,4}
wjandrea


0

Tổng quát hóa ghi chú trên câu trả lời khác của tôi , như một câu trả lời trực tiếp hơn cho câu hỏi của bạn, bạn có thể sử dụng shtập lệnh POSIX này để chuyển đổi toàn cầu thành findbiểu thức:

#! /bin/sh -
glob=${1#./}
shift
n=$#
p='./*'

while true; do
  case $glob in
    (*/*)
      set -- "$@" \( ! -path "$p" -o -path "$p/*" -o -name "${glob%%/*}" -o -prune \)
      glob=${glob#*/} p=$p/*;;
    (*)
      set -- "$@" -path "$p" -prune -name "$glob"
      while [ "$n" -gt 0 ]; do
        set -- "$@" "$1"
        shift
        n=$((n - 1))
      done
      break;;
  esac
done
find . "$@"

Được sử dụng với mộtsh quả cầu tiêu chuẩn (vì vậy không phải là hai khối của ví dụ của bạn sử dụng mở rộng dấu ngoặc ):

glob2find './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' \
  -type f -exec cmd {} +

(không bỏ qua các tập tin dấu chấm hoặc tập tin dấu chấm trừ ...không sắp xếp danh sách các tập tin).

Cái đó chỉ hoạt động với các khối liên quan đến thư mục hiện tại, không có .hoặc ..các thành phần. Với một số nỗ lực, bạn có thể mở rộng nó ra bất kỳ địa cầu nào, hơn cả một địa cầu ... Điều đó cũng có thể được tối ưu hóa để glob2find 'dir/*'không tìm kiếm dirđiều tương tự như đối với một mô hình.

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.