【问题标题】:Makefile replace symlink list of filesMakefile 替换文件的符号链接列表
【发布时间】:2019-01-22 23:53:17
【问题描述】:

使用 GNU Make,我想获取一个文件列表并在另一个目录中创建它们的符号链接(具有相同的文件名),但还要覆盖任何具有相同文件名的现有链接。

existing_files = $(wildcard dir1/dir2/*.txt)

# The next line shows where I would want to put the symbolic links
symlinks = $(wildcard new_dir1/new_dir2/*.txt)

make_some_links:
# Remove previous symlinks if they share name as existing_files
ifeq ($(notdir $(existing_files)), $(notdir $(symlinks)))
    $(info Removing previous symlinks)
    @rm $(symlinks)
endif
# Loop to link here

顺便说一句,上述条件是尝试匹配每个列表的每个元素,还是只有其中一个元素匹配成功?

我不太清楚如何在 Make 中循环,包括使用$(foreach),所以我编写了以下循环来说明我在具有相同变量名的 Python 中的意思。

for i in len($(existing_files)):
    @ln -s $(existing_files)[i] $(symlinks)[i]

这里,$(list_files) 中的第一个元素链接到$(symlinks) 中的第一个元素。有关如何在 Make 中编写此循环的任何见解,或者是否有直接的更好方法来解决此问题将非常有帮助。谢谢。

【问题讨论】:

标签: makefile gnu-make gnu symlink


【解决方案1】:

让我们分阶段进行。

首先,创建一个符号链接,并删除该名称的预先存在的链接(如果有的话):

ln -fs filename linkname

现在列出现有文件:

existing_files = $(wildcard dir1/dir2/*.txt)

到目前为止,一切都很好。假设这给了我们dir1/dir2/red.txt dir1/dir2/green.txt。

# The next line shows where I would want to put the symbolic links
symlinks = $(wildcard new_dir1/new_dir2/*.txt)

这将为您提供该目录中已经存在的内容的列表,这可能不是您想要的。我们必须从我们拥有的文件列表中构建我们想要的链接列表:

filenames := $(notdir $(existing_files))
symlinks := $(addprefix new_dir1/new_dir2/, $(filenames))

现在有一个或多个规则来构建符号链接。我们可以写两条明确的规则:

new_dir1/new_dir2/red.txt: dir1/dir2/red.txt
    ln -fs dir1/dir2/red.txt new_dir1/new_dir2

new_dir1/new_dir2/green.txt: dir1/dir2/green.txt
    ln -fs dir1/dir2/green.txt new_dir1/new_dir2

但这太多余了,而且我们事先不知道文件名。首先我们可以通过定义一个变量并使用the automatic variale $<来消除一些冗余:

DEST_DIR := new_dir1/new_dir2

$(DEST_DIR)/red.txt: dir1/dir2/red.txt
    ln -fs $< $(DEST_DIR)

$(DEST_DIR)/green.txt: dir1/dir2/green.txt
    ln -fs $< $(DEST_DIR)

现在我们可以看到如何用pattern rule 替换这些规则:

$(DEST_DIR)/%.txt: dir1/dir2/%.txt
    ln -fs $< $(DEST_DIR)

现在我们只需要一个需要链接的主规则:

.PHONY: make_some_links
make_some_links: $(symlinks)

【讨论】:

  • 这个解决方案有几个问题。 1) 如果一个链接已经存在但指向另一个文件,它可以被认为是最新的,因此不会被替换。 2) 如果存在与目标链接同名的目录,ln 将不会按照您的预期进行。 3) 您必须使用ln 的-r 选项来创建相对于链接位置的链接。 4)在尝试创建链接之前,目标目录应该存在。
  • 虽然这篇文章很有帮助,但它缺少我需要的最后一步。我的问题中没有具体说明这件小事,所以我接受了。我有多个用于符号链接和原始文件的目录,所以我需要自己处理。我为每个目录复制了倒数第二个步骤。这可能并不理想,但它现在有效,这需要太多时间。谢谢
  • @RenaudPacalet:你是对的。我匆忙把这个答案放在一起,在发布后几分钟就开始看到缺陷。当我开始纠正它时,你已经发布了你的答案,它涵盖了所有相同的点。我会删除这个。
  • @RenaudPacalet: ...该死,我无法删除已接受的答案,即使它是我的。
【解决方案2】:

在大多数情况下,当您开始在 make 配方中设计 shell 循环时,您会错过 make 的重要功能。 Make 会自然地“循环”你声明的目标,以达到你要求它达到的目标。并且 make 提供了许多方法来分解相似的规则。

但在目标是符号链接的情况下,需要考虑几个问题:

  • 如果您将链接声明为常规目标,并且如果某些链接已经存在但指向您想要的文件之外的其他文件,则 make 可以将它们视为最新并跳过它们,而不是替换它们。因此,您必须找到一种方法来强制 make 重新创建所有链接,或者至少重新创建那些不指向正确文件的链接。而最后的修改时间不足以解决这个问题。
  • 即使使用-f 选项ln 也不会替换现有目录(或指向目录的链接),而是会在目录中创建链接。因此,您必须首先删除与目标链接冲突的所有目录(或指向目录的链接)。
  • ln 命令应与-sr 选项一起使用,以创建相对于链接位置的符号链接。
  • 在您尝试创建第一个链接之前,目标目录 (new_dir1/new_dir2) 应存在。

如果您不在乎重新创建已经正确的链接,事情就很简单了:

  • 声明一个虚假的虚拟目标 (force) 并使其成为所有链接的先决条件,以强制重新创建它们,
  • 在创建链接之前,请删除与其冲突的现有链接、文件或目录,
  • 使用正确的ln 选项,
  • 将目标目录声明为链接的order-only prerequisite,并添加规则以在缺少时创建它。

以下应该可以完成工作:

source_dir     := dir1/dir2
link_dir       := new_dir1/new_dir2
existing_files := $(wildcard $(source_dir)/*.txt)
symlinks       := $(patsubst $(source_dir)/%,$(link_dir)/%,$(existing_files))

.PHONY: all force
all: $(symlinks)

$(link_dir)/%: $(source_dir)/% force | $(link_dir)
    rm -rf $@
    ln -sr $< $@

force:;

$(link_dir):
    mkdir -p $@

如果你想避免重新创建现有的正确链接,事情会变得更加困难,因为 make 需要知道哪些是正确的,哪些不是,这不能基于上次修改时间......但正如我怀疑重新创建现有正确链接对性能的影响可以忽略不计,我建议我们不要为不存在的问题设计复杂的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-25
    • 1970-01-01
    • 1970-01-01
    • 2022-11-29
    • 1970-01-01
    • 2019-01-08
    相关资源
    最近更新 更多