Function makefile子shell中的变量扩展失败

Function makefile子shell中的变量扩展失败,function,shell,makefile,Function,Shell,Makefile,我在makefile(myfunction.mk)中有一个makefile函数: 此函数调用脚本并将结果追加到日志(如果日志不存在,则使用其文件夹创建),仅当作为第4($(4))参数给定的makefile变量等于“yes”时,才会追加脚本的结果 你这样称呼它: include myfunction.mk OUTPUT_ENABLED ?= yes target: $(call call_script, echo "test", reports/mylog.log, &

我在makefile(myfunction.mk)中有一个makefile函数:

此函数调用脚本并将结果追加到日志(如果日志不存在,则使用其文件夹创建),仅当作为第4($(4))参数给定的makefile变量等于“yes”时,才会追加脚本的结果

你这样称呼它:

include myfunction.mk

OUTPUT_ENABLED ?= yes

target:
  $(call call_script, echo "test", reports/mylog.log, "doing test", OUTPUT_ENABLED)  
这在很大程度上起作用:

  • 如果我用“tee-a”代替“tee-a”,它会工作
  • 如果我将“??”替换为$(重定向),它将失败
  • 如果我用$$REDIRECT替换'???',它将失败
为什么?

注意:从shell运行它
/bin/sh:dash的符号链接


注意:当然我想添加一个
ifeq
,它允许我检查
$(4)
,并将
|tee-a
替换为
&>
,我假设您在配方中使用
调用
,而不是在生成文件中使用。您的shell脚本几乎没有问题。首先,如果在命令行上尝试以下操作:

mkdir -p reports
REDIRECT='| tee -a'
echo '>> echo "test"'
(echo "test" $REDIRECT reports/mylog.log)
eval "echo "test" $REDIRECT reports/mylog.log"
您将看到
echo
考虑:

"test" $REDIRECT reports/mylog.log
作为它的论据。它们被展开并回显,打印:

test | tee -a reports/mylog.log
在标准输出上,我猜不是你期望的效果。例如,您可以使用
eval
。在命令行上:

mkdir -p reports
REDIRECT='| tee -a'
echo '>> echo "test"'
(echo "test" $REDIRECT reports/mylog.log)
eval "echo "test" $REDIRECT reports/mylog.log"
在Makefile中,它将成为:

eval "$(1) $$REDIRECT $(2)"
接下来,您不应引用
call
的第三个参数,因为这些引用将在未修改的情况下传递,并且您的脚本将通过make as展开:

echo " "doing test" terminated with error $RET_CODE"
也许不是你想要的

第三,您应该避免在
call
的参数中使用无用的空格,因为它们也被保留下来(正如您在前面两个双引号之间看到的):

对于您最后想要的功能,将
OUTPUT\u ENABLED
的值传递给
call
会稍微容易一些,而不是它的名称,但是我们这样做:

$ cat myfunction.mk
define call_script
set +x
mkdir -p $$(dirname $(2))
if [ ! -f $(2) ]; then
echo "" > $(2)
fi
if [ "$($(4))" = "yes" ]; then
REDIRECT='| tee -a'
else
REDIRECT='&>>'
fi
echo '>> $(1)'
eval "$(1) $$REDIRECT $(2)"
RET_CODE=$$?
echo "exit_code is: $$RET_CODE"
if [ ! $$RET_CODE = 0 ]; then 
echo "$(3) terminated with error $$RET_CODE"
exit $$RET_CODE
else 
if [ ! -z "$(strip $(3))" ]; then
echo "$(3) done"
fi
fi
endef
$ cat Makefile
.ONESHELL:

include myfunction.mk

OUTPUT_ENABLED ?= yes

target:
    $(call call_script,echo "test",reports/mylog.log,doing test,OUTPUT_ENABLED)

请注意,我移动了主Makefile中的
.ONESHELL:
,因为最好不要将其隐藏在包含的文件中。取决于您。

这里最有问题的问题是,如果您通过管道传输命令,则退出代码是管道中最后一个命令的退出代码,例如
false | tee foo.log
将以0退出,因为
tee
很可能会成功。还要注意,管道只重定向stdout,所以除非显式重定向,否则日志将不包含任何stderr消息

考虑到管道命令会影响退出代码,并且缺少
$PIPESTATUS
的可移植性(在
破折号中不支持这一点),我会尽量避免使用管道命令,并使用临时文件来收集输出,即:

$ cat Makefile
# $(1) - script to execute
# $(2) - log file
# $(3) - description
define call_script
echo '>> $(1)'
$(if $(OUTPUT_ENABLED), \
  $(1) > $@.log 2>&1; RET_CODE=$$?; mkdir -p $(dir $(2)); cat $@.log >> $(2); cat $@.log; rm -f $@.log, \
  $(1); RET_CODE=$$? \
); \
echo "EXIT_CODE is: $${RET_CODE}"; \
if [ $${RET_CODE} -ne 0 ]; then $(if $(3),echo "$(3) terminated with error $${RET_CODE}";) exit $${RET_CODE}; fi; \
$(if $(3), echo "$(3) done.")
endef

good:
        $(call call_script,echo "test",reports/mylog.log,doing test)

bad:
        $(call call_script,mkdir /root/foo,reports/mylog.log,intentional fail)

ugly:
        $(call call_script,bad_command,reports/mylog.log)
常规调用不会创建日志,并在出现错误时停止:

$ make good bad ugly
echo '>> echo "test"'
>> echo "test"
echo "test"; RET_CODE=$? ; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "doing test terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "doing test done."
test
EXIT_CODE is: 0
doing test done.
echo '>> mkdir /root/foo'
>> mkdir /root/foo
mkdir /root/foo; RET_CODE=$? ; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "intentional fail terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "intentional fail done."
mkdir: cannot create directory ‘/root/foo’: Permission denied
EXIT_CODE is: 1
intentional fail terminated with error 1
make: *** [Makefile:19: bad] Error 1
请注意,
bad
上的故障导致未生成
丑陋的
。现在与日志相同:

$ make good bad ugly OUTPUT_ENABLED=1
echo '>> echo "test"'
>> echo "test"
echo "test" > good.log 2>&1; RET_CODE=$?; mkdir -p reports/; cat good.log >> reports/mylog.log; cat good.log; rm -f good.log; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "doing test terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "doing test done."
test
EXIT_CODE is: 0
doing test done.
echo '>> mkdir /root/foo'
>> mkdir /root/foo
mkdir /root/foo > bad.log 2>&1; RET_CODE=$?; mkdir -p reports/; cat bad.log >> reports/mylog.log; cat bad.log; rm -f bad.log; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "intentional fail terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "intentional fail done."
mkdir: cannot create directory ‘/root/foo’: Permission denied
EXIT_CODE is: 1
intentional fail terminated with error 1
make: *** [Makefile:19: bad] Error 1

$ cat reports/mylog.log
test
mkdir: cannot create directory ‘/root/foo’: Permission denied
请注意,这一次也没有运行。但如果稍后运行,它将正确附加到日志中:

$ make ugly OUTPUT_ENABLED=1
echo '>> bad_command'
>> bad_command
bad_command > ugly.log 2>&1; RET_CODE=$?; mkdir -p reports/; cat ugly.log >> reports/mylog.log; cat ugly.log; rm -f ugly.log; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then  exit ${RET_CODE}; fi;
/bin/sh: 1: bad_command: not found
EXIT_CODE is: 127
make: *** [Makefile:22: ugly] Error 127

$ cat reports/mylog.log
test
mkdir: cannot create directory ‘/root/foo’: Permission denied
/bin/sh: 1: bad_command: not found

就我个人而言,我不喜欢以这种方式实现日志记录。它很复杂,只记录命令的输出,不记录make输出本身,只记录显式调用的命令的输出。我宁愿保持
Makefile
干净简单,只运行
make2>&1 | tee log
而不是记录输出。

您显示的有点奇怪。根本没有目标?你这样调用
调用
?我不明白。添加用于您理解的所有信息,使用单独的文件啊,好的,我现在明白了。要添加到下面(很好)的答案中,您不能使用
$(重定向)
或使用
ifeq
等的原因是这些是make变量等。但您正在shell中运行此操作。当您的shell脚本中有设置shell变量
REDIRECT
,而不是make变量
REDIRECT
,因此不能使用make构造时。请注意:如果make宏有一个单独的Makefile(
myfunction.mk
),相反,使用
myfunction.sh
shell脚本并在主生成文件的配方中调用该脚本可能更有意义。脚本将不那么笨拙,更容易维护,更不容易出错。Make是一个漂亮的工具,但是当配方变得太复杂时,即使你用宏隐藏了它,它通常也表明应该使用其他更合适的工具。如果我想在调用“$(1)”后执行“$”,我如何使用eval来执行它??如果我把它放在评估之后,我想它会接受评估返回码?谢谢
eval
返回代码是它计算的命令的返回代码;如果[$?!=0]
只是说
if!cmd
;另请看,您的答案几乎就是我要寻找的,除了如果我执行
eval“$(1)$$REDIRECT$(2)”
它不会在出错时停止,但行
$(1)
会停止。此外,在完成
$(1)
之前,将显示
$(3)已完成
。。。不是我想要的。这里最有问题的问题是,如果你通过管道传输命令,退出代码就是管道中最后一个命令的退出代码,例如
false | tee foo.log
将以0退出,因为
tee
很可能会成功。还请注意,管道仅重定向stdout,因此您的日志将不包含任何stderr消息,除非称为
2>&1 | tee-a