我在Linux上使用GNU Make。这是我的Makefile。
foo:
printf 'x41n'
bar:
printf 'x41n' | cat
输出如下:
$ make foo
printf 'x41n'
A
$ make bar
printf 'x41n' | cat
x41
使用的shell是Dash:
# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Sep 12 04:41 /bin/sh -> dash
当我通过cat
管道时,为什么输出会变得不同?
奇怪的是,当我直接对dash
执行命令时,它本身有不同的行为。
$ printf 'x41n'
x41
$ printf 'x41n' | cat
x41
这是怎么回事?为什么在Makefile命令的输出与Dash不一致?
您看到不同行为的原因是xNN
的形式没有在printf
的POSIX标准中定义。该标准仅定义了以八进制指定的特殊字符的行为,使用oNNN
。它不支持十六进制格式。
看到不同行为的原因是在简单的情况下:
foo:
printf 'x41n'
make
不需要调用shell:它可以直接运行调用系统上/usr/bin/printf
程序的命令。这个程序除了POSIX要求的之外,还有额外的功能,它可以处理xNN
字符。
在更复杂的情况下:
bar:
printf 'x41n' | cat
这需要make
调用shell,因为make
不能处理管道等。当您调用shell时,它使用shell的内置printf
而不是单独的程序/usr/bin/printf
。由于您的shell是dash
,它只提供posix - compliant行为(在大多数情况下),它的printf
内置不能处理非标准的xNN
字符。
简短的回答:坚持POSIX定义的行为,它会一直工作。
在foo的情况下,运行外部printf可执行文件而不是shell内置(参见这个答案)https://unix.stackexchange.com/questions/202302/variable-definition-in-bash-using-the-local-keyword/202326#202326这就解释了shell内置和外部之间的区别如strace::
所示,可执行文件)$ strace -f make
(...)
[pid 1396] execve("/usr/bin/printf", ["printf", "\x41\n"], 0x56453618b880 /* 14 vars */ <unfinished ...>
所以即使使用了破折号A也会被打印出来,因为printf是A部分coretils可以理解x序列:
$ dash
$ /usr/bin/printf 'x41n'
A
在bar的情况下,运行内置的破折号,你可以看到strace:
$ strace -f make bar
(...)
[pid 1414] execve("/bin/sh", ["/bin/sh", "-c", "printf '\x41\n' | cat"], 0x55835836a8e0 /* 14 vars */ <unfinished ...>
让我们使用一行语句总结其他答案的结果,其中输出提供了查看该结果所需的所有证据:
- 坚持八进制字符表示不存在使用和不使用管道
cat
获得不同结果的问题 make
在没有管道printf
的情况下运行系统printf
,而printf
管道到cat
的shell是什么核心原因是你看到printf
的POSIX标准只定义了使用NNN
的八进制符号指定的特殊字符的行为(但不支持xNN
)
在提供的命令行的帮助下,现在任何人都可以尝试在自己的系统上重现这个问题,并且可以看到make使用的是哪个shell:
$ cat Makefile;回声 -----------;Strace - 0 StraceOut -f make;回声 -----------;猫strace。输出| grep 'printf' | grep -v ' enent '
(不要忘记将输出的第一部分存储在之前名为"Makefile"
的文件中):
$ cat Makefile; echo -----------; strace -o strace.out -f make; echo -----------; cat strace.out | grep 'printf' | grep -v 'ENOENT'
all: pXA pXAcat poA poAcat
pXA:
printf 'x41n'
pXAcat:
printf 'x41n' | cat
poA:
printf '101n'
poAcat:
printf '101n' | cat
-----------
printf 'x41n'
A
printf 'x41n' | cat
x41
printf '101n'
A
printf '101n' | cat
A
-----------
28434 write(1, "printf '\x41\n'n", 16) = 16
28435 execve("/usr/bin/printf", ["printf", "\x41\n"], [/* 69 vars */]) = 0
28434 write(1, "printf '\x41\n' | catn", 22) = 22
28436 execve("/bin/sh", ["/bin/sh", "-c", "printf '\x41\n' | cat"], [/* 69 vars */]) = 0
28434 write(1, "printf '\101\n'n", 16) = 16
28439 execve("/usr/bin/printf", ["printf", "\101\n"], [/* 69 vars */]) = 0
28434 write(1, "printf '\101\n' | catn", 22) = 22
28440 execve("/bin/sh", ["/bin/sh", "-c", "printf '\101\n' | cat"], [/* 69 vars */]) = 0
顺便说一下:如果你看一下"strace.out"
文件的第一行,你可以看到哪个make正在处理Makefile:
28434 execve("/usr/bin/make", ["make"], [/* 64 vars */]) = 0
运行长命令行之前要保存的"Makefile"
代码下面:
all: pXA pXAcat poA poAcat
pXA:
printf 'x41n'
pXAcat:
printf 'x41n' | cat
poA:
printf '101n'
poAcat:
printf '101n' | cat
如果您的make
没有给出与上面相同的结果,请将其调整为make all
以获得所有输出。
注:请注意,"Makefile"
中printf
行的缩进必须使用制表符而不是空格(因此用制表符替换空格或从编辑中复制脚本而不是查看文本框)。
注:附注:正如您在strace
的输出中看到的那样,在我的系统上运行make
所调用的shell是sh
,而Terminal
系统的标准shell是bash
。因此,与在终端中运行相比,在make
中运行的"相同"命令在行为上可能有所不同。由于这很容易导致混淆,所以在使用命令行运行命令或可执行文件时,最好充分意识到,如果在另一个上下文中运行同一命令或可执行文件,由于存在多个shell或具有相同名称的不同命令,其结果可能与使用相同的命令或可执行文件所获得的结果不同。而且,充分认识到在一个给定的shell中运行完全相同的程序,如果在另一个shell中运行或没有连接到shell,则不完全是相同的事情,这也是一个好主意。