Làm thế nào để vượt qua kết quả của `find` như một danh sách các tệp?


11

Tình huống là, tôi có một máy nghe nhạc MP3 mpg321chấp nhận danh sách các tệp làm đối số. Tôi giữ âm nhạc của mình trong một thư mục có tên là "âm nhạc", trong đó có một vài thư mục nữa. Tôi chỉ muốn chơi tất cả trong số họ, vì vậy tôi chạy chương trình với

mpg321 $(find /music -iname "*\.mp3")

. Vấn đề là, một số tên tệp có khoảng trắng trong đó và chương trình chia các tên đó thành các phần nhỏ hơn và phàn nàn về các tệp bị thiếu. Gói kết quả của finddấu ngoặc kép

mpg321 "$(find /music -iname "*\.mp3")"

không giúp ích gì vì tất cả sẽ trở thành một "tên tệp" lớn, rõ ràng là không tìm thấy.

Làm thế nào tôi có thể làm điều này sau đó? Nếu vấn đề đó, tôi đang sử dụng bash, nhưng sẽ sớm chuyển sang zsh.

Câu trả lời:


17

Hãy thử sử dụng find -print0hoặc -printftùy chọn kết hợp với xargsnhư thế này:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Cách thức hoạt động này được giải thích bởi trang hướng dẫn của find :

-print0

Thật; in tên tệp đầy đủ trên đầu ra tiêu chuẩn, theo sau là ký tự null (thay vì ký tự dòng mới mà -print sử dụng). Điều này cho phép tên tệp chứa dòng mới hoặc các loại khoảng trắng khác được diễn giải chính xác bởi các chương trình xử lý đầu ra tìm. Tùy chọn này tương ứng với tùy chọn -0 của xargs.


Xin lỗi cho tất cả các chỉnh sửa. Tôi dường như nhớ mẫu -print0 xarg -0 không thành công trên các loại khoảng trắng khác, nhưng tôi không thể tìm thấy một ví dụ.
Steven D

oh vâng, nó hoạt động! nhưng tôi vẫn tự hỏi tại sao mpg321 $(find /music -iname "*\.mp3" -print0)không?
phunehehe

1
Chà, tôi tin rằng nó không hoạt động vì hai lý do: (1) tên tệp được phân tách bằng ký tự null, đó không phải là điều mà mpg321 đang mong đợi và (2) xargs đang thực hiện công việc thoát khỏi khoảng trắng khác, không phải tìm thấy.
Steven D

2
@phunehehe, @Steven: mpg321không liên quan gì đến nó, đó là cái vỏ phá vỡ đầu ra của findcác đối số riêng biệt. Và -print0 | xargs -0sẽ làm việc với mọi tên tập tin có thể.
Gilles 'SO- ngừng trở nên xấu xa'

8
find /music -iname "*\.mp3" -exec mpg123 {} +

Với GNU find, bạn cũng có thể sử dụng -print0xargs -0 , nhưng có rất ít điểm trong việc học một công cụ khác. Các -exec ... {} +cú pháp ít đề cập vì Linux đã mua nó muộn hơn -print0, nhưng không có lý do không sử dụng nó ngay bây giờ.

Với zsh hoặc bash 4, việc này đơn giản hơn rất nhiều:

mpg123 **/*.[Mm][Pp]3

Chỉ trong zsh, bạn có thể tạo một (một phần của mẫu) không phân biệt chữ hoa chữ thường:

mpg123 (#i)**/*.mp3

+1 cho điều này, "find -print0" là một hack xấu xí.
p-static

2

Tôi nghĩ giải pháp của Steven là tốt nhất, nhưng một cách khác là sử dụng -Icờ của xargs , cho phép bạn chỉ định một chuỗi sau đó sẽ được thay thế trong lệnh bằng đối số (thay vì chỉ thêm đối số vào cuối lệnh). Bạn có thể sử dụng điều đó để trích dẫn đối số:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"

Tôi không hiểu câu trả lời này ... Bạn đang sử dụng -0cờ của xargs mà không tìm thấy -print0. Ngoài ra, -Icờ của xargs có một số hậu quả. Không giống như chỉ đơn giản là xargssẽ nén tất cả các dòng từ stdin vào một lệnh, xargs -Isẽ thực thi mpg321tiềm năng hàng trăm hoặc hàng nghìn lần (một lần cho mỗi tệp) mà chắc chắn không phải là mục đích. Cũng nên nhớ rằng trích dẫn foo là không cần thiết cũng như xargsđiều này trong nội bộ. Lưu ý nếu bạn nén tất cả tên tệp vào dòng và gửi nó đến xargs -I, chúng sẽ không mở.
Sáu

1

Thường là tốt nhất để trực tiếp -exec ${tgt_process} \{\} +nhưng nếu bạn cần có một danh sách tên tệp được phân tách đáng tin cậy trong một tệp hoặc truyền phát từ findbất kỳ lý do gì, thì bạn có thể làm điều này:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

Những gì bạn nhận được từ đó là hai chuỗi duy nhất . Ở đầu mỗi tên tệp là chuỗi \n///và ở đuôi của mỗi tên tệp là chuỗi ///\n. Hai chuỗi này không xuất hiện ở bất kỳ nơi nào khác trong findđầu ra ngoại trừ ở các vị trí đó bất kể tên ký tự chứa tên gì.

Ngoài ra, việc sử dụng ở trên là POSIX cơ bản di động và có thể dựa vào để hoạt động trên gần như bất kỳ hệ thống unix nào. Điều này không đúng với việc sử dụng một dấu phân cách byte null - mặc dù sự tiện lợi của nó - được đề xuất bởi một số người khác.

Nhưng, một lần nữa, điều này chỉ cần thiết nếu bạn không thể trực tiếp -execcủa bạn $tgt_processvì lý do gì, vì điều đó nên mục tiêu của bạn. Đối với một điều, phương pháp trên vẫn yêu cầu phân tích cú pháp. Ví dụ: nếu bạn muốn mỗi vỏ tên tệp được trích dẫn, trước tiên bạn phải đảm bảo mọi trích dẫn cứng trong tên tệp được thoát:

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

Điều đó tạo ra một loạt tên tệp được thoát vỏ đúng cách bất kể các ký tự cấu thành của chúng có thể là gì. Bây giờ bạn chỉ cần hy vọng rằng ứng dụng của bạn ở đầu nhận sẽ không xử lý được.


0

Một cách khác để làm điều này là thoát tất cả các ký tự đặc biệt có trong tên tệp của bạn. Ví dụ:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Điều này về cơ bản sẽ chuyển tên tệp được thoát đúng cho xargs để thực thi và sẽ không có bất kỳ vấn đề nào.


1
Điều này vẫn phá vỡ các dòng mới trong tên tập tin. Và bạn cũng có thể sed 's|.|\\&|g'- đó là những gì POSIX khuyên dùng.
mikeerv
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.