Temukan direktori yang TIDAK mengandung file

58

Ya, saya sedang memilah musik saya. Saya telah mengatur segalanya dengan indah dalam mantra berikut: /Artist/Album/Track - Artist - Title.extdan jika ada, sampulnya ada /Artist/Album/cover.(jpg|png).

Saya ingin memindai semua direktori tingkat kedua dan menemukan yang tidak memiliki penutup. Pada level kedua, maksud saya saya tidak peduli jika /Britney Spears/tidak memiliki cover.jpg, tetapi saya akan peduli jika /Britney Spears/In The Zone/tidak memilikinya.

Jangan khawatir tentang pengunduhan sampulnya (itu adalah proyek yang menyenangkan bagi saya besok). Saya hanya peduli tentang kemalasan bash yang mulia tentang contoh terbalik find.

Oli
sumber
untuk siapa saja yang tertarik mengunduh sampul yang hilang cukup pasang launchpad.net/coverlovin dan ganti -print dalam jawaban @phoibos dengan "-exec ./coverlovin.py {} \;"
Dror Cohen

Jawaban:

81

Kasus 1: Anda tahu nama file yang tepat untuk dicari

Gunakan finddengan test -e your_fileuntuk memeriksa apakah ada file. Misalnya, Anda mencari direktori yang tidak ada cover.jpgdi dalamnya:

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec test -e "{}/cover.jpg" ';' -print

Ini case sensitif sekalipun.

Kasus 2: Anda ingin lebih fleksibel

Anda tidak yakin dengan kasusnya, dan ekstensi mungkin jPg, png...

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec sh -c 'ls -1 "{}"|egrep -i -q "^cover\.(jpg|png)$"' ';' -print

Penjelasan:

  • Anda perlu menelurkan shell shuntuk setiap direktori karena pemipaan tidak memungkinkan saat menggunakanfind
  • ls -1 "{}"keluaran hanya nama file direktori findsaat ini melintasi
  • egrep(bukan grep) menggunakan ekspresi reguler yang diperluas; -imembuat case pencarian tidak sensitif, -qmembuatnya menghilangkan output apa pun
  • "^cover\.(jpg|png)$"adalah pola pencarian. Dalam contoh ini, ini cocok dengan misalnya cOver.png, Cover.JPGatau cover.png. The .harus melarikan diri jika tidak berarti bahwa itu cocok setiap karakter. ^menandai awal baris, $akhirnya

Contoh pola pencarian lain untuk egrep :

Gantikan egrep -i -q "^cover\.(jpg|png)$"bagian dengan:

  • egrep -i -q "cover\.(jpg|png)$": Juga cocok cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": Cocok cover.png,, cover.jpgtapi TIDAK Cover.jpg(sensitivitas huruf tidak dimatikan)
  • egrep -iq "^(cover|front)\.jpg$": cocok misalnya front.jpg, Cover.JPGtetapi tidak Cover.PNG

Untuk info lebih lanjut tentang ini, lihat Ekspresi Reguler .

phoibos
sumber
Benar-benar cantik - dengan masalah yang tidak fleksibel untuk memilih di antara case atau ekstensi yang berbeda (saya mencoba wildcard tapi tidak ada jalan). Saya ingin tahu apakah ada alternatif yang lebih baik test.
Oli
1
Hmm Anda dapat membuat sarang dengan ini -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;tapi itu cukup kotor dalam hal optimasi. Tapi itu berhasil.
Oli
Saya menemukan bahwa Anda dapat melewatkan testbanyak -o EXPRESSIONpermintaan ATAU ... mis.: test -e "{}/cover.jpg" -o -e "{}/cover.png"Yang lebih baik daripada melakukan pencarian penuh tetapi itu tetap peka.
Oli
Saya harus mencatat bahwa membandingkan kinerja ini (dua tes, per komentar terakhir saya) terhadap dua solusi lainnya (komisi menemukan dan melakukan globbing) sejauh ini adalah yang paling lambat (masing-masing 684ms vs 40ms dan 50ms)
Oli
Solusi in-answer yang asli membutuhkan waktu satu detik dan rusak dalam keadaan yang ada $dalam nama dir (Ke $ ha, misalnya).
Oli
12

