【问题标题】:Multi-line bash commands in makefilemakefile 中的多行 bash 命令
【发布时间】:2012-04-24 15:47:16
【问题描述】:

考虑到每个命令都在其自己的 shell 中运行,在 makefile 中运行多行 bash 命令的最佳方式是什么?例如,像这样:

for i in `find`
do
    all="$all $i"
done
gcc $all

【问题讨论】:

    标签: bash makefile


    【解决方案1】:

    您可以使用反斜杠来续行。但是请注意,shell 接收的整个命令连接成一行,因此您还需要用分号终止某些行:

    foo:
        for i in `find`;     \
        do                   \
            all="$$all $$i"; \
        done;                \
        gcc $$all
    

    但如果您只想获取find 调用返回的整个列表并将其传递给gcc,您实际上不一定需要多行命令:

    foo:
        gcc `find`
    

    或者,使用更传统的外壳 @9​​87654325@ 方法(注意 $ 转义):

    foo:
        gcc $$(find)
    

    【讨论】:

    • 是的,一个简短的回答 1. $ := $$ 2. 用 \ 附加多行 BTW bash 家伙通常建议用 $$(find) 替换 `find` 调用
    【解决方案2】:

    如问题所示,每个子命令都在其自己的 shell 中运行。这使得编写非平凡的 shell 脚本有点混乱 -- 但这是可能的! 解决方案是将你的脚本合并到 make 将考虑单个子命令(单行)的内容中。

    在 makefile 中编写 shell 脚本的提示:

    1. 通过替换为$$ 来逃避脚本对$ 的使用
    2. 通过在命令之间插入; 将脚本转换为单行
    3. 如果要在多行上编写脚本,请使用\ 转义行尾
    4. 可选择以 set -e 开头,以匹配 make 的规定以在子命令失败时中止
    5. 这完全是可选的,但您可以用(){} 将脚本括起来以强调多行序列的内聚性——这不是典型的makefile 命令序列

    这是一个受 OP 启发的示例:

    mytarget:
        { \
        set -e ;\
        msg="header:" ;\
        for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
        msg="$$msg :footer" ;\
        echo msg=$$msg ;\
        }
    

    【讨论】:

    • 您可能还希望在您的 makefile 中使用 SHELL := /bin/bash 来启用 BASH 特定的功能,例如 process substitution
    • 微妙之处:{ 之后的空格对于防止将 {set 解释为未知命令至关重要。
    • 微妙点 #2:在 makefile 中可能无关紧要,但是如果您有时想要复制脚本并运行,则使用 {}() 包装之间的区别会产生很大的不同它直接从shell提示符。你可以通过声明变量对你的 shell 实例造成严重破坏,尤其是在{} 内部使用set 修改状态。 () 防止脚本修改您的环境,这可能是首选。示例(这将结束您的 shell 会话):{ set -e ; } ; false.
    • 您可以使用以下形式包含 cmets:command ; ## my comment \` (the comment is between ;` 和 `\`)。这似乎工作正常,除了 if 您手动运行命令(通过复制和粘贴),命令历史记录将以破坏命令的方式包含注释(如果您尝试重用它)。 [注意:由于在反引号中使用了反斜杠,此注释的语法突出显示被破坏。]
    • 如果你想在makefile里面的bash/perl/script中打断一个字符串,关闭字符串引号,反斜杠,换行,(不缩进)打开字符串引号。例子; perl -e QUOTE print 1;引用反斜杠 NEWLINE 引用打印 2 引用
    【解决方案3】:

    ONESHELL 指令允许编写多行配方以在同一个 shell 调用中执行。

    all: foo
    
    SOURCE_FILES = $(shell find . -name '*.c')
    
    .ONESHELL:
    foo: ${SOURCE_FILES}
        FILES=()
        for F in $^; do
            FILES+=($${F})
        done
        gcc "$${FILES[@]}" -o $@
    

    但有一个缺点:特殊前缀字符(“@”、“-”和“+”)的解释不同。

    https://www.gnu.org/software/make/manual/html_node/One-Shell.html

    【讨论】:

      【解决方案4】:

      当然,编写 Makefile 的正确方法是实际记录哪些目标取决于哪些源。在简单的情况下,建议的解决方案将使foo 依赖于自己,但当然,make 足够聪明,可以放弃循环依赖。但是如果你将一个临时文件添加到你的目录中,它会“神奇地”成为依赖链的一部分。最好一劳永逸地创建一个明确的依赖关系列表,也许通过脚本。

      GNU make 知道如何运行 gcc 以从一组 .c.h 文件中生成可执行文件,所以也许你真正需要的只是

      foo: $(wildcard *.h) $(wildcard *.c)
      

      【讨论】:

        【解决方案5】:

        只调用命令有什么问题?

        foo:
               echo line1
               echo line2
               ....
        

        对于您的第二个问题,您需要转义 $,而是使用 $$,即 bash -c '... echo $$a ...'

        编辑:您的示例可以像这样重写为单行脚本:

        gcc $(for i in `find`; do echo $i; done)
        

        【讨论】:

        • 因为“每个命令都在自己的 shell 中运行”,而我需要在 command2 中使用 command1 的结果。就像在示例中一样。
        • 如果您需要在 shell 调用之间传播信息,您需要使用某种形式的外部存储,例如临时文件。但是你总是可以重写你的代码以在同一个 shell 中执行多个命令。
        • +1,尤其是简化版。和只做 ``gcc `find``` 不一样吗?
        • 是的。但你当然可以做比“回声”更复杂的事情。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-21
        • 2012-09-20
        • 1970-01-01
        • 1970-01-01
        • 2017-12-04
        • 1970-01-01
        相关资源
        最近更新 更多