Знайдіть каталоги, які НЕ містять файл


58

Так, я сортую свою музику. У мене все гарно впорядковано в наступній мантрі: /Artist/Album/Track - Artist - Title.extі якщо така існує, обкладинка сидить /Artist/Album/cover.(jpg|png).

Я хочу переглянути всі каталоги другого рівня та знайти ті, у яких немає обкладинки. Під другим рівнем я маю на увазі, що мені байдуже, чи /Britney Spears/не має cover.jpg, але мені було б байдуже, якби /Britney Spears/In The Zone/його не було.

Не хвилюйтеся з приводу завантаження обкладинки (це завтра цікавий проект для мене завтра) Мене хвилює лише славна божевільність щодо зворотного findприкладу.


для всіх, хто зацікавлений у завантаженні обкладинок, яких не вистачає, просто встановіть startpad.net/coverlovin та замініть -print у відповіді @phoibos на "-exec ./coverlovin.py {} \;"
Dror Cohen

Відповіді:


81

Випадок 1: Ви знаєте точне ім'я файлу, яке потрібно шукати

Використовуйте findдля, test -e your_fileщоб перевірити, чи існує файл. Наприклад, ви шукаєте каталоги, яких немає cover.jpgв них:

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

Однак це чутливе до регістру.

Випадок 2: Ви хочете бути більш гнучкими

Ви не впевнені у справі, і розширення може бути jPg, png...

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

Пояснення:

  • Вам потрібно покласти оболонку shдля кожного каталогу, оскільки трубопровід неможливий при використанніfind
  • ls -1 "{}"Виводить лише імена файлів в findданий час
  • egrep(замість grep) використовує розширені регулярні вирази; -iробить випадок пошуку нечутливим, -qзмушує його опускати будь-який результат
  • "^cover\.(jpg|png)$"- це схема пошуку. У цьому прикладі він відповідає, наприклад cOver.png, Cover.JPGабо cover.png. .Повинно бути екрановано інакше це означає , що він відповідає будь-якому символу. ^позначає початок рядка, $його кінець

Інші приклади пошуку для egrep :

Замініть egrep -i -q "^cover\.(jpg|png)$"деталь на:

  • egrep -i -q "cover\.(jpg|png)$": Також матчі cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": Матчі cover.png, cover.jpgале НЕ Cover.jpg(чутливість регістру не вимкнено)
  • egrep -iq "^(cover|front)\.jpg$": відповідає, наприклад front.jpg, Cover.JPGале ні Cover.PNG

Для отримання додаткової інформації про це, ознайомтеся з Регулярні вирази .


Абсолютно красиво - з проблемою, що не дуже гнучко вибирати між корпусами чи різними розширеннями (я спробував підстановку, але не ходити). Цікаво, чи є краща альтернатива test.
Олі

1
Хм, ви можете гніздо знайти це, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;але це досить брудно з точки зору оптимізації. Це все-таки працює.
Олі

Я виявив, що ви можете передати testнавантаження -o EXPRESSIONна запити АБО ... наприклад: test -e "{}/cover.jpg" -o -e "{}/cover.png"що краще, ніж виконувати пошук у повному обсязі, але це все ще залежить від регістру
Олі

Я мушу зазначити, що порівнюючи ефективність цього (два тести, за моїм останнім коментарем) порівняно з іншими двома рішеннями (Comm'd знайшов і comm'd глобулінг), це далеко не найповільніше (684 мс проти 40 мс і 50 мс відповідно)
Олі

Оригінальне рішення у відповідь займає секунду і розривається в обставинах, що є $в імені dir (наприклад, Ke $ ha).
Олі

12

Просто, це виявляється. Далі наведено список каталогів із обкладинкою та порівнює цей список зі списком усіх каталогів другого рівня. Рядки, які відображаються в обох "файлах", придушуються, залишаючи список каталогів, які потребують обкладинок.

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

Ура.

Примітки:

  • commАргументи наступні:

    • -1 придушити рядки, унікальні для file1
    • -2 придушити рядки, унікальні для file2
    • -3 придушити рядки, що з’являються в обох файлах
  • commбере лише файли, отже, <(...)метод введення kooky . Це передає вміст через реальний [тимчасовий] файл.

  • commпотрібен відсортований ввід, або він не працює і аж findніяк не гарантує замовлення. Він також повинен бути унікальним. Перша findоперація могла знайти декілька файлів для того, cover.*щоб не було дублікатів. sort -uшвидко обробляє тих, хто до одного. Друга знахідка завжди буде унікальною.

  • dirnameє зручним інструментом для отримання файлу dir, не вдаючись до sed(et al).

  • findі commвони дещо безладні зі своїм результатом. Фінал sedє, щоб прибрати речі, щоб ви залишилися Artist/Album. Це може бути або не бути бажаним для вас.


2
ваш перший, findможливо, може бути спрощений find ~/Music/ -iname 'cover.*' -printf '%h\n', уникаючи потреби в цьому dirname. хоча dirnameце зручно в інших місцях.
Том

Дякую @Tom, це набагато швидше, що розвіваються скрізь (29мс проти 734мс на моєму музичному реєстрі - обидва «теплі» знахідки)
Олі

9

Це набагато приємніше вирішити з глобусом, ніж із знахідкою.

$ 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

Тепер припустимо, у вас немає жодних файлів у цій гарній структурі. Поточний каталог містить лише підкаталоги виконавців, а такі - лише підкаталоги альбомів. Тоді ми можемо зробити щось подібне:

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

<(...)Синтаксис Bash підміна процесу: він дозволяє використовувати команду замість файлу аргумент. Це дозволяє розглядати результат команди як файл. Таким чином, ми можемо запустити дві програми та приймати їх різниці, не зберігаючи їх вихід у тимчасових файлах. diffПрограма вважає , що вона працює з двома файлами, але насправді це читання з двох труб.

Команда , яка виробляє правильний вхід руки в diff, printf "%s\n" */*просто перераховує альбом каталоги. Команда ліворуч повторює *.coverконтури та друкує їхні назви каталогів.

Пробіг:

$ 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

Ага, a/bа foo/barкаталогів немає cover.jpg.

Є кілька розбитих кутових випадків, наприклад, за замовчуванням *розширюється до себе, якщо нічого не відповідає. Це можна вирішити за допомогою Баша set -o nullglob.


Вибачення за пізню відповідь. Цікава ідея, але: обкладинки можуть бути в png та jpb і, чи не commбуде чистішими diff?
Олі

comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)видається розумним компромісом без жодного diffпуху. Це, однак, трохи повільніше, ніж моя подвійна знахідка.
Олі

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

Показуватимуть усі каталоги, у яких немає файлів txt.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.