GNU make 中的递归通配符?

问题描述 投票:0回答:7

我已经有一段时间没有使用

make
了,所以请耐心等待...

我有一个目录,

flac
,其中包含 .FLAC 文件。我有一个相应的目录,
mp3
,包含 MP3 文件。如果 FLAC 文件比相应的 MP3 文件新(或者相应的 MP3 文件不存在),那么我想运行一堆命令将 FLAC 文件转换为 MP3 文件,并复制标签。

更关键的是:我需要递归搜索

flac
目录,并在
mp3
目录中创建相应的子目录。目录和文件的名称中可以有空格,并以 UTF-8 命名。

我想用

make
来驱动它。

makefile gnu-make
7个回答
139
投票

我会尝试一些类似的事情

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)
    是目标文件名的目录部分 (
    $@
    )
  • 确保其中包含 shell 命令的行以制表符开头,而不是空格。

即使这应该处理所有 UTF-8 字符和内容,它也会在文件或目录名中的空格处失败,因为

make
使用空格来分隔 makefile 中的内容,我不知道有什么方法可以解决这个问题。恐怕只剩下一个 shell 脚本了:-/


79
投票

您可以像这样定义自己的递归通配符函数:

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)

此功能基于本文的实现,并进行了一些改进。


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或其他)。


2
投票

FWIW,我在 Makefile 中使用了类似的东西:

RECURSIVE_MANIFEST = `find . -type f -print`

上面的示例将从当前目录 ('.') 搜索所有“普通文件”('-type f'),并将

RECURSIVE_MANIFEST
make 变量设置为它找到的每个文件。然后,您可以使用模式替换来减少此列表,或者向 find 提供更多参数以缩小它返回的范围。请参阅 find 的手册页。


2
投票

我的解决方案基于上面的解决方案,使用

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 会为你创建目录!

2
投票

这是我快速编写的一个 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
投票

要递归查找文件而不诉诸外部依赖项(如

find
),您可以使用函数。然后使用另一个答案中的结果来转换文件。

rwildcard=$(wildcard $1) $(foreach d,$1,$(call rwildcard,$(addsuffix /$(notdir $d),$(wildcard $(dir $d)*))))

FLAC_FILES = $(call rwildcard,flac/*.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 "$@"
© www.soinside.com 2019 - 2024. All rights reserved.