shell 的一个“特性”是将带有前导零的数字解释为八进制数:
$ echo "$((00100))"
64
但在许多 shell 中无法禁止此“功能”,因此,强制将数字序列解释为十进制(或其他基数)数字变得很困难。
当只有一个数字需要转换时,有几个外部程序可以进行修剪:
expr "00100" + 0
echo "00100" | sed 's/^0*//'
echo "00100" | grep -o '[^0].*$'
echo "00100" | awk '{print int($0)}'
echo "00100" | perl -pe '$_=int."\n";'
但每次需要它们时都需要一些时间来执行它们。在多次调用中累积使用此类外部工具,延迟会变得相当大。只是为了测量造成的延迟,重复调用 1000 次以上,您将得到(以秒为单位):
expr 1.934
sed 3.450
grep 3.775
awk 5.291
perl 5.064
当然(expr 除外)大多数工具都可以处理 1000 行的文件:
sed file 0.004
grep file 0.003
awk file 0.007
perl file 0.006
如果所有单独的 1000 个值在同一时间点都可用。
事实并非如此。那么,还有待回答的是:
是否有一种本机(对于 shell)提取整数的方法比为每个单独的整数(而不是文件中的列表)调用外部工具更快?
每次调用都会累积,延迟将变得很重要。
如果号码也可能有前导符号并且您想要拒绝无效号码,则处理会变得更加复杂。
答案1
请注意,尽管$((010))
POSIX 要求扩展为 8,但某些 shell 默认情况下不会执行此操作(或仅在某些上下文中),除非处于一致性模式,因为这是一个特征你通常不想要。
使用 时zsh
,它由选项控制octalzeroes
(默认情况下关闭,除了 sh/ksh 模拟)。
$ zsh -c 'echo $((010))'
10
$ zsh -o octalzeroes -c 'echo $((010))'
8
$ (exec -a sh zsh -c 'echo "$((010))"')
8
在 中mksh
,这由posix
选项控制(默认情况下关闭):
$ mksh -c 'echo "$((010))"'
10
$ mksh -o posix -c 'echo "$((010))"'
8
在 bash 中,没有选项可以将其关闭,但您可以使用$((10#010))
ksh 语法强制以十进制解释(在 ksh 和 zsh 中也适用),但在bash
和mksh -o posix
中$((10#-010))
不起作用(正如10#0 - 010
您从扩展中看到的那样)的$((-10#-010))
屈服-8
),你需要$((-10#010))
(或者为了与抱怨是无效基础$((- 10#010))
的兼容性)。zsh
-10
$ bash -c 'echo "$((10#010))"'
10
与ksh93
,比较:
$ ksh93 -c 'echo "$((010))"'
8
$ ksh93 -c '((a = 010)); echo "$a"'
8
和:
$ ksh93 -c 'a=010; echo "$((a))"'
10
$ ksh93 -c 'printf "%d\n" 010'
10
$ ksh93 -c 'let a=010; echo "$a"'
10
$ ksh93 -c 'echo "$((010e0))"'
10
$ ksh93 -o letoctal -c 'let a=010; echo "$a"'
8
因此,至少如果您专门为这些 shell 中的任何一个进行编码,那么有一些方法可以解决该“错误功能”。
但是,在编写 POSIX 可移植脚本时,这些都没有帮助,在这种情况下,您需要删除前导零,如您所示。
答案2
类似的事情可以在一行中完成:
$ a=-00100; a=${a%"${a#[+-]}"}${a#"${a%%[!0+-]*}"}; a=${a:-0}
$ echo "$a"
-100
1000 次重复仅需 0.0482,比使用外部程序少 100 倍。
这是基于两个双参数扩展:
- 提取符号:
${a#[+-]}
删除第一个字符(如果它是一个符号)。${a%"${a#[+-]}"}
保留第一个标志,前提是它是一个标志。
- 删除所有前导符号和/或零:
${a%%[!0+-]*}
删除从任意位置(不是 0 或 + 或 - )开始到结束的位置。${a#"${a%%[!0+-]*}"}
删除上述内容,即所有前导零和符号。
这会选择一个符号并删除所有前导零。但是它允许(没有错误):
- 几个领先的迹象。
- 前导符号和零之后的任何字符。
- “超出范围”(太大)的数字。
如果需要这些测试,请继续阅读。
可以用以下方法测试标志的数量:
signs=${a%%[!+-]*}
[ ${#signs} -gt 1 ] && echo "$0: Invalid number $a: Too many signs"
可以使用以下命令检查允许的字符类型:
num=${a#"${a%%[!0+-]*}"}
any=${num%%[!0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@_]*}
[ "$any" != "$num" ] && echo "$0: Invalid number $a"
hex=${num%%[!0123456789abcdefABCDEF]*}
[ "$hex" != "$num" ] && echo "$0: Invalid hexadecimal number $a"
dec=${num%%[!0123456789]*}
[ "$dec" != "$num" ] && echo "$0: Invalid decimal number $a"
最后,我们可以利用printf
打印“超出范围”数字警告的能力(仅适用于 printf 理解的基数):
printf '%d' $sign$dec >/dev/null # for a decimal number
printf '%d' "${sign}0x$hex" >/dev/null # for hex numbers
是的,所有 printf 都使用%d
,这不是拼写错误。
是的,以上所有内容都适用于大多数具有printf
.
答案3
这是我的系统上的 x1000 示例:
$ cat shell.sh
#!/bin/dash
q=1
while [ "$q" -le 1000 ]
do
z=-00100
z=${z%"${z#[+-]}"}${z#"${z%%[!0+-]*}"}
z=${z:-0}
echo "$z"
q=$((q + 1))
done
结果:
$ time ./shell.sh >/dev/null
real 0m0.047s
现在我对 sed 示例有疑问。我确实看到了一个带有文件的示例,但我没有看到为什么使用文件不可接受的明确原因。另外,使用管道的示例也是有问题的,因为不需要管道 - 也不需要调用 sed 1000 次。如果您出于某种原因无法使用文件 - 此处文档就可以了:
cat > sed.sh <<alfa
sed 's/^0*//' <<bravo
$(yes 00100 | head -1000)
bravo
alfa
结果:
$ time ./sed.sh >/dev/null
real 0m0.047s
所以在我的系统上,速度完全相同,没有什么大惊小怪的。