为什么 zsh printf 不遵循八进制表示法?

Nic*_*ull 1 zsh printf

sh -c 'printf "%d " 024'
bash -c 'printf "%d " 024'
zsh -c 'printf "%d" 024'
Run Code Online (Sandbox Code Playgroud)

以上输出20 20 24。为什么 zsh 不尊重八进制表示法?有办法改变这个吗?

Sté*_*las 5

zsh不是 POSIX shell,它是在 POSIX 规范sh发布之前编写的,采用了 ksh、csh、tcsh、rc 的功能和语法,并添加了许多自己的功能和语法。它的语法在很大程度上类似于 Korn,但这并不意味着它与 Korn shell 完全兼容。无论如何,它不是也从来没有打算成为 sh 语言的 POSIX 兼容解释器(至少在默认模式下不是)。

然而,它有许多模拟模式(csh、ksh 和 sh(以前尝试遵循 SysV sh,现在遵循 POSIX sh)),可用于提高与其他 shell 的兼容性并解释为它们编写的代码。这意味着它可以拥有独立于 sh 或任何其他 shell(如 csh、rc、fish、perl、python...)的自己的语法,并且仍然能够解释为 sh 编写的代码。

如果以shcsh、 或(或以(或对于 Bourne)或)ksh开头的任何内容调用,或者在较新版本中以,它将进入相应的模拟模式,但这不是您通常使用它们的方式。要解释脚本,您需要运行,而不是。sbck--emulate sh/ksh/cshshshzsh

当您想要在 zsh 中解释 sh 代码时,您宁愿运行emulate shinsidezsh来切换模拟模式,也可以运行以在模拟中解释。shemulate sh -c 'some code'some codesh

所以,在zsh

emulate sh -c 'printf "%d\n" 024'
Run Code Online (Sandbox Code Playgroud)

将以printf更 POSIX 的方式运行。

至于printf,大约 1986 年,一个printf实用程序首次出现在 Research Unix 第九版(来自 AT&T Research / Bell Labs,未广泛使用)中,用于通过printflibc 函数获得与 C 中可用的相同文本格式化 API。

虽然它C采用格式第一个参数作为指向 NUL 分隔的字节数组和不同类型(指针、整数、双精度……)的额外参数的指针,但可执行文件只能将参数作为字符串。因此,要格式化数字(例如 )%d,需要为其提供数字的文本表示形式,并将其转换回二进制整数,然后将printf其转换回文本以进行输出。

In the original implementation in V9, printf recognised the same numbers as in the C language (dec -123, +123, octal 0123, hex 0x123, float 1.2e-2, even 'x' or "foo" (where it's the value of the first character that is used)).

SVR4 (descended from V7) also had a very dumb printf utility that just did:

        printf(fmt, argv[2], argv[3], argv[4],  argv[5],
                        argv[6], argv[7], argv[8], argv[9],
                        argv[10], argv[11], argv[12], argv[13],
                        argv[14], argv[15], argv[16], argv[17],
                        argv[18], argv[19], argv[20]);
Run Code Online (Sandbox Code Playgroud)

So it was only useful for %s-type formatting as printf() was only given pointers to the string arguments.

POSIX.2 (written in the late 80s behind (mostly-)closed doors and released in 1992 (not freely available then)), specified a printf command. As the echo utility was very unportable and unreliable, an alternative was much needed. For some reason, they didn't go for ksh's print builtin but specified a printf utility mostly based on the V9 implementation and with a reference to the C language when it comes to convert strings to numbers.

ksh88, the shell on which the POSIX sh specification is based on never had a printf builtin (nor did its pdksh clone). It has its own print builtin as a reliable alternative to echo. A -f option for print was added in ksh93 though along with a printf alias for print -f.

In ksh, most builtins or constructs that take numbers as input will accept any arithmetic expression, not just numeric constants. So in ksh93

printf '%d\n' '1 + 1'
Run Code Online (Sandbox Code Playgroud)

Would print 2.

And in early versions, printf '%d\n' 010 would print 10, not 8.

In ksh shell arithmetic expressions, initially, numbers with leading 0s were not considered as octal because in a shell, it's much more common to deal with 0-padded decimal numbers (like in date/time, file names) than it is to deal with octal numbers.

However the POSIX specification did sort of require those to be treated as octal which most shells ignored (as the original implementation of the shell on which the standard was based didn't). However after that PASC Interpretation Request which made it clearer, most shells started to switch their behaviour (including ksh93, and zsh, though that was reverted), causing much pain.

If you look at the release history of ksh93, you'll notice that the handling of 0-prefixed numbers as octal has been on and off. Even today, you'll find that 010 in arithmetic expressions is 8 inside ((...)) or $((...)), but 10 in most other places, including let '...', array[...], [[ ... -eq ... ]] and... printf. set -o posix makes it change to 8 in most places, one notable exception being... printf again.

Since zsh never claimed to be a POSIX shell, it just added a new option (octal_zeroes) in 2000, enabled in sh emulation, but not otherwise.

printf2001 年晚些时候添加了一个内置函数,最初用于strtod()将文本转换为数字,因此将前导 0 的数字视为八进制,但后来更改为接受像 ksh93 中的任何算术表达式,因此受选项限制octal_zeroes,并允许更多数字格式,例如0b10010、 ksh -样式任意基数字 ( 12#A001, 2#10010...), 1_000_000...

因此,在 中zsh,要将八进制数传递给printf,选项是:

  • 模拟sh并使用前导 0:emulate sh -c 'printf %d 024'
  • 设置octalzeroes选项:set -o octalzeroes; printf %d 024
  • 仅在本地范围内相同:((){ set -o localoptions -o octalzeroes; printf %d 024; }此处在匿名函数中)。
  • 使用8#oooo始终被认可的符号:printf %d '8#24'
  • 禁用printf内置函数,并假设系统的printf实用程序在这方面符合 POSIX:disable printf; printf %d 024
  • 调用独立的其他方法printf:(command printf %d 024不在sh仿真中),=printf %d 024(不在sh仿真中)。