У меня есть этот скрипт для 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
но мне нужны два улучшения:
Что я могу изменить, чтобы он вывел список файлов в папках, содержащих 2 файла, 3 файла и т.д., было бы еще лучше, если бы это можно было передать в качестве параметра
В настоящее время он находит папки, содержащие только один файл, и этот файл должен быть аудиофайлом. Но на самом деле я хочу, чтобы он находил папки, содержащие только один аудиофайл, т. е. если папка содержит три файла, но только один из них является аудиофайлом, я хочу, чтобы этот аудиофайл был указан в списке.
спасибо Пол
решение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" {} \;
Итак, в вашем сценарии было несколько ошибок:
- Вы использовали
mindepth
вместоmaxdepth
. - Точки (.) в вашем egrep соответствовали бы любому символу. Так что
.wma
соответствовали бы 'Snowman.txt'. - Вам не нужно было выполнять второй тест для типа «d», поскольку в команду оболочки передаются только каталоги.
Заметки по моему сценарию:
- Использование:
findodd.sh <top_folder> <no_of_files>
- Кавычки имеют решающее значение. Определение
COMMAND
на самом деле представляет собой 2 строковых литерала по обе стороны от$2
. Это действительно важно. - Он только перечисляет папки, содержащие файлы, а не сами файлы. Чтобы сделать последнее, вам придется заменить
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