Рекурсивные подстановочные знаки в 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 или что-то еще). О