在 zsh 中舍入/截断字符串中的数字(或使用外部工具)

在 zsh 中舍入/截断字符串中的数字(或使用外部工具)

我正在尝试做一个界面,bc以便可以直观地使用它,而不会“卡在”其中而烦恼。我没有时间对其进行太多测试,因为我陷入了另一个细节,即如何呈现结果(我认为是一个字符串)。

四舍五入或截断并不重要,两者都可以。看看下面,你立刻就明白了。我使用 zsh,但外部工具就可以了,因为我不会在任何时候或其他关键上下文中使用它,它只是一个桌面工具。

calc () {
    result=`bc <<EOF
    scale=3;
    $@
    EOF`
    echo ${result//%0/} # doesn't work; will only remove one zero
                        # also, if there are only zeroes, should
                        # remove dot as well - what about .333, etc.?
}

编辑

下面的解决方案给我留下了深刻的印象,尤其是如何noglob摆脱引号!

但是,使用点来强制浮点计算是我永远不会记得的事情(你不使用这样的普通计算器)。它甚至有点冒险,特别是对于浮点会产生完全不同的结果(很可能是您想要的结果)并不明显的计算而言。

另外,下面的计算显示了一些不漂亮的输出(太长的实数和尾随点)。

也许我应该将这个(其中一些)与@Gille 的输出格式结合起来回答以下?当我让它完美工作时,我会将结果发布在这里。 (编辑:所接受的回答效果很好。请务必阅读该答案的评论。)

calc () {
  echo $(($*));
}
alias calc='noglob calc'

calc 1./3
0.33333333333333331
calc 7.5 - 2.5
5.

答案1

使用zsh自己的算术,你可以这样做:

calc() printf '%.6g\n' $(($*))
alias 'calc=noglob calc'

但这意味着您需要输入数字,以便123.将它们视为浮点并触发浮点计算。

.您可以通过附加任何不属于十六进制数字(或其他基数的数字)或变量名称或类型数字的十进制数字序列来解决此问题,12e-20例如:

setopt extendedglob
calc() printf '%.6g\n' $((${*//(#bm)(([0-9.]##[eE][-+][0-9]##|[[:alnum:]_#]#[.#_[:alpha:]][[:alnum:]_#]#)|([0-9]##))/$MATCH${match[3]:+.}}))
alias 'calc=noglob calc'

到那时您可能会认为使用bc和修剪尾随 0 更容易。

也可以看看awk

calc() awk "BEGIN{print $*}"

它支持较少的运算符和数学函数,但可能对您来说足够了。

答案2

如果我理解正确的话,您想要删除尾随零和尾随点。在这种情况下,如果EXTENDED_GLOB设置了,您可以使用

${result//%.#0##/}

即:在字符串 ( ) 的末尾%匹配零个或多个点 ( .#),后跟一个或多个零 ( 0##)。

""但如果result是,这将返回0。您可以对第一个替换进行另一个替换,以将返回值恢复为0

${${result//%.#0##/}:-0}

答案3

bc可以将结果打印为长整数或小数。这是一个脚本,它将分成多行的长整数连接在一起,并删除小数中小数点后的尾随零。

calc () {
  emulate -L zsh; setopt extended_glob
  local line
  bc <<EOF |
scale=3
$@
EOF
    while read line; do
      if [[ $line = *.* ]]; then
        print -r -- ${${${line%%0##}/#%0#./0}%.}
      else
        print -r -- $line
      fi
    done
}

它的编写方式更多的是通过参数替换进行文本操作的练习,而不是漂亮打印小数的真正清晰的方式。

  • ${…%%0##}删除最长的后缀匹配0##,即尾随零。
  • ${…/#%0#./0}如果字符串仅由可选前导零 ( )0组成(#%模式的前缀) ,则将字符串设置为${VAR/PATTERN/REPLACEMENT}0#.
  • ${…%.}删除尾随.(如果有)。

我认为拆分步骤更清晰。

if [[ $line = *.* ]]; then line=${line%%0##}; fi
if [[ $line = . ]]; then line=0; else line=${line%.}
print -r -- $line

答案4

我们可以使用与 asm.js 相同的技术来完成此操作,而无需字符串操作或子进程:

echo $((123.45|0))  # prints 123

它利用了某些运算符(例如按位或)只能对整数执行的事实。

相关内容