Sederhana, itu terjadi. Yang berikut mendapat daftar direktori dengan sampul dan membandingkannya dengan daftar semua direktori tingkat kedua. Baris yang muncul di kedua "file" ditekan, meninggalkan daftar direktori yang perlu ditutupi.

comm -3 \
    <(find ~/Music/ -iname 'cover.*' -printf '%h\n' | sort -u) \
    <(find ~/Music/ -maxdepth 2 -mindepth 2 -type d | sort) \
| sed 's/^.*Music\///'

Hore.

Catatan:

  • commArgumennya adalah sebagai berikut:

    • -1 menekan baris unik ke file1
    • -2 menekan baris unik ke file2
    • -3 menekan garis yang muncul di kedua file
  • commhanya mengambil file, maka <(...)metode input kooky . Ini mem-pipe konten melalui file [temporer] sungguhan.

  • commmembutuhkan input yang diurutkan atau tidak berfungsi dan findtidak menjamin pesanan. Itu juga harus unik. findOperasi pertama dapat menemukan beberapa file cover.*sehingga ada entri duplikat. sort -udengan cepat mengacaknya menjadi satu. Temuan kedua selalu akan menjadi unik.

  • dirnameadalah alat yang berguna untuk mendapatkan dir file tanpa menggunakan sed(et al).

  • finddan commkeduanya agak berantakan dengan output mereka. Final sedada di sana untuk membersihkan semuanya sehingga Anda pergi dengan Artist/Album. Ini mungkin atau mungkin tidak diinginkan untuk Anda.

Oli
sumber
2
pertama Anda findmungkin dapat disederhanakan find ~/Music/ -iname 'cover.*' -printf '%h\n', menghindari kebutuhan dirname. meskipun dirnameberguna di tempat lain.
Tom
Terima kasih @ Tom, itu jauh lebih cepat dari yang ada di mana-mana (29ms vs 734ms di dir musik saya - keduanya "hangat" ditemukan)
Oli
9

Ini jauh lebih baik untuk diselesaikan dengan globbing daripada dengan find.

$ cd ... # to the directory one level above the album/artist structure

$ echo */*/*.cover   # lists all the covers

$ printf "%s\n" */*/*.cover # lists all the covers, one per line

Sekarang anggaplah Anda tidak memiliki file liar dalam struktur yang bagus ini. Direktori saat ini hanya berisi subdirektori artis, dan direktori tersebut hanya berisi subdirektori album. Maka kita dapat melakukan sesuatu seperti ini:

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)

The <(...)sintaks proses substitusi Bash: itu memungkinkan Anda menggunakan perintah di tempat argumen berkas. Ini memungkinkan Anda memperlakukan output dari suatu perintah sebagai file. Jadi kita bisa menjalankan dua program, dan mengambil diff mereka, tanpa menyimpan output mereka dalam file sementara. The diffProgram berpikir itu bekerja dengan dua file, tapi sebenarnya itu membaca dari dua pipa.

Perintah yang menghasilkan masukan tangan kanan untuk diff, printf "%s\n" */*, hanya berisi daftar direktori album. Perintah tangan kiri beralih melalui *.coverjalur dan mencetak nama direktori mereka.

Uji coba:

$ find .   # let's see what we have here
.
./a
./a/b
./foo
./foo/bar
./foo/baz
./foo/baz/cover.jpg

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)
0a1,2
> a/b
> foo/bar

Aha, a/bdan foo/bardirektori tidak punya cover.jpg.

Ada beberapa kasus sudut yang rusak, seperti itu secara default *mengembang sendiri jika tidak cocok. Ini bisa diatasi dengan Bash set -o nullglob.

Segera
sumber
Permintaan maaf atas jawaban yang terlambat. Itu ide yang menarik tetapi: sampul bisa di png dan jpb dan, tidak commakan lebih bersih dari itu diff?
Oli
comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)tampaknya seperti kompromi yang masuk akal tanpa diffbulu. Namun, ini sedikit lebih lambat dari penemuan ganda saya.
Oli
0
ls --color=never */*.txt | sed 's|/.*||' | sort -u -n > withtxt.txt
ls --color=never -d * | sort -u -n > all.txt
diff all.txt withtxt.txt

Akan menampilkan semua direktori yang tidak memiliki file txt di dalamnya.

Roel Van de Paar
sumber