Как найти папки, содержащие x файлов определенного типа, и вывести эти пути на OSX

Как найти папки, содержащие x файлов определенного типа, и вывести эти пути на OSX

У меня есть этот скрипт для OSX для поиска папок, которые содержат только один файл, и если этот файл является аудиофайлом, вывода пути к аудиофайлу

find "$1" -type d -exec sh -c '[[ $(find "$0" -mindepth 1 | wc -l) -eq 1 ]] 
&& [[ $(find "$0" -mindepth 1 -type d | wc -l) -eq 0 ]]  
&& find "$0"' {} \; |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"

т.е. использовать как

./findodd.sh /Users/paul/Music

но мне нужны два улучшения:

  1. Что я могу изменить, чтобы он вывел список файлов в папках, содержащих 2 файла, 3 файла и т.д., было бы еще лучше, если бы это можно было передать в качестве параметра

  2. В настоящее время он находит папки, содержащие только один файл, и этот файл должен быть аудиофайлом. Но на самом деле я хочу, чтобы он находил папки, содержащие только один аудиофайл, т. е. если папка содержит три файла, но только один из них является аудиофайлом, я хочу, чтобы этот аудиофайл был указан в списке.

спасибо Пол

решение1

$ find
.
./folder3
./folder3/quux.txt
./folder1
./folder1/test.mp3
./folder1/test.txt
./folder1/test.wma
./folder2
./folder2/bar.txt
./folder2/foo.txt
./folder2/test.ogg

Примеры запусков:

$ ./findaudio.sh /tmp/findaudio 1
/tmp/findaudio/folder2/test.ogg

$ ./findaudio.sh /tmp/findaudio 2
/tmp/findaudio/folder1/test.mp3
/tmp/findaudio/folder1/test.wma

# The first parameter defaults to the current directory and
# the second parameter defaults to 1 so this works as well:
$ ./findaudio.sh
./folder2/test.ogg

А вот и код:

#!/bin/bash

shopt -s nullglob

find "${1:-.}" -type d | while read dir; do
        files=( "${dir}"/*.{mp4,mp3,ogg,flac,wma,m4a} )
        IFS=$'\n'
        (( ${#files[@]} == ${2:-1} )) && echo "${files[*]}"
done

Он перебирает все подкаталоги указанного каталога и использует подстановку для чтения всех имен аудиофайлов текущего подкаталога в массив files. Если размер массива соответствует желаемому значению, он просто выводит имена файлов, разделенные новой строкой.

EDIT: Это мой более ранний подход, основанный на предположении, что вы хотели распечатать папки, а не имена файлов. Я оставлю это здесь для дальнейшего использования.

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -u
./folder2

Это находит все файлы с перечисленными расширениями аудио и печатает только компоненты их каталогов вместо полного пути. Это дает вам список родительских папок для всех аудиофайлов. Пропускает uniqнеуникальные строки, что должно дать вам результат, который вы ищете, то есть печатает только папки, которые содержат ровно один аудиофайл.

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

Вы можете улучшить это, чтобы удовлетворить ваш первый пункт, подсчитав дублирующиеся строки и распечатав только те папки, которые соответствуют вашему запрошенному количеству. Наивное решение было бы:

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=1 '$1==count'
1 ./folder2

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=2 '$1==count'
2 ./folder1

хотя, возможно, было бы лучше объединить uniqправую часть и правую сторону трубы в одну awkлинию.

решение2

ВТОРАЯ ПОПЫТКА

Хорошо, после того, как я сам попробовал это сделать в своей папке «Музыка», вот решение для обоих ваших запросов:

COMMAND='[[ $(find "$0" -maxdepth 2 |egrep "\.mp4|\.mp3|\.ogg|\.flac|\.wma|\.m4a"| wc -l) == '$2' ]] && echo "$0"'
find $1 -type d -exec sh -c "$COMMAND" {} \;

Итак, в вашем сценарии было несколько ошибок:

  1. Вы использовали mindepthвместо maxdepth.
  2. Точки (.) в вашем egrep соответствовали бы любому символу. Так что .wmaсоответствовали бы 'Snowman.txt'.
  3. Вам не нужно было выполнять второй тест для типа «d», поскольку в команду оболочки передаются только каталоги.

Заметки по моему сценарию:

  1. Использование:findodd.sh <top_folder> <no_of_files>
  2. Кавычки имеют решающее значение. Определение COMMANDна самом деле представляет собой 2 строковых литерала по обе стороны от $2. Это действительно важно.
  3. Он только перечисляет папки, содержащие файлы, а не сами файлы. Чтобы сделать последнее, вам придется заменить echo "$0"на другой find.

Сейчас я тестирую на машине с Arch Linux, и моя оболочка — «bash», поэтому я понятия не имею, будет ли это работать на OSX, поскольку не все оболочки созданы равными. :-)


РАНЬШЕ ПЕРВАЯ ПОПЫТКА:

Хм-м-м. Я не знаю, насколько OSX похож на Unix/Linux, но я попробую.

Ответ на оба ваших вопроса, я полагаю, кроется в первом тесте команды 'sh -c'. Это часть, которая гласит:

$(find "$0" -mindepth 1 | wc -l) -eq 1

Чтобы передать в скрипт второй параметр для количества файлов, вам нужно просто изменить «1» на $2, тогда тест будет выглядеть так:

$(find "$0" -mindepth 1 | wc -l) -eq $2

Не заключайте кавычки $2, иначе это будет интерпретировано как второй параметр, переданный команде «sh -c», а не ваш скрипт.

Тогда командная строка будет выглядеть так:

./findodd.sh /Users/paul/Music 2

Чтобы выполнить ваше второе требование, насколько я понимаю, вам нужно поместить команду egrepв этот первый тест, например:

$(find "$0" -mindepth 1 |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"| wc -l) -eq $2

Хотя, возможно, вам придется следить за цитатами.

В любом случае, попробуйте и дайте нам знать.

решение3

Вы можете реализовать это на Python, выполнив что-то вроде этого:

#!/usr/bin/env python

import fnmatch
import os
import sys

if len(sys.argv) != 3 or \
        not sys.argv[1].isdigit() or \
        not os.path.exists(sys.argv[2]):
    print "Usage: %s [number of files] [search root]" % sys.argv[0]
    sys.exit(1)

num_files = int(sys.argv[1])
search_root = sys.argv[2]

# this must be a tuple to work with endswith()
audio_extensions = (
    'mp4',
    'mp3',
    'ogg',
    'flac',
    'wma',
    'm4a',
)

for dirpath, dirnames, filenames in os.walk(search_root):
    audio_files = [f for f in filenames if f.endswith(audio_extensions)]
    if len(audio_files) == num_files:
        print "\n".join([os.path.join(dirpath, f) for f in audio_files])

Если вы chmod +x findodd.pyможете, то запустите его так же, как вы запускаете свой текущий скрипт, например:

./findodd.py 1 /Users/paul/Music

Связанный контент