Các tệp Glob * không * sử dụng shell

Aug 22 2020

Tôi muốn liệt kê các tệp trong một thư mục con nhất định, nhưng tôi đang làm như vậy như một phần của bộ chứa docker execbên trong bộ chứa docker, vì vậy tôi không muốn bận tâm đến việc khởi động một trình bao mà tôi không thực sự cần. Có thể tìm thấy tất cả các kết quả phù hợp cho một khối cầu bằng một công cụ dòng lệnh đơn giản, và không chỉ một trình bao không?

Ví dụ, lời gọi hiện tại của tôi là bash -l -c 'echo /usr/local/conda-meta/*.json'. Có thể đơn giản hóa điều này bằng cách sử dụng một công cụ thông dụng có sẵn, dẫn đến một cái gì đó như globber /usr/local/conda-meta/*.json, sẽ đơn giản hơn và trọng lượng nhẹ hơn nhiều không?

Trả lời

16 StéphaneChazelas Aug 23 2020 at 05:12

shlà đơn giản và thường có sẵn. shlà công cụ được gọi để phân tích cú pháp các dòng lệnh trong nhiều thứ như system(cmdline)trong nhiều ngôn ngữ. Nhiều hệ điều hành bao gồm một số hệ điều hành GNU đã ngừng sử dụng bash(trình bao GNU) để triển khai shvì lý do nó trở nên quá cồng kềnh để chỉ thực hiện một việc đơn giản là phân tích cú pháp dòng lệnh và diễn giải các shtập lệnh POSIX .

bash -l -c 'echo /usr/local/conda-meta/*.json'Dòng lệnh của bạn có thể đang được thông dịch bởi một shlời gọi. Vì vậy, có thể bạn chỉ có thể làm:

printf '%s\n' /usr/local/conda-meta/*.json

trực tiếp. Nếu không:

sh -c 'printf "%s\n" /usr/local/conda-meta/*.json'

Bạn cũng có thể sử dụng findở đây. findkhông làm nhấp nháy nhưng nó có thể báo cáo tên tệp phù hợp với các mẫu tương tự như các trình bao.

LC_ALL=C find /usr/local/conda-meta/. ! -name . -prune -name '*.json'

Hoặc với một số findtriển khai:

LC_ALL=C find /usr/local/conda-meta -mindepth 1 -maxdepth 1 -name '*.json'

(lưu ý rằng điều LC_ALL=Ccần thiết ở đây để *khớp với bất kỳ chuỗi byte nào, không chỉ những byte đang tạo các ký tự hợp lệ trong ngôn ngữ hiện tại, là một cấu trúc shell. Nếu dòng lệnh đó không được thông dịch bởi shell, bạn có thể cần phải thay đổi nó thành env LC_ALL=C find...)

Một số điểm khác biệt với vỏ cầu:

  • danh sách các tệp không được sắp xếp
  • các tệp ẩn được bao gồm (bạn có thể thêm một ! -name '.*'để loại trừ chúng)
  • bạn không nhận được đầu ra nếu không có tệp phù hợp. các quả cầu có điểm sai đó là chúng để nguyên mẫu - không được mở rộng trong trường hợp đó.
  • với biến thể đầu tiên (tiêu chuẩn), các tệp sẽ được xuất dưới dạng /usr/local/conda-meta/./file.json.
  • một số hình ảnh địa cầu như x*/y/../*zkhông được dịch dễ dàng (cũng lưu ý các hành vi khác nhau đối với các liên kết tượng trưng đến các thư mục trong trường hợp đó).

Trong mọi trường hợp, bạn không thể sử dụng echođể xuất dữ liệu tùy ý.

Câu hỏi tiếp theo của tôi sẽ là: bạn sẽ làm gì với đầu ra đó? Với echo, bạn đang xuất các đường dẫn tệp đó được phân tách bằng ký tự SPC và với của tôi printfhoặc findở trên, được phân cách bằng ký tự NL. Cả hai NLvà SPCđều là các ký tự hoàn toàn hợp lệ trong tên tệp, vì vậy các kết quả đầu ra đó không đáng tin cậy sau khi xử lý. Bạn có thể sử dụng '%s\0'thay vì '%s\n'(hoặc sử dụng find's -print0nếu được hỗ trợ), không phù hợp để trưng bày cho người sử dụng, nhưng sau xử lý.

Về mặt hiệu quả, so sánh Ubuntu /bin/sh20.04 (gạch ngang 0.5.10.2) với find(GNU find4.7.0) của nó.

Thời gian khởi động:

$ time (repeat 1000 sh -c '') ( repeat 1000; do; sh -c ''; done; ) 0.91s user 0.66s system 105% cpu 1.483 total $ time (repeat 1000 find . -quit)
( repeat 1000; do; find . -quit; done; )  1.35s user 1.25s system 103% cpu 2.507 total

Đánh bóng một số jsontệp:

$ TIMEFMT='%U user %S system %P cpu %*E total' $ time (repeat 1000 sh -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
0.95s user 0.72s system 105% cpu 1.587 total
$ time (repeat 1000  find /usr/share/iso-codes/json -mindepth 1 -maxdepth 1 -name '*.json') > /dev/null
1.34s user 1.35s system 103% cpu 2.599 total

Even bashhầu như không chậm hơn findở đây:

$ time (repeat 1000 bash -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
1.53s user 1.36s system 102% cpu 2.808 total

Tất nhiên YMMV tùy thuộc vào hệ thống, cách triển khai, phiên bản của các tiện ích tương ứng và các thư viện mà chúng được liên kết với nhau.

Bây giờ trên phiếu lịch sử, glob tên thực sự xuất phát từ tên của một tiện ích gọi là globtrong các phiên bản đầu tiên của Unix đầu thập niên 70. Nó được đặt tại /etcvà được gọi shnhư một người trợ giúp để mở rộng các mẫu ký tự đại diện.

Bạn sẽ tìm thấy một vài dự án trực tuyến để hồi sinh lớp vỏ rất cũ đó chẳng hạn như https://etsh.nl/. Giống như một bài tập về khảo cổ học, bạn có thể xây dựng globtiện ích từ đó và sau đó có thể làm:

glob printf '%s\n' '/usr/local/conda-meta/*.json'

Tuy nhiên, một số lưu ý cảnh báo.

  • đó là những quả cầu cổ xưa, [!x](hãy để một mình [^x]) không được hỗ trợ.
  • nó không an toàn 8 bit. Trên thực tế, bit thứ 8 được sử dụng để thoát các toán tử cầu ( $'\xe9*'sẽ khớp với điều tương tự như i*, $'\xaa*'sẽ khớp với tên tệp bắt đầu bằng *; shell sẽ đặt bit thứ 8 đó cho các ký tự được trích dẫn trước khi gọi glob)
  • các phạm vi như [a-f]đối sánh trên giá trị byte hơn là thứ tự đối chiếu (trong thực tế, đó thường là một IMO lợi thế).
  • Các quả cầu không khớp dẫn đến No matchlỗi (một lần nữa, có lẽ tốt hơn, đó là thứ đã bị vỡ bởi vỏ Bourne vào cuối những năm 70).

Các globchức năng sau đó đã được chuyển sang vỏ bắt đầu với vỏ PWB và Bourne shell vào cuối thập niên 70. Sau đó, một số fnmatch()và glob()chức năng đã được thêm vào thư viện C để cho phép tính năng đó được sử dụng từ các ứng dụng khác, nhưng tôi không biết về một tiện ích tiêu chuẩn cũng như phổ biến là giao diện trống cho chức năng đó. Thậm chí perlđược sử dụng để gọi cshtrong những ngày đầu của nó để mở rộng các mẫu hình cầu.

7 BasileStarynkevitch Aug 23 2020 at 14:50

Các tệp Glob mà không cần sử dụng shell

Tài liệu rõ ràng để đọc là cầu (7) .

Bạn có thể viết hoặc sử dụng chương trình C gọi fnmatch (3) , global (3) , nftw (3) , stat (2) , readdir (3)

Nếu bạn viết mã bằng Guile , Python , Go , Rust , Ocaml , Common Lisp (ví dụ: SBCL ) ... bạn sẽ tìm thấy các hàm tương tự. Với C ++, hãy xem POCO và Qt .

Tôi giả sử bạn đang sử dụng hệ thống Linux. BTW, trình bao tương tác của tôi là zsh (có tiện ích tự động hoàn thành IMHO thích hợp hơn).