打印'x41n'的输出 |Makefile 中的 cat 与 shell 中的输出不同



我在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,则不完全是相同的事情,这也是一个好主意。

相关内容

  • 没有找到相关文章