Nhận danh sách các thư mục con chứa tệp có tên chứa chuỗi


45

Làm cách nào tôi có thể nhận được danh sách các thư mục con chứa tệp có tên khớp với một mẫu cụ thể?

Cụ thể hơn, tôi đang tìm các thư mục chứa một tệp có chữ 'f' ở đâu đó xuất hiện trong tên tệp.

Lý tưởng nhất là danh sách sẽ không có các bản sao và chỉ chứa đường dẫn mà không có tên tệp.

Câu trả lời:


43
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

Ở trên tìm thấy tất cả các tệp bên dưới thư mục hiện tại ( .) là các tệp thông thường ( -type f) và có fmột nơi nào đó trong tên của chúng ( -name '*f*'). Tiếp theo, sedxóa tên tệp, chỉ để lại tên thư mục. Sau đó, danh sách các thư mục được sắp xếp ( sort) và trùng lặp loại bỏ ( uniq).

Các sedlệnh bao gồm một sự thay thế duy nhất. Nó tìm kiếm các kết quả khớp với biểu thức chính quy /[^/]+$và thay thế bất kỳ thứ gì khớp với không có gì. Ký hiệu đô la có nghĩa là kết thúc của dòng. [^/]+'có nghĩa là một hoặc nhiều ký tự không phải là dấu gạch chéo. Như vậy, /[^/]+$có nghĩa là tất cả các ký tự từ dấu gạch chéo cuối cùng đến cuối dòng. Nói cách khác, cái này khớp với tên tệp ở cuối đường dẫn đầy đủ. Do đó, lệnh sed sẽ loại bỏ tên tệp, không thay đổi tên của thư mục chứa tệp.

Đơn giản hóa

Nhiều sortlệnh hiện đại hỗ trợ một -ucờ mà uniqkhông cần thiết. Đối với GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

Và, đối với MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

Ngoài ra, nếu findlệnh của bạn hỗ trợ nó, có thể findin tên thư mục trực tiếp. Điều này tránh sự cần thiết cho sed:

find . -type f -name '*f*' -printf '%h\n' | sort -u

Phiên bản mạnh mẽ hơn (Yêu cầu công cụ GNU)

Các phiên bản trên sẽ bị nhầm lẫn bởi tên tệp bao gồm dòng mới. Một giải pháp mạnh mẽ hơn là thực hiện sắp xếp trên các chuỗi kết thúc NUL:

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'

Tôi có rất nhiều tập tin khiến việc sắp xếp chúng quá tốn kém. Ném uniqvào hỗn hợp giúp rất nhiều bằng cách loại bỏ các dòng lặp đi lặp lại ngay cạnh nhau. find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'. Hoặc nếu công cụ của bạn cũ hơn một chút, thì uniq có thể không có tùy chọn -z. find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112

1
Người dùng MacOS: Cờ sed không -r. Vì một số lý do, nó -E
David

@David Rất đúng. Trả lời cập nhật để hiển thị -Echo MacOS.
John1024

23

Tại sao không thử điều này:

find / -name '*f*' -printf "%h\n" | sort -u

Câu trả lời hay nhất. Hoàn toàn tương thích POSIX, không giống như một số câu trả lời ở trên, ở trên và cũng kiếm được giải thưởng Đường ống ngắn nhất đặc biệt :).
kkm

Tôi rất thích thấy ai đó hiển thị thời gian của việc này so với những người khác ở trên, bởi vì tôi có cảm giác đây là cách nhanh nhất.
dlamblin

4
@kkm Tôi đồng ý đây là giải pháp tốt nhất nhưng thông số kỹ thuật POSIXfind thực sự khá thưa thớt, -printfnhà điều hành không được chỉ định. Điều này không hoạt động với BSD find. Vì vậy, không "hoàn toàn tương thích POSIX." (Mặc dù sort -u là trong POSIX .)
Wildcard

8

Về cơ bản có 2 phương pháp bạn có thể sử dụng để làm điều này. Một cái sẽ phân tích chuỗi trong khi cái còn lại sẽ hoạt động trên mỗi tệp. Phân tích chuỗi sử dụng một công cụ như grep, sedhoặc awklà rõ ràng là sẽ nhanh hơn nhưng đây là một ví dụ cho thấy cả hai, cũng như làm thế nào bạn có thể "hồ sơ" 2 phương pháp.

Dữ liệu mẫu

Đối với các ví dụ dưới đây, chúng tôi sẽ sử dụng dữ liệu sau

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

Xóa một số *f*tệp khỏi dir1/*:

$ rm dir1/dir10{0..2}/*f*

Cách tiếp cận số 1 - Phân tích cú pháp qua chuỗi

Dưới đây chúng ta sẽ sử dụng các công cụ sau, find, grep, và sort.

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

Cách tiếp cận # 2 - Phân tích cú pháp bằng các tệp

Cùng một chuỗi công cụ như trước đây, ngoại trừ lần này chúng ta sẽ sử dụng dirnamethay vì grep.

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

LƯU Ý: Các ví dụ trên đang sử dụng head -5để chỉ giới hạn số lượng đầu ra mà chúng tôi đang xử lý cho các ví dụ này. Họ thường sẽ bị xóa để có được danh sách đầy đủ của bạn!

So sánh kết quả

Chúng ta có thể sử dụng timeđể xem xét 2 cách tiếp cận.

tên hiệu

real        0m0.372s
user        0m0.028s
sys         0m0.106s

grep

real        0m0.012s
user        0m0.009s
sys         0m0.007s

Vì vậy, tốt nhất là luôn luôn xử lý các chuỗi nếu có thể.

Các phương pháp phân tích chuỗi thay thế

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

quyến rũ

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

ôi

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u

+1 Bởi vì nó hoạt động, nhưng điều thú vị là việc này mất nhiều thời gian hơn câu trả lời của @ John1024
Muhd

@Muhd - vâng, các cuộc gọi đến dirname đều chậm. Tôi đang làm việc trên một giải pháp thay thế.
slm

2

Đây là một cái tôi thấy hữu ích:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq

1

Câu trả lời này là không biết xấu hổ dựa trên câu trả lời slm. Đó là một cách tiếp cận thú vị, nhưng nó có một hạn chế nếu tên tệp và / hoặc tên thư mục có ký tự đặc biệt (dấu cách, cột bán ...). Một thói quen tốt là sử dụng find /somewhere -print0 | xargs -0 someprogam.

Dữ liệu mẫu

Đối với các ví dụ dưới đây, chúng tôi sẽ sử dụng dữ liệu sau

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

Xóa một số *f*tệp khỏi dir1/*/:

rm dir1/dir\ 10{0..2}/*f*

Cách tiếp cận # 1 - Phân tích cú pháp bằng các tệp

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

LƯU Ý : Các ví dụ trên đang sử dụng head -5để chỉ giới hạn số lượng đầu ra mà chúng tôi đang xử lý cho các ví dụ này. Họ thường sẽ bị xóa để có được danh sách đầy đủ của bạn! Ngoài ra, thay thế echobất cứ lệnh nào bạn muốn sử dụng.


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.