Рекурсивные подстановочные знаки в GNU?
Прошло некоторое время с тех пор, как я использовал make
, так что несите меня...
У меня есть каталог, flac
, содержащий файлы .FLAC. У меня есть соответствующий каталог, mp3
содержащий файлы MP3. Если файл FLAC новее, чем соответствующий файл MP3 (или соответствующий файл MP3 не существует), я хочу запустить кучу команд для преобразования FLAC файла в MP3 файл и скопировать теги.
Кикер: мне нужно рекурсивно искать каталог flac
и создавать соответствующие подкаталоги в каталоге mp3
. Каталоги и файлы могут иметь пробелы в именах и называются в UTF-8.
И я хочу использовать make
для этого.
Ответы
Ответ 1
Я бы попробовал что-то в этом направлении
FLAC_FILES = $(shell find flac/ -type f -name '*.flac')
MP3_FILES = $(patsubst flac/%.flac, mp3/%.mp3, $(FLAC_FILES))
.PHONY: all
all: $(MP3_FILES)
mp3/%.mp3: flac/%.flac
@mkdir -p "$(@D)"
@echo convert "$<" to "[email protected]"
Несколько быстрых заметок для make
новичков:
-
@
перед командами предотвращает make
от печати команды до ее фактического запуска.
-
$(@D)
является частью каталога имени целевого файла ([email protected]
)
- Убедитесь, что строки с командами оболочки в них начинаются с вкладки, а не с пробелами.
Даже если это должно обрабатывать все символы UTF-8 и прочее, оно будет терпеть неудачу при пробелах в именах файлов или каталогов, поскольку make
использует пробелы для разделения файлов в make файлах, и я не знаю, как работать что. Так что оставляю вас только с оболочкой script, я боюсь: -/
Ответ 2
Вы можете определить свою собственную рекурсивную функцию подстановки следующим образом:
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
Первый параметр ($1
) - это список каталогов, а второй ($2
) - это список шаблонов, которые вы хотите сопоставить.
Примеры:
Чтобы найти все файлы C в текущем каталоге:
$(call rwildcard,.,*.c)
Чтобы найти все файлы .c
и .h
в src
:
$(call rwildcard,src,*.c *.h)
Эта функция основана на реализации из этой статьи с некоторыми улучшениями.
Ответ 3
FWIW, я использовал что-то вроде этого в Makefile:
RECURSIVE_MANIFEST = `find . -type f -print`
Приведенный выше пример будет искать из текущего каталога ('.') для всех "простых файлов" ('-type f') и устанавливать переменную RECURSIVE_MANIFEST
make в каждый найденный файл. Затем вы можете использовать замены шаблонов, чтобы уменьшить этот список, или, альтернативно, предоставить больше аргументов в поиске, чтобы сузить то, что оно возвращает. См. Справочную страницу для поиска.
Ответ 4
Мое решение основано на вышеизложенном, вместо patsubst
используется sed
, чтобы отменить вывод find
И избежать пробелов.
Переход от flac/to ogg/
OGGS = $(shell find flac -type f -name "*.flac" | sed 's/ /\\ /g;s/flac\//ogg\//;s/\.flac/\.ogg/' )
Предостережения:
- Все еще barfs, если в имени файла есть полуколоны, но они довольно редки.
- Трюк $(@D) не будет работать (выводит тарабарщину), но oggenc создает для вас каталоги!
Ответ 5
Здесь Python script Я быстро взломал вместе, чтобы решить исходную проблему: сохраните сжатую копию музыкальной библиотеки. script преобразует файлы .m4a (предполагается, что ALAC) в формат AAC, если файл AAC уже существует и является более новым, чем файл ALAC. Файлы MP3 в библиотеке будут связаны, так как они уже сжаты.
Просто остерегайтесь того, что прерывание script (ctrl-c) оставит за собой файл с половиной преобразованного файла.
Я изначально также хотел написать Makefile, чтобы справиться с этим, но поскольку он не может обрабатывать пробелы в именах файлов (см. принятый ответ), и потому, что запись bash script гарантированно введет меня в мир боли, Python это так. Он довольно простой и короткий, и поэтому его легко настроить в соответствии с вашими потребностями.
from __future__ import print_function
import glob
import os
import subprocess
UNCOMPRESSED_DIR = 'Music'
COMPRESSED = 'compressed_'
UNCOMPRESSED_EXTS = ('m4a', ) # files to convert to lossy format
LINK_EXTS = ('mp3', ) # files to link instead of convert
for root, dirs, files in os.walk(UNCOMPRESSED_DIR):
out_root = COMPRESSED + root
if not os.path.exists(out_root):
os.mkdir(out_root)
for file in files:
file_path = os.path.join(root, file)
file_root, ext = os.path.splitext(file_path)
if ext[1:] in LINK_EXTS:
if not os.path.exists(COMPRESSED + file_path):
print('Linking {}'.format(file_path))
link_source = os.path.relpath(file_path, out_root)
os.symlink(link_source, COMPRESSED + file_path)
continue
if ext[1:] not in UNCOMPRESSED_EXTS:
print('Skipping {}'.format(file_path))
continue
out_file_path = COMPRESSED + file_path
if (os.path.exists(out_file_path)
and os.path.getctime(out_file_path) > os.path.getctime(file_path)):
print('Up to date: {}'.format(file_path))
continue
print('Converting {}'.format(file_path))
subprocess.call(['ffmpeg', '-y', '-i', file_path,
'-c:a', 'libfdk_aac', '-vbr', '4',
out_file_path])
Конечно, это может быть усилено для параллельного выполнения кодирования. Это остается как упражнение для читателя; -)
Ответ 6
Если вы используете Bash 4.x, вы можете использовать новую функцию globbing, например:
SHELL:=/bin/bash -O globstar
list:
@echo Flac: $(shell ls flac/**/*.flac)
@echo MP3: $(shell ls mp3/**/*.mp3)
Этот рекурсивный подстановочный шаблон может найти все интересующие вас файлы (.flac,.mp3 или что-то еще). О