You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Makefile中为运行时生成的文件设置通配符目标与依赖?

问题描述

在数据科学项目中,需要用GNU Make构建训练数据流程:

  1. 下载包含10k+音频文件的zip包
  2. 解压其中的.aiff文件到data/train目录
  3. 用ffmpeg将.aiff转换为.wav后删除原文件

原Makefile代码如下:

.PHONY: dataset
DATA_DIR = data

dataset: ${DATA_DIR}/train/%.wav
    @echo Dataset built!

${DATA_DIR}/train/%.wav: ${DATA_DIR}/train/%.aiff
    ffmpeg -i $*.aiff $*.wav; rm $*.aiff

${DATA_DIR}/train/%.aiff: ${DATA_DIR}/data.zip
    unzip $< data/train/*.aiff

${DATA_DIR}/data.zip:
    # ... download the zip file and place it in ./data...  

执行时解压正常,但转换步骤报错,提示%.aiff: No such file or directory——因为Make将%当作字面量处理,而非动态匹配文件名。

后续尝试用目录作为目标实现功能,但违反了Make的最佳实践(目录目标无法追踪内部文件的更新):

dataset: ${DATA_DIR}/train
    @echo Dataset built!

${DATA_DIR}/train: ${DATA_DIR}/data.zip
    unzip $< "data/train/*.aiff"
    for f in $@/*.aiff; do ffmpeg -i "$${f}" "$${f%.aiff}.wav"; rm "$${f}"; done

${DATA_DIR}/data.zip:
    # ... download the zip file and place it in ./data...  

需求:在不知道具体文件名和数量的情况下,让Make自动识别解压后的.aiff文件,生成对应的.wav目标与依赖。

解决方案

核心思路

通过生成文件列表的方式,让Make获取解压后的.aiff文件路径,再动态生成.wav目标。这种方法既遵循Make的最佳实践,又能自动处理大量文件。

完整Makefile实现

.PHONY: dataset clean
DATA_DIR = data
TRAIN_DIR = $(DATA_DIR)/train
# 用于记录解压后的所有.aiff文件路径
AIFF_LIST = $(TRAIN_DIR)/aiff_files.list

# 最终目标:依赖所有转换完成的.wav文件
dataset: $(patsubst %.aiff,%.wav,$(shell cat $(AIFF_LIST) 2>/dev/null))
	@echo Dataset built successfully!

# 单个.aiff转.wav的模式规则:只处理存在的文件,转换后删除原文件
$(TRAIN_DIR)/%.wav: $(TRAIN_DIR)/%.aiff
	ffmpeg -hide_banner -loglevel error -i $< $@ && rm $<

# 解压zip包并生成.aiff文件列表
$(AIFF_LIST): $(DATA_DIR)/data.zip
	# 解压指定目录下的.aiff文件,避免解压无关内容
	unzip $< "*/train/*.aiff" -d $(DATA_DIR)
	# 生成排序后的文件列表,确保Make目标顺序稳定
	find $(TRAIN_DIR) -name "*.aiff" | sort > $@

# 下载zip包的规则(替换为实际下载命令)
$(DATA_DIR)/data.zip:
	# 示例:wget -O $@ https://example.com/dataset.zip
	@echo "Downloading dataset zip file..."

# 清理所有生成的文件
clean:
	rm -rf $(TRAIN_DIR) $(DATA_DIR)/data.zip

工作流程说明

  1. 触发顺序:执行make dataset时,Make首先检查$(AIFF_LIST)是否存在。如果不存在,会先执行下载(若data.zip未生成)、解压规则,同时生成.aiff文件列表。
  2. 动态生成目标:通过$(shell cat $(AIFF_LIST))读取文件列表,用patsubst将每个.aiff路径替换为.wav,作为dataset的依赖。
  3. 单个文件转换:模式规则$(TRAIN_DIR)/%.wav: $(TRAIN_DIR)/%.aiff会为每个.wav目标匹配对应的.aiff源文件,执行转换并删除原文件。
  4. 增量更新:如果zip包更新,$(AIFF_LIST)会重新生成,Make会自动重新转换所有文件;如果单个.aiff文件被修改,只会重新转换对应的.wav。

替代方案(二次扩展)

如果不想生成列表文件,也可以使用.SECONDEXPANSION:实现动态匹配,但第一次执行时需要手动触发解压:

.PHONY: dataset unzip
DATA_DIR = data
TRAIN_DIR = $(DATA_DIR)/train

.SECONDEXPANSION:
dataset: $$(wildcard $(TRAIN_DIR)/*.wav)
	@echo Dataset built!

$(TRAIN_DIR)/%.wav: $(TRAIN_DIR)/%.aiff
	ffmpeg -hide_banner -loglevel error -i $< $@ && rm $<

unzip: $(DATA_DIR)/data.zip
	unzip $< "*/train/*.aiff" -d $(DATA_DIR)

$(DATA_DIR)/data.zip:
	# 下载命令
	@echo "Downloading zip file..."

执行时需先运行make unzip,再运行make dataset,否则第一次执行make dataset时因无.wav文件会直接结束。

内容的提问来源于stack exchange,提问作者codersblock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:07:23