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

为什么 zsh printf 不遵循八进制表示法?
sh -c 'printf "%d " 024'
bash -c 'printf "%d " 024'
zsh -c 'printf "%d" 024'

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

答案1

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来切换模拟模式sh,也可以运行emulate sh -c 'some code'以在模拟some code中解释。sh

所以,在 内zsh

emulate sh -c 'printf "%d\n" 024'

printf将以更 POSIX 的方式运行。

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

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

在里面V9 中的原始实现printf识别与 C 语言中相同的数字( dec -123+123、 Octal 0123、 hex 0x123、 float 1.2e-2、 Even'x'"foo"(其中它是使用的第一个字符的值))。

SVR4(V7 的后代)也有一个非常愚蠢的printf实用程序,它只是执行以下操作:

        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]);

%s因此它仅对-type 格式化有用,因为printf()仅给出了指向字符串参数的指针。

POSIX.2(在 80 年代末闭门编写并于 1992 年发布(当时还不能免费使用))指定了一个printf命令。由于该echo实用程序非常不便于携带且不可靠,因此非常需要一种替代方案。由于某种原因,他们没有使用kshprint内置函数,而是指定了一个printf主要基于 V9 实现的实用程序,并在将字符串转换为数字时参考了 C 语言。

ksh88,POSIX 规范所基于的 shellsh从来没有printf内置的(它的 pdksh 克隆也没有)。它有自己的print内置函数作为echo. ksh93 中添加了-f的选项以及printprintf别名print -f

在 ksh 中,大多数以数字作为输入的内置函数或构造都将接受任何算术表达式,而不仅仅是数字常量。所以在 ksh93 中

printf '%d\n' '1 + 1'

会打印2.

在早期版本中,printf '%d\n' 010会打印 10,而不是 8。

在 ksh shell 算术表达式中,最初,带有前导 0 的数字不被视为八进制,因为在 shell 中,处理以 0 填充的十进制数字(例如日期/时间、文件名)比处理以 0 填充的十进制数字更常见。八进制数。

然而,POSIX 规范确实要求将它们视为八进制,而大多数 shell 都忽略了这一点(因为标准所基于的 shell 的原始实现并未这样做)。然而之后PASC 解释请求这更清楚地表明,大多数 shell 开始改变它们的行为(包括 ksh93、并且zsh,尽管这已被恢复),造成很大的痛苦。

如果你看一下ksh93 的发布历史,您会注意到以 0 为前缀的数字作为八进制的处理已经打开和关闭。即使在今天,您也会发现算术表达式中的 010 在((...))or内部是 8 $((...)),但在大多数其他地方(包括let '...'array[...][[ ... -eq ... ]]...)是 10 printfset -o posix使得大部分地方变成8,一个值得注意的例外是……printf再次

由于 zsh 从未声称自己是 POSIX shell,它只是添加了一个新选项(octal_zeroes )2000 年,在sh仿真中启用,但在其他方面则不然。

Aprintf2001 年晚些时候添加了最初使用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仿真中)。

答案2

用于command printf ...运行标准 printf 实用程序。 zsh 的 printf 有自己的参数解释,与 POSIX 标准冲突,%d 将参数解释为算术表达式,因此您可以使用printf %d '8#24'.

答案3

默认情况下,zsh 不符合 POSIX 标准,而且偏差也不小。

这是使用 zsh 的常见问题。

尝试:

zsh --emulate sh -c 'printf "%d\n" 024'

它将打印 20

但请注意,这仅适用于最新zsh版本。

如果您喜欢一种甚至适用于旧zsh版本的方法,那么有一种方法可以做到这一点。创建一个$HOME/.zshenv包含以下内容的文件:

if [ -n "$ZSH_EMULATION" ]; then
    emulate "$ZSH_EMULATION"
fi

如果您有该文件,您可以调用:

ZSH_EMULATION=sh zsh -c 'printf "%d\n" 024'

这适用于任何版本的zsh.

BTW:当前相关文本ZSH_EMULATION已经在评论中讨论后引入。

相关内容