在 makefile 中,我有几个如下所示的规则:
out.txt: foo.sh input.txt
./foo.sh -i input.txt > out.txt
如果foo.sh
失败,out.txt
则将创建为 0 大小的文件。如果我跑制作再次,它会错误地认为out.txt
文件已成功创建,并且不会再次运行该规则。
处理这种情况的正确方法是什么?
答案1
如果规则失败,您可以请求删除目标文件,方法是定义一个特殊目标命名的.DELETE_ON_ERROR
。它不需要执行任何操作或具有任何依赖项,因此只需将其添加到您的 makefile 中:
.DELETE_ON_ERROR:
然后你会得到以下内容:
$ cat Makefile
.DELETE_ON_ERROR:
foo:
false > foo
$ make
false > foo
make: *** [foo] Error 1
make: *** Deleting file `foo'
zsh: exit 2 make
$ stat foo
stat: cannot stat `foo': No such file or directory
从食谱中的错误:
通常,当配方行失败时,如果它根本更改了目标文件,则该文件已损坏且无法使用,或者至少未完全更新。然而该文件的时间戳表明它现在是最新的,因此下次
make
运行时,它不会尝试更新该文件。这种情况和 shell 被信号杀死的情况一样;看中断。因此,如果在开始更改文件后配方失败,通常正确的做法是删除目标文件。如果作为目标出现,make
则会执行此操作。.DELETE_ON_ERROR
这几乎总是你想做的make
,但这不是历史实践;因此为了兼容性,您必须明确请求它。
答案2
只要有可能,makefile 规则就应该首先以临时名称创建目标,然后将其移动到位。这样,如果构建过程因任何原因中断,就不会出现无法将半写入的目标文件与完全写入的文件区分开来的情况。
out.txt: foo.sh input.txt
./foo.sh -i input.txt >[email protected]
mv -f [email protected] $@
mv -f [email protected] $@
是常见的 makefile 习惯用法。
朱利亚诺的回答显示了动态生成临时文件名称的变体。如果可能有多个进程生成相同的目标,或者目录可以由其他用户写入,则需要动态名称生成。对于构建树来说,这种情况很少发生(如果这些是问题,典型的 makefile 中发生的很多事情都会被破坏),因此额外的复杂性通常是不必要的。
答案3
由于这似乎foo.sh
是您编写的内容(或由您编写的内容生成的),因此我会考虑-o
向其添加一个标志,该标志采用输出文件名。那么您就不必依赖默认的 I/O 重定向行为。您的脚本可以清理部分输出文件,如果您可以尽早检测到错误,甚至可能避免首先创建该文件。
答案4
我认为foo.sh
错误时会正确返回非零。然后你应该做一个临时的,并且只在成功时覆盖输出。
tmp=$$(mktemp) && ./foo.sh input.txt > $$tmp && mv $$tmp $@ || rm -f $$tmp && false