Рекурсивні узагальнюючі символи в GNU роблять?


92

Минув деякий час з того часу, як я використовував make, так що терпіть зі мною

У мене є каталог flacіз файлами .LAC. У мене є відповідний каталог, mp3що містить файли MP3. Якщо файл FLAC новіший за відповідний файл MP3 (або відповідний файл MP3 не існує), я хочу виконати купу команд для перетворення файлу FLAC у файл MP3 та скопіювати теги.

Кікер: Мені потрібно flacрекурсивно шукати каталог і створити відповідні підкаталоги в mp3каталозі. Каталоги та файли можуть мати пробіли в іменах і називатися в UTF-8.

І я хочу скористатися makeцим.


1
Будь-яка причина для вибору марки для цієї мети? Я б думав, що написання сценарію bash було б простішим

4
@Neil, концепція make як трансформація файлової системи на основі шаблону - найкращий спосіб вирішити вихідну проблему. Можливо, реалізації цього підходу мають свої обмеження, але makeближче до його реалізації, ніж просто bash.
П Швед

1
@Pavel Ну, shсценарій, який проходить по списку файлів flac ( find | while read flacname), робить mp3nameз цього, запускає "mkdir -p" на dirname "$mp3name", а потім if [ "$flacfile" -nt "$mp3file"]перетворює "$flacname"на "$mp3name"насправді не магія. Єдина особливість, яку ви насправді втрачаєте в порівнянні з makeбазовим рішенням, - це можливість Nпаралельно запускати процеси перетворення файлів make -jN.
ndim

4
@ndim Це перший раз, коли я коли-небудь чув, що синтаксис марки описується як "приємний" :-)

1
Використання makeта наявність пробілів в іменах файлів суперечливі вимоги. Використовуйте інструмент, відповідний проблемному домену.
Йенс,

Відповіді:


117

Я б спробував щось подібне

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початківців:

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

Навіть якщо це має обробляти всі символи UTF-8 та інше, це не спрацює з пробілами в іменах файлів або каталогів, оскільки makeвикористовує пробіли для розділення речей у файлах make, і я не знаю способу обійти це. Отже, у вас залишається лише сценарій оболонки, я боюся: - /


Саме сюди я йшов ... швидкими пальцями для перемоги. Хоча, схоже, ви можете зробити щось розумне vpath. Потрібно вивчити це на днях.
dmckee --- кошеня екс-модератора

1
Здається, не працює, коли каталоги мають пробіли в іменах.
Роджер Ліпскомб

Не розумів, що мені доведеться розібратися, щоб findотримувати імена рекурсивно ...
Роджер Ліпскомб

1
@PaulKonova: Біжи make -jN. Для Nвикористання кількість перетворень, які makeповинні виконуватися паралельно. Застереження. Запуск make -jбез використання Nзапустить усі процеси перетворення одночасно паралельно, що може бути еквівалентно вилочній бомбі.
ndim

1
@Adrian: .PHONY: allРядок повідомляє make, що рецепт allцілі повинен бути виконаний, навіть якщо є файл, який називається allновішим за всі $(MP3_FILES).
ndim

52

Ви можете визначити власну рекурсивну функцію підстановки таким чином:

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)

Ця функція базується на реалізації з цієї статті , з деякими вдосконаленнями.


Здається, це не працює для мене. Я скопіював точну функцію, і вона все одно не буде виглядати рекурсивно.
Jeroen

3
Я використовую GNU Make 3.81, і, здається, це працює для мене. Однак це не буде працювати, якщо в будь-якому з імен файлів є пробіли. Зверніть увагу, що імена файлів, які він повертає, мають шляхи щодо поточного каталогу, навіть якщо ви перелічуєте файли лише у підкаталозі.
larskholte

2
Це справді приклад make- це смола Тьюрінга (див. Тут: yosefk.com/blog/fun-at-the-turing-tar-pit.html ). Це навіть не так складно, але треба прочитати це: gnu.org/software/make/manual/html_node/Call-Function.html, а потім "зрозуміти повторення". ВИ мусили писати це рекурсивно, в дослівному розумінні; це не повсякденне розуміння "автоматично включати речі з піддиректорій". Це фактичне ПОВТОРЕННЯ. Але пам’ятайте - «Щоб зрозуміти рецидив, ви повинні розуміти рецидив».
Томаш Гандор,

@TomaszGandor Вам не потрібно розуміти повторення. Ви повинні розуміти рекурсію, і для цього потрібно спочатку зрозуміти рекурсію.
user1129682

Мій поганий, я впав на мовного фальшивого друга. Коментарі не можна редагувати через стільки часу, але я сподіваюся, що всі зрозуміли думку. І вони також розуміють рекурсію.
Томаш

2

FWIW, я використав щось подібне у Makefile :

RECURSIVE_MANIFEST = `find . -type f -print`

У наведеному вище прикладі буде здійснено пошук у поточному каталозі ( '.' ) Усіх "простих файлів" ( '-типу f' ) і встановлення RECURSIVE_MANIFESTзмінної make для кожного знайденого файлу. Потім ви можете використовувати заміни шаблонів, щоб зменшити цей список, або, як варіант, надати більше аргументів для пошуку, щоб звузити те, що він повертає. Дивіться сторінку керівництва для пошуку .


1

Моє рішення базується на наведеному вище, використовує 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 створює для вас каталоги!

1

Ось сценарій 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])

Звичайно, це може бути посилено для паралельного виконання кодування. Це залишається як вправа для читача ;-)


0

Якщо ви використовуєте Bash 4.x, ви можете використовувати нову опцію глобалізації , наприклад:

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

Цей тип рекурсивного підстановочного символу може знайти всі файли, що вас цікавлять ( .flac , .mp3 або що завгодно). О


1
Мені здається, працює навіть $ (wildcard flac / ** / *. Flac). OS X, Gnu Make 3.81
akauppi

2
Я спробував $ (wildcard ./**/*.py), і він поводився так само, як $ (wildcard ./*/*.py). Я не думаю, що make насправді підтримує **, і це просто не виходить з ладу, коли ви використовуєте два * s поруч один з одним.
lahwran

@lahwran Це має статися, коли ви викликаєте команди через оболонку Bash і ви ввімкнули опцію globstar . Можливо, ви не використовуєте GNU make або щось інше. Ви можете замість цього спробувати цей синтаксис . Перегляньте коментарі, щоб отримати кілька пропозицій. Інакше це питання для нового питання.
kenorb

@kenorb ні, ні, я навіть не пробував вашу справу, бо хотів уникнути виклику оболонки саме для цієї речі. Я використовував запропоновану акауппі річ. Те, з чим я пішов, виглядало як відповідь larskholte, хоча я отримав це звідкись ще, бо в коментарях тут говорилося, що цей тонко порушений.
знизати

@lahwran У цьому випадку **не буде працювати, оскільки розширене глобування - це річ bash / zsh.
kenorb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.