Рекурсивные подстановочные знаки в GNU make?


это было время, так как я использовал make, Так что потерпите меня...

у меня есть каталог, flac, содержащий .FLAC файлы. У меня есть соответствующий каталог, mp3 содержит MP3 файлы. Если файл FLAC является более новым, чем соответствующий MP3-файл (или соответствующий MP3-файл не существует), то я хочу запустить кучу команд для преобразования файла FLAC в MP3-файл и скопировать теги.

Кикер: мне нужно искать

6 70

6 ответов:

Я бы попробовал что-то в этом роде

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 "$@"

несколько быстрых заметок для make новичков:

  • The @ перед командами предотвращает make от печати команды до ее фактического выполнения.
  • $(@D) является частью каталога имени целевого файла ($@)
  • убедитесь, что строки с командами оболочки в них начинаются с вкладки, а не с пробелов.

даже если это должен обрабатывать все символы UTF-8 и прочее, он не будет работать в пробелах в именах файлов или каталогов, как make использует пробелы для разделения материала в makefiles, и я не знаю, как это обойти. Так что у вас остается только сценарий оболочки, я боюсь : -/

вы можете определить свою собственную рекурсивную подстановочную функцию следующим образом:

rwildcard=$(foreach d,$(wildcard *),$(call rwildcard,$d/,) $(filter $(subst *,%,),$d))

первый параметр () - это имя каталога, а второе () - это шаблон, который вы хотите, чтобы соответствовать.

примеры

чтобы найти все файлы C в текущем каталоге:

$(call rwildcard, , *.c)

найти все .c и .h файлы src:

$(call rwildcard, src/, *.c *.h)

источник

FWIW, я использовал что-то вроде этого в Makefile:

RECURSIVE_MANIFEST = `find . -type f -print`

приведенный выше пример будет искать в текущем каталоге ('.- ) для всех "текстовые файлы" ( '- тип f') и RECURSIVE_MANIFEST сделать переменную для каждого файла, который он находит. Затем вы можете использовать замены шаблонов, чтобы уменьшить этот список, или, альтернативно, предоставить больше аргументов в найти чтобы сузить то, что он возвращает. Смотрите man-страницу для найти.

мое решение основано на одном выше, использует sed вместо patsubst калечить выход find и избежать пробелов.

переход от flac / к ogg/

OGGS = $(shell find flac -type f -name "*.flac" | sed 's/ /\ /g;s/flac\//ogg\//;s/\.flac/\.ogg/' )

предостережения:

  1. все еще блевотина, если в имени файла есть точки с запятой, но они довольно редки.
  2. трюк $(@D) не будет работать (выводит тарабарщину), но oggenc создает каталоги для вас!

вот скрипт Python, который я быстро взломал, чтобы решить исходную проблему: сохранить сжатую копию музыкальной библиотеки. Скрипт будет конвертировать .файлы m4a (предположительно ALAC) в формат AAC, если файл AAC уже существует и является более новым, чем файл ALAC. MP3 файлы в библиотеке будут связаны, так как они уже сжаты.

просто будьте осторожны, что прерывание сценария (ctrl-c) оставит после себя наполовину преобразованный файл.

Я изначально также хотел написать Makefile для обработки этого, но поскольку он не может обрабатывать пробелы в именах файлов (см. принятый ответ) и потому, что написание скрипта bash гарантированно помещает меня в мир боли, 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])

конечно, это может быть улучшено для выполнения кодирования параллельно. Это оставлено в качестве упражнения для читателя ; -)

Если вы используете Bash 4.X, вы можете использовать новый подстановка параметра, например:

SHELL:=/bin/bash -O globstar
list:
  @echo Flac: $(shell ls flac/**/*.flac)
  @echo MP3: $(shell ls mp3/**/*.mp3)

такой рекурсивный подстановочный знак может найти все интересующие вас файлы (.Флак, .mp3 или что-то еще). O