Найти каталоги, которые не содержат файл

58

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

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

Не беспокойтесь о загрузке обложки (это интересный проект для меня завтра). Меня волнует только великолепный пример с обратным findпримером.

Оли
источник
для тех, кто заинтересован в загрузке пропущенных обложек, просто установите launchpad.net/coverlovin и замените -print в ответе @phoibos на «-exec ./coverlovin.py {} \;»
Дрор Коэн

Ответы:

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

Для получения дополнительной информации об этом, проверьте Регулярные выражения .

Phoibos
источник
Абсолютно красиво - проблема в том, что нельзя выбирать между случаями или различными расширениями (я пробовал подстановочный знак, но не ходил). Интересно, есть ли лучшая альтернатива test?
Оли
1
Хм, вы можете вкладывать находку с этим, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;но это довольно грязно с точки зрения оптимизации. Это работает, хотя.
Оли
Я обнаружил, что вы можете передать testнагрузку -o EXPRESSIONдля запросов ИЛИ ... например: test -e "{}/cover.jpg" -o -e "{}/cover.png"это лучше, чем полный поиск, но он по-прежнему чувствителен к регистру.
Оли
Я должен отметить, что сравнивая производительность этого (два теста, в моем последнем комментарии) с двумя другими решениями (comm'd find и comm'd globbing), это намного медленнее (684ms против 40ms и 50ms соответственно)
Оли
Первоначальное решение в ответе занимает более секунды и прерывается в обстоятельствах, которые имеют $имя 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принимает только файлы, отсюда и странный <(...)метод ввода. Это передает содержимое через настоящий [временный] файл.

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

  • dirnameэто удобный инструмент для получения директории файла без обращения к sed(и др.)

  • 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

Покажет все каталоги, в которых нет текстовых файлов.

Роэль Ван де Паар
источник