如何在多行变量中正确转义配方换行符?

时间:2017-11-10 18:24:09

标签: makefile gnu-make

问题

示例1

考虑以下用户定义的函数:

define func =
    if [ -f $(1) ]; then \
        printf "'%s' is a file\n" '$(1)'; \
        printf "This is a relatively long command that"; \
        printf " won't fit on one line\n"; \
    fi
endef

all:
    $(call func,foo)

这将输出以下内容:

$ make
if [ -f foo ]; then printf "'%s' is a file\n" 'foo'; printf "This is a rel
atively long command that"; printf " won't fit on one line\n"; fi

为了便于阅读,我希望make在多行上打印命令, 如Makefile中所写。我该如何做到这一点?

我尝试过什么

示例2

以下按我想要的方式工作,但不允许参数化 功能:

filename := foo

.PHONY: foo
foo:
    if [ -f $(filename) ]; then \
        printf "'%s' is a file\n" '$(filename)'; \
        printf "This is a relatively long command that"; \
        printf " won't fit on one line\n"; \
    fi

输出:

$ make foo
if [ -f foo ]; then \
        printf "'%s' is a file\n" 'foo'; \
        printf "This is a relatively long command that"; \
        printf " won't fit on one line\n"; \
fi

示例3

我明显的第一直觉是逃避反斜杠:

define func =
    if [ -f $(1) ]; then \\
        printf "'%s' is a file\n" '$(1)'; \\
        printf "This is a relatively long command that"; \\
        printf " won't fit on one line\n"; \\
    fi
endef

输出:

$ make
if [ -f foo ]; then \\
        printf "'%s' is a file\n" 'foo'; \\
        printf "This is a relatively long command that"; \\
        printf " won't fit on one line\n"; \\
fi
/bin/sh: \: command not found
'foo' is a file
/bin/sh: line 1: \: command not found
This is a relatively long command that/bin/sh: line 2: \: command not found
 won't fit on one line
/bin/sh: line 3: \: command not found
make: *** [Makefile:11: all] Error 127

示例4

好的,为什么不试试\\\

$ make
if [ -f foo ]; then \ printf "'%s' is a file\n" 'foo'; \ printf "This is a
relatively long command that"; \ printf " won't fit on one line\n"; \ fi
/bin/sh: -c: line 1: syntax error: unexpected end of file
make: *** [Makefile:11: all] Error 1

实施例5

有趣。我们去四点......

$ make
if [ -f foo ]; then \\\\
        printf "'%s' is a file\n" 'foo'; \\\\
        printf "This is a relatively long command that"; \\\\
        printf " won't fit on one line\n"; \\\\
fi
/bin/sh: \\: command not found
'foo' is a file
/bin/sh: line 1: \\: command not found
This is a relatively long command that/bin/sh: line 2: \\: command not found
 won't fit on one line
/bin/sh: line 3: \\: command not found
make: *** [Makefile:11: all] Error 127

现在我们又回到了上次的位置。

什么有效

示例6

这是唯一可行的方法:

define func =
    if [ -f $(1) ]; then #\\
        printf "'%s' is a file\n" '$(1)'; #\\
        printf "This is a relatively long command that"; #\\
        printf " won't fit on one line\n"; #\\
    fi
endef

输出:

$ make
if [ -f foo ]; then #\\
        printf "'%s' is a file\n" 'foo'; #\\
        printf "This is a relatively long command that"; #\\
        printf " won't fit on one line\n"; #\\
fi

但男人,看起来很丑陋,而且感觉很乱。必须有一个更好的 这样做的方式。或者我只是在第一个错误的方式 放置?

在我看来,make只是被当时发生的魔法所困惑 逃避食谱中的换行符。打印到终端的线条 执行与shell看到的不匹配。这应该被视为一个错误吗?

我在Cygwin上使用GNU Make 4.2.1。

修改

澄清一下:make通常会对配方中的尾部反斜杠进行特殊处理。它们并不像其他地方那样表明线路延续。相反,它们表明多个配方行将被视为单个命令,并且它们将完整地传递给shell。

如果不在配方中,但定义变量,则此特殊处理不适用。简单地连接线,如例1所示。这是预期的。

我希望将双反斜杠转换为变量中的单个字面反斜杠,但是会保留两个反斜杠。当变量在配方中展开时,我希望make的行为就像配方在每一行的末尾都有\\一样。如果是这种情况,则每行将单独执行。但正如您在示例3和示例6中看到的那样,这些行一起执行。

关键是,可以从变量的扩展中获得魔法反斜杠解析。问题是这种行为的机制是不一致和令人困惑的。

3 个答案:

答案 0 :(得分:2)

的Urk!这是由于 make 的noddy解析器。

食谱按原样存储。 当 make 即将调用shell时,它们会被扩展。 整个配方一旦扩展, 第一行传递给shell。 如果该命令成功,则运行第二个命令。 洗涤,冲洗,重复。 保留了行尾的反斜杠, 结果是下一行与第一行同时传递。

然而,在递归变量定义中, 在读取定义

时,删除行末尾的反斜杠
define oneline =
  aa \
  bb \
  cc
endef

$(error [$(value oneline)])

给出了

$ make Makefile:9: *** [ aa bb cc]. Stop.

我们的目标

我们需要按下 make 的语法,以便变量扩展为完全此文本:

target:
    if [ -f foo ]; then \
        printf "'%s' is a file\n" 'foo'; \
        printf "This is a relatively long command that"; \
        printf " won't fit on one line\n"; \
    fi

然后我们只需通过类似

的内容输入 make
$(eval ${var})

反斜杠

只需用空格替换每个换行符 - 反斜杠 - 换行符 - 标签使用$(subst)进行四元组。

执行此操作的功能:

empty :=
tab := ${empty}   # Trailing tab
define \n :=


endef

stanza = $(subst ${\n}, \${\n}${tab})

检查它是否有效:

define func =
  if [ -f $1 ]; then
    printf "'%s' is a file\n" '$1';
    printf "This is a relatively long command that";
    printf " won't fit on one line\n";
  fi
endef

$(error [$(call stanza,$(call func,foo))])

,并提供:

Makefile:23: *** [ if [ -f foo ]; then \ printf "'%s' is a file\n" 'foo'; \ printf "This is a relatively long command that"; \ printf " won't fit on one line\n"; \ fi]. Stop.

请注意,func的定义现在在其行的末尾没有注释。

全部放在一起

define \n :=


endef
empty :=
tab := ${empty}        # Trailing tab

stanza = $(subst ${\n}, \${\n}${tab},$1)

define func =
  if [ -f $1 ]; then
    printf "'%s' is a file\n" '$1';
    printf "This is a relatively long command that";
    printf " won't fit on one line\n";
  fi
endef

define rule =
  target:
        echo vvv
        $(call stanza,$(call func,foo))
        echo ^^^
endef

$(eval ${rule})

导致

$ touch foo; make -R --warn echo vvv vvv if [ -f foo ]; then \ printf "'%s' is a file\n" 'foo'; \ printf "This is a relatively long command that"; \ printf " won't fit on one line\n"; \ fi 'foo' is a file This is a relatively long command that won't fit on one line echo ^^^ ^^^

一项有趣的学术活动。 仍然不能把文字反斜杠放在:)

答案 1 :(得分:1)

define newline :=
$(strip)
$(strip)
endef

define func =
    if [ -f $(1) ]; then \$(newline)\
       printf "'%s' is a file\n" '$(1)'; \$(newline)\
       printf "This is a relatively long command that"; \$(newline)\
       printf " won't fit on one line\n"; \$(newline)\
    fi
endef

func2 = if [ -f $(1) ]; then \$(newline)   printf "'%s' is a file\n" '$(1)'; \$(newline)   printf "This is a relatively long command that"; \$(newline)   printf " won't fit on one line\n"; \$(newline)fi

all:
    $(call func,foo)
    @echo --------------
    $(call func2,foo)

第一个似乎是空间剥离的。第二个看起来很不错,但是......好吧,好像被卡在岩石和硬地之间:/

答案 2 :(得分:0)

我找到了一个似乎更好的解决方案(尽管仍然很笨拙):

define func =
    if [ -f $(1) ]; then $\\
        printf "'%s' is a file\n" '$(1)'; $\\
        printf "This is a relatively long command that"; $\\
        printf " won't fit on one line\n"; $\\
    fi
endef

all:
    @echo vvvvvvvv
    $(call func,foo)
    @echo ^^^^^^^^

输出:

$ make
vvvvvvvv
if [ -f foo ]; then \
        printf "'%s' is a file\n" 'foo'; \
        printf "This is a relatively long command that"; \
        printf " won't fit on one line\n"; \
fi
'foo' is a file
This is a relatively long command that won't fit on one line
^^^^^^^^

我认为这是这样的:

  1. 在第一次扫描连续行时,$被忽略,因此 解析器看到\\,假定反斜杠已转义,并保留行 完好无损。

  2. 扩展功能时,$\被识别为变量。假设 您尚未实际分配名为\的变量,则该变量扩展为 什么都没有。

  3. 现在,我们在行尾只剩下一个反斜杠,即 就像是按字面意思输入配方一样。