Dapatkan daftar subdirektori yang berisi file yang namanya berisi string


45

Bagaimana saya bisa mendapatkan daftar subdirektori yang berisi file yang namanya cocok dengan pola tertentu?

Lebih khusus lagi, saya mencari direktori yang berisi file dengan huruf 'f' di suatu tempat yang terjadi dalam nama file.

Idealnya, daftar tidak akan memiliki duplikat dan hanya berisi path tanpa nama file.

Jawaban:


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

Di atas menemukan semua file di bawah direktori saat ini ( .) yang merupakan file biasa ( -type f) dan ada fdi suatu tempat di namanya ( -name '*f*'). Selanjutnya, sedmenghapus nama file, hanya menyisakan nama direktori. Kemudian, daftar direktori diurutkan ( sort) dan duplikat dihapus ( uniq).

The sedperintah terdiri dari pengganti tunggal. Ia mencari kecocokan dengan ekspresi reguler /[^/]+$dan menggantikan apa pun yang cocok dengan apa pun. Tanda dolar berarti akhir dari garis. [^/]+'berarti satu atau lebih karakter yang bukan garis miring. Dengan demikian, /[^/]+$berarti semua karakter dari garis miring terakhir ke baris akhir. Dengan kata lain, ini cocok dengan nama file di akhir path lengkap. Dengan demikian, perintah sed menghapus nama file, tidak mengubah nama direktori tempat file itu berada.

Penyederhanaan

Banyak sortperintah modern mendukung -ubendera yang membuatnya uniqtidak perlu. Untuk sed GNU:

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

Dan, untuk MacOS sed:

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

Juga, jika findperintah Anda mendukungnya, dimungkinkan untuk findmencetak nama direktori secara langsung. Ini menghindari kebutuhan untuk sed:

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

Versi lebih kuat (Membutuhkan alat GNU)

Versi di atas akan bingung dengan nama file yang menyertakan baris baru. Solusi yang lebih kuat adalah melakukan pengurutan pada string yang diakhiri NUL:

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

Saya memiliki banyak file yang membuat semuanya terlalu mahal. Melempar uniqke dalam campuran sangat membantu dengan menghapus garis berulang yang sudah tepat bersebelahan. find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'. Atau jika alat Anda sedikit lebih tua, maka uniq mungkin tidak memiliki opsi -z. find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112

1
Pengguna MacOS: Bendera sed bukan -r. Untuk beberapa alasan, -E
David

@ David Sangat benar. Jawaban diperbarui untuk ditampilkan -Euntuk MacOS.
John1024

23

Mengapa tidak mencoba ini:

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

Jawaban Terbaik. Kompatibel sepenuhnya POSIX, tidak seperti beberapa jawaban di atas, di atas, dan juga mendapatkan hadiah The Shortest Pipeline spesial :).
kkm

Saya akan senang melihat seseorang menunjukkan waktu ini vs yang lain di atas, karena saya merasa ini adalah yang tercepat.
dlamblin

4
@ kkm Saya setuju ini adalah solusi terbaik tetapi spesifikasi POSIXfind sebenarnya jarang - -printfoperator tidak ditentukan. Ini tidak berfungsi dengan BSD find. Jadi, tidak "sepenuhnya kompatibel POSIX." (Padahal sort -u dalam POSIX .)
Wildcard

8

Pada dasarnya ada 2 metode yang dapat Anda gunakan untuk melakukan ini. Satu akan mengurai string sedangkan yang lain akan beroperasi pada setiap file. Parsing string menggunakan alat seperti grep, sed, atau awkjelas akan lebih cepat tapi di sini adalah contoh yang menunjukkan keduanya, serta bagaimana Anda bisa "profil" 2 metode.

Contoh data

Untuk contoh di bawah ini kami akan menggunakan data berikut

$ 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}

Hapus beberapa *f*file dari dir1/*:

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

Approach # 1 - Parsing via string

Di sini kita akan menggunakan alat-alat berikut, find, grep, dan sort.

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

Approach # 2 - Parsing menggunakan file

Rantai alat yang sama seperti sebelumnya, kecuali kali ini kami akan menggunakan dirnamebukan grep.

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

CATATAN: Contoh di atas hanya digunakan head -5untuk membatasi jumlah output yang kita hadapi untuk contoh-contoh ini. Biasanya akan dihapus untuk mendapatkan daftar lengkap Anda!

Membandingkan hasilnya

Kita dapat menggunakan timeuntuk melihat 2 pendekatan.

dirname

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

grep

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

Jadi selalu yang terbaik untuk berurusan dengan string jika memungkinkan.

Metode penguraian string alternatif

grep & PCRE

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

sed

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

awk

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

+1 Karena berhasil, tetapi yang menarik ini membutuhkan waktu beberapa kali lebih lama daripada jawaban @ John1024
Muhd

@ Muhd ​​- ya panggilan ke dirname lambat. Saya sedang mengerjakan alternatif.
slm

2

Inilah yang menurut saya berguna:

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

1

Jawaban ini tanpa malu-malu didasarkan pada jawaban slm. Itu adalah pendekatan yang menarik, tetapi memiliki batasan jika nama file dan / atau direktori memiliki karakter khusus (spasi, semi-kolom ...). Kebiasaan yang baik adalah menggunakan find /somewhere -print0 | xargs -0 someprogam.

Contoh data

Untuk contoh di bawah ini kami akan menggunakan data berikut

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}

Hapus beberapa *f*file dari dir1/*/:

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

Pendekatan # 1 - Parsing menggunakan file

$ 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

CATATAN : Contoh di atas hanya digunakan head -5untuk membatasi jumlah output yang kita hadapi untuk contoh-contoh ini. Biasanya akan dihapus untuk mendapatkan daftar lengkap Anda! juga, ganti echoperintah apa pun yang ingin Anda gunakan.


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.