printf“\x41\n”得输出|Makefile中的cat与shell中的输出不同

hsgswve4  于 2022-11-16  发布在  Shell
关注(0)|答案(3)|浏览(164)

我正在Linux上使用GNU Make。这是我的Makefile。

foo:
    printf '\x41\n'

bar:
    printf '\x41\n' | cat

输出如下:

$ make foo
printf '\x41\n'
A
$ make bar
printf '\x41\n' | cat
\x41

使用的shell是Dash:

# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Sep 12 04:41 /bin/sh -> dash

为什么当我通过cat进行管道传输时,输出会变得不同?
奇怪的是,当我直接在dash上执行命令时,它本身的行为会有所不同。

$ printf '\x41\n'
\x41
$ printf '\x41\n' | cat
\x41

这是怎么回事?为什么Makefile中的命令输出与Dash中的不一致?

xoshrz7s

xoshrz7s1#

foo 情况下,运行的是外部printf可执行文件,而不是shell builtin(请参阅此答案https://unix.stackexchange.com/questions/202302/variable-definition-in-bash-using-the-local-keyword/202326#202326,它解释了shell builtin和外部可执行文件之间的区别),正如您在strace:中看到的那样:

$ strace -f make
(...)
[pid  1396] execve("/usr/bin/printf", ["printf", "\\x41\\n"], 0x56453618b880 /* 14 vars */ <unfinished ...>

因此,即使使用了破折号,也会打印A,因为printf是coreutils的一部分,它理解\x序列:

$ dash
$ /usr/bin/printf '\x41\n'
A

bar 情况下,如您在strace中所见,make runs dash是内置的:

$ strace -f make bar
(...)
[pid  1414] execve("/bin/sh", ["/bin/sh", "-c", "printf '\\x41\\n' | cat"], 0x55835836a8e0 /* 14 vars */ <unfinished ...>
eiee3dmh

eiee3dmh2#

您看到不同行为的原因是形式\xNN没有在POSIX标准中定义printf。该标准仅使用\oNNN定义八进制中指定的特殊字符的行为。它不支持十六进制形式。
看到不同行为的原因在于,在简单的情况下:

foo:
        printf '\x41\n'

make不需要调用shell:它可以直接运行命令,调用系统上的/usr/bin/printf程序。除了POSIX所要求的功能外,该程序还有额外的功能,它可以处理\xNN字符。
在更复杂的情况下:

bar:
        printf '\x41\n' | cat

这需要make来调用shell,因为make不能处理管道等。当您调用shell时,它使用的是shell的内置printf,而不是单独的程序/usr/bin/printf。(在大多数情况下),它的printf内置函数不能处理非标准的\xNN字符。
简短回答:坚持使用POSIX定义的行为,它将一直工作。

yh2wf1be

yh2wf1be3#

让我们使用一行语句总结其他答案的结果,其中的输出提供了所有必要的证据,以了解:

  • 坚持字符的八进制表示不会出现使用和不使用管道cat时得到不同结果的问题
  • makeprintf未通过管道传输的情况下运行系统printf,并且带有printf的shell通过管道传输到cat。除了printf的POSIX标准仅使用\NNN定义了八进制表示法中指定的特殊字符的行为(但不支持\xNN)之外,您看到不同行为的核心原因是什么?

在提供的命令行的帮助下,任何人现在都可以尝试在自己的系统上重现该问题,副作用是查看make使用了哪个shell:
$ cat生成文件;回声-----------; stace-o stace.过时;回声-----------;猫爬出|grep '打印'| grep -v '枚举'
(don“别忘了把输出的第一部分存储在一个名为"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 '\x41\n'
pXAcat:
    printf '\x41\n' | cat
poA:
    printf '\101\n'
poAcat:
    printf '\101\n' | cat
-----------
printf '\x41\n'
A
printf '\x41\n' | cat
\x41
printf '\101\n'
A
printf '\101\n' | 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' | cat\n", 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' | cat\n", 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 '\x41\n'
pXAcat:
    printf '\x41\n' | cat
poA:
    printf '\101\n'
poAcat:
    printf '\101\n' | cat

如果您的make没有给予与上面相同的结果,请将其调整为make all,以获得所有输出。
P.S.请注意,"Makefile"printf行的缩进必须使用制表符,而不是空格(因此,请用制表符替换空格,或从编辑和非查看文本框中复制脚本)。
P.S. P.S.正如您在strace的输出中所看到的,在我的系统上运行make所调用的shell是sh,而系统Terminal的标准shell是bash。因此,与在终端中运行它们相比,在make中运行的“相同”命令在行为上可能有所不同。由于这很容易导致混淆,因此,在使用命令行运行命令或可执行文件时,通常最好充分意识到,如果由于存在多个shell或不同的命令具有相同的名称。而且,充分意识到这样一个事实也是一个好主意,即在一个给定的shell中运行完全相同的程序,如果在另一个shell中运行或没有连接到shell,则是不完全相同的事情。

相关问题