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

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

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

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

Случай 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 "{}" выводит только имена файлов каталога, в настоящее время просматривается
  • egrep (вместо grep ) использует расширенные регулярные выражения; -i делает регистр поиска нечувствительным, -q делает его недействительным
  • "^cover\.(jpg|png)$" – шаблон поиска. В этом примере он соответствует, например, cOver.png , Cover.JPG или cover.png . . должен быть экранирован, иначе это означает, что он соответствует любому символу. ^ обозначает начало строки, $ end

Другие примеры шаблонов поиска для 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 , но NOT Cover.jpg (чувствительность к регистру не отключена)
  • egrep -iq "^(cover|front)\.jpg$" : соответствует, например, Cover.JPG , Cover.JPG но не Cover.PNG

Дополнительные сведения об этом см. В разделе Регулярные выражения .

Простой, это выясняется. Следующее получает список каталогов с обложкой и сравнивает это со списком всех каталогов второго уровня. Строки, которые отображаются в обоих файлах, подавляются, оставляя список каталогов, которые нуждаются в обложках.

 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 подавляет линии, уникальные для файла2
    • -3 подавляет линии, которые появляются в обоих файлах
  • comm принимает только файлы, следовательно, метод ввода kooky <(...) . Это передает содержимое через настоящий [временный] файл.

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

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

  • find и comm являются немного беспорядочными с их выходом. Окончательный sed должен очистить вещи, поэтому вы остаетесь с Artist/Album . Это может быть или не быть желательным для вас.

Это гораздо приятнее решить с помощью globbing, чем с помощью 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 

Теперь предположим, что у вас нет бродячих файлов в этой красивой структуре. Текущий каталог содержит только подкаталоги исполнителя, и они содержат только подкаталоги альбома. Тогда мы можем сделать что-то вроде этого:

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

Синтаксис <(...) – это замена Bash-процесса: он позволяет вам использовать команду вместо аргумента файла. Он позволяет обрабатывать вывод команды в виде файла. Таким образом, мы можем запускать две программы и использовать их diff, не сохраняя их вывод во временных файлах. Программа diff считает, что она работает с двумя файлами, но на самом деле это чтение из двух труб.

Команда, которая производит ввод правой руки для diff , printf "%s\n" */* , просто перечисляет каталоги альбомов. Левая команда выполняет *.cover пути *.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 Bash set -o nullglob .