Bash Shell创建RPN计算器

Bash Shell创建RPN计算器,你也许只用口算就能将二进制转换成十进制、八进制、十六进制,不过同样的法子对简单的算术运算似乎无能为力,而且计算器还总是在需要的时候不见踪影。这该怎么办?

解决方案

像下例中那样,用 shell 算术和 RPN6 创建一个计算器。

6RPN(Reverse Polish notation,逆波兰表示法或逆波兰记法)是一种由波兰数学家扬 • 武卡谢维奇于 1920 年引入的数学表达式方式。在逆波兰记法中,所有运算符都置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识运算符的优先级。

#!/usr/bin/env bash
# 实例文件:rpncalc
#
# 简单的RPN命令行(整数)计算器
#
# 获取用户提供的参数并计算
# 参数形式为:a b运算符
# 允许使用*x*代替*
#
# 检查参数数量:
if [ \( $# -lt 3 \) -o \( $(($# % 2)) -eq 0 \) ]
then
    echo "usage: calc number number op [ number op ] ..."
    echo "use x or '*' for multiplication"
    exit 1
fi

ANS=$(($1 ${3//x/*} $2))
shift 3
while [ $# -gt 0 ]
do
    ANS=$((ANS ${2//x/*} $1))
    shift 2
done
echo $ANS

讨论

RPN(或者后缀)风格的表示法将操作数(数字)放在前面,然后是运算符。如果使用 RPN,那么表达式应该写成 5 4 +,而不是 5 + 4。要是想将结果再乘以 2,将 2 * 写在后面即可,因此整个表达式就是 5 4 + 2 *,这种写法在解析表达式时实在是再好不过了,因为可以从左向右处理,压根不需要括号。任何运算的结果都会变成下一个子表达式的第一个操作数。

在我们这个简单的 bash 计算器中,允许用小写字母 x 代替乘号,因为 * 对于 shell 而言具有特殊含义。但如果你写成 '*'\*,也没问题。

如何检查参数错误?如果参数数量少于 3 个(我们需要 2 个操作数和 1 个运算符,如 6 3 /),那么就认为出错了。参数可以多于 3 个,但总数始终应该是奇数(一开始是 3 个参数,然后是下一个操作数和运算符,共计 2 个,以此类推,始终要再添加 2 个参数。有效的参数数量应该是 3、5、7、9……)。我们用下列语句检查参数数量,比较结果是否为 0。

$(($# % 2)) -eq 0

$(()) 用来执行 shell 算术运算。我们用 % 运算符(求余运算符)检查 $#(其中保存着参数数量)能否被 2 整除(通过 -eq 0 判断)。

$(()) 只能对其中的表达式执行整数运算。

现在我们知道了参数数量没问题,可以计算结果了。

ANS=$(($1 ${3//x/*} $2))

该语句将 * 替换成字母 x 并计算出最终结果。在命令行上调用脚本时,给出的是 RPN 表达式,但是 shell 算术运算所采用的语法还是正常的(中缀)写法。因此,必须交换一下参数位置才能在 $(()) 中求值。暂时忽略用 * 替换 x 这部分,前面的语句就变成了:

ANS=$(($1 $3 $2))

这里只是将运算符移到了两个操作数之间。在进行算术求值之前,bash 会替换掉这几个参数,因此,如果 $1 是 5、$2 是 4、$3 是 +,那么参数替换后得到的就是:

ANS=$((5 + 4))

我们将求值结果 9 赋给变量 $ANS。处理完这 3 个参数后,用 shift 3 将其丢弃并获得新的参数,然后继续进行后续处理。由于之前已经确认了有奇数个参数,因此如果还有参数要处理,至少是 2 个(只有 1 个的话,参数总量就是偶数了,因为 3+1=4)。

接下来进入 while 循环,每次处理 2 个参数。上一步的结果作为第一个操作数,下一个参数(执行完 shift 之后,现在变成了 $1)作为第二个操作数,我们将保存在 $2 中的运算符放置在两个操作数之间,然后像先前那样对表达式求值。一旦处理完所有参数,$ANS 中保存的就是最终结果。

最后再说一下替换操作。我们用 ${2} 引用第二个参数。虽然通常用不着 {},直接写成 $2 即可,但这里是为了让 bash 对该参数执行额外的操作。我们将其写成 ${2//x/*} 是想在返回 $2 的值之前先用 *(在第 3 个 / 后指明)替换掉(//)x。多加一个变量可以将这一步拆成两小步。

OP=${2//x/*}
ANS=$((ANS OP $1))

如果是刚接触 bash 的这些特性,这个额外变量有助于你加深理解,一旦熟悉了这种惯用写法,你会发现自己不由自主地就将它们写成了一行(尽管可读性不太好)。

你是不是好奇我们对表达式求值时为什么不写成 $ANS$OP?在 $(()) 中,除了位置参数(如 $1$2),变量名前面并不需要加 $。位置参数需要 $ 是为了和普通数字(如 1、2)区分开来。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!