我正在编写一个 Makefile(在 Ubuntu 20.04 上,如果相关的话),并注意到echo
.以这个简单的 Makefile 为例:
test.txt:
@echo -e 'hello\nworld'
@echo -e 'hello\nworld' > test.txt
当我运行时make
,我希望在 stdout 上看到与 test.txt 中相同的内容,但事实上我没有。我在标准输出上得到这个:
hello
world
但这在 test.txt 中:
-e hello
world
同时,如果我-e
从 Makefile 中的两行中删除,我会在标准输出上得到:
hello\nworld
在 test.txt 中:
hello
world
这让我想知道是否echo
检测到重定向并表现出不同的行为,但当我在 shell 中手动运行它时却没有/bin/echo -e 'hello\nworld' > test.txt
(如我通常期望的那样,它会在单独的行上产生hello
和)。world
我什至通过添加一行来确认 Makefile 使用的是 /bin/echo 而不是内置的 shell @echo --version
。
这里发生了什么?
答案1
需要UNIX 兼容的实现echo
才能-e<space>hello<newline>world<newline>
在那里输出。
那些不符合规定的人不符合要求。许多都不是,这意味着它几乎不可能便携式使用echo
,printf
应该使用。bash
's echo
,在它的一些(大多数)版本中,仅当您同时启用posix
和xpg_echo
选项时才符合要求。这可能是echo
您所期望的行为。
echo
GNU 附带的独立实用程序也是如此coreutils
,只有在其环境中使用$POSIXLY_CORRECT
set 调用它时才兼容(并且是足够新的版本)。
make
通常运行sh
以解释每个操作行上的命令行。
make
然而,作为一种优化,如果代码足够简单并且它认为不需要调用 shell 来解释它,则 的GNU 实现可以直接运行命令。
这就解释了为什么echo --version
给你/bin/echo
, 但echo ... > file
需要一个 shell 来执行重定向。
您可以使用strace -fe execve make
来查看make
执行的内容(如果不是 Linux,则在您的系统上使用truss
/ ... 等效项)。tusc
在这里,虽然您的/bin/echo
不兼容,但您的sh
内置echo
是兼容的。
printf
如果您想扩展echo
- 样式转义序列,请在此处使用:
printf '%b\n' 'hello\nworld'
在其格式参数,理解 C 风格的转义序列(与 (echo) 与(C) 八进制序列printf
的回声风格的转义序列有区别)\0xxx
\xxx
printf 'hello\nworld\n'
在这里,你还可以这样做:
printf '%s\n' hello world
这是在单独的行上输出多个参数的常见且可移植的方法。
另一种方法是添加:
SHELL = bash
到您的Makefile
formake
来调用bash
(假设它已安装并在 中找到$PATH
)而不是sh
解释命令行。或者调用make
为make <target> SHELL=bash
.
这不一定会使其更加可移植,因为bash
现在有越来越多的系统默认安装,但有些系统(例如在 Solaris 上)的bash
构建使其echo
内置默认情况下以标准方式运行。
设置SHELL
为除了之外的任何值也会产生禁用上述 GNU 优化/bin/sh
的副作用,因此使用or ,您将在两次调用之间获得一致的行为,同时仍然不必添加对 bash 的依赖项。make
make <target> SHELL=sh
make <target> SHELL=/bin//sh