【问题标题】:How to define Makefile target(s) when rule calls script?规则调用脚本时如何定义 Makefile 目标?
【发布时间】:2017-07-19 22:13:15
【问题描述】:

我第一次尝试使用make 来运行一系列脚本。我有一个像这样的树目录结构:

project
├── data
│   └── run1
│       └── pass
│            ├── 0
│            ├── 1
│            └── 2
├── include
│   └── variables.mk
├── Makefile
└── scripts
    └── operations.sh

我有一个数据集run1,它在pass 中有多个目录,所有目录都包含txt 文件。 operations.sh 脚本使用递归搜索命名目录(此处为pass)的方法。我希望有更多数据(run2run3 等),并且希望能够在数据可用时以相同的方式处理它们。为此,我在variables.mk 上使用include(不确定这是否合适,但它工作正常),定义$(INPUT_RUNS),我将在新运行到达时简单地更新它。

我写了一个测试 Makefile

include $(CURDIR)/include/variables.mk
DATA_DIRS := $(addprefix $(CURDIR)/data/, $(foreach r, $(INPUT_RUNS), $(r))/pass)
OUT_DIRS := $(addprefix $(CURDIR)/analysis/, $(foreach r, $(INPUT_RUNS), $(r)))

##targets 
all: operations_run 

##operations
operations_run: $(OUT_DIRS) $(DATA_DIRS)
  mkdir -p $</operations
  sh scripts/operations.sh $</operations $(DATA_DIRS)

这指定了一组目录(dataanalysis 每次运行)。然后我可以创建一个运行operations.sh 的目标。这工作正常。但在我看来,它实际上并没有正确使用make。我想make 输出,然后如果重新运行make,如果数据或分析的任何部分都没有更改,则不重新生成输出。

因此,我的问题是:通常,目标是文件。 operations.sh 脚本运行的方法不是我开发的,并且对输入和输出有特定的规则(两者都是 dirs)。我想让目标成为operations.sh 生成的一组文件。我希望它能够像

%.output.txt: $(DATA_DIRS)
    sh operations.sh $< > $@

我想我知道如何使用% 来命名依赖项,虽然还没有测试过。我可以将 $(DATA_DIRS) 作为依赖项,而 makeing 目标文件吗?从概念上讲,我不知道从哪里开始。

非常感谢任何帮助。

【问题讨论】:

  • 如果您告诉我们一些关于“数据或分析”中可能发生的变化的事情会简单得多。你的意思是txt文件可以添加、删除和修改,还是仅此而已?
  • 请注意 make 对于函数中的空格是特殊的。逗号后的空格将被视为额外的空输入。例如我认为DATA_DIRS := $(addprefix $(CURDIR)/data/,$(foreach r,$(INPUT_RUNS),$(r))/pass) 是您想要的。
  • @beta,在分析中,txt 文件由脚本重新格式化,该脚本检查“条形码”字符串并将txt 中的行排序为多个之一(通常为 8,但最多为 12 ) 目录相应地。初始输入txt 不变。虽然我目前有一次运行 (run1),但我期望更多,但这些数据将采用相同的格式并进行相同的分析。
  • @bdecaf 我已经包含了一个测试来回显目录,并且没有空格,但要记住这一点

标签: bash makefile gnu-make


【解决方案1】:

我认为规则应该是这样的:

$(CURDIR)/analysis/%: $(CURDIR)/data/%/pass
   sh operations.sh $< $@

基本上有一个单独的outdirs的规则。假设操作需要输入和输出目录。

尽管这可能“认为”输出目录因任何原因而被更改(想想来自查看器的临时文件等)。我个人喜欢手动“标记”完成。

$(CURDIR)/analysis/%/.done: $(CURDIR)/data/%/pass
   sh operations.sh $< $(@:/.done=)
   touch $@

这将在 outdir 中放置一个空的 .done 文件,其中包含上次成功创建的时间戳。

operations_run: $(addsuffix /.done,$(OUT_DIRS))

运行整个集合。

【讨论】:

  • 这种方法对我来说很有意义。澄清一下:我假设 $(@:/.done=) 从目标中删除 /.done 允许脚本将目标目录作为输入(很好的提示)。然后将operations_run 目标添加到all: 规则中,以使all 知道要处理的内容。由于某种原因,我不得不将addpostfix 更改为addprefix,然后它才能工作(?!),但现在可以了。请问,像这样盖章的文件很常见吗?当我不使用文件作为目标时,我可以看到自己使用它作为标准。感谢您的帮助。
  • 愚蠢的我。它是addsuffix。我会在答案中修复它。就我个人而言,当我的脚本没有产生简单的结果时,我经常使用这个touch。我看到的唯一缺点是它可能会让人感到困惑。是否普遍我不敢说 - 我仍然认为它是 hack。
  • 是的 - 你是对的 $(@:/.done=)。有几种方法可以做到这一点,但没有一种是直观的。
【解决方案2】:

如果您不知道脚本的实际输入是什么,则很难判断是否必须重新完成特定目标。 Make 比较目标文件和必备文件的最后修改时间。目录更难用于此分析,因为目录的最后修改时间具有不同的含义:添加或删除文件时它会更改,而不是文件内容更改时。您应该首先了解脚本的真正输入是什么,然后表达输出 files 和输入 files 之间的依赖关系。

注意:您可以告诉 make 构建要处理的目录列表,例如:

DATA_DIR := $(shell find $(CURDIR)/data -type d -name pass)

【讨论】:

  • 我想我不知道真正的输入,有很多文件,它们在不同的运行中有所不同。然而,一旦运行完成,这些数据就不会改变。我确实知道脚本将返回一组目录,其中有一组文件,一旦写入这些文件就不会改变,并且可以cat'd 一起制作一个我希望的文件在 Makefile 中继续。我认为@bdecaf 的解决方案是有道理的,因为它首先是“运行”的,并且添加./done 文件意味着目标是一个文件(这是明智的)。
  • 关于使用 shell 调用为 $(DATA_DIRS) 定义现有目录的巧妙提示
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-18
  • 1970-01-01
相关资源
最近更新 更多