一步步打造自己的linux命令行计算器

相信很多人,在工作中会需要使用到计算器。一般的做法是,打开并使用系统自带的计算器。

这种做法可能对我来说,有如下几个问题。

  • 太慢。每次需要打开计算器,然后改成编程模式,手工选择进制,再使用输入表达式进行计算。
  • 需要切换窗口。编程时经常是在终端中,使用GUI计算器则意味着要离开终端,计算完毕再切换回来。
  • 无法使用混合进制表达式。混合进制的意思是,在一个表达式中同时使用多种进制,如“0x10 * 10”表示十六进制的0x10乘以十进制的10。

如果以上有一条你也有同感的话,那么你也应该试一下,使用命令行计算器。

命令行计算器,调用bc

只需经过简单的搜索,便可以了解到,linux中原生提供了一个命令行计算器 GNU bc。

GNU bc支持高精度数字和多种数值类型(例如二进制、十进制、十六进制)的输入输出。

bc的交互式使用方式,运行bc,进入交互模式。在交互模式中输入表达式,回车即可获得结果。需要退出时输入quit退出即可。

bc的非交互式使用方式,通过管道将表达式传入。

使用效果如下

代码语言:javascript
复制
zhuangqiubin@zhuangqiubin-PC:~$ bc
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
1+2
3
quit
zhuangqiubin@zhuangqiubin-PC:~$ echo "1+2" | bc
3

OK,get到了命令行计算器的新技能了,但每次进入交互模式或者手工输入“echo 表达式 | bc ”都感觉略麻烦。那这个时候,就需要脚本,写个mycalc.sh好了

代码语言:javascript
复制
zhuangqiubin@zhuangqiubin-PC:~$ cat mycalc.sh
#!/bin/bash

echo "$@" | bc
zhuangqiubin@zhuangqiubin-PC:~$ ./mycalc.sh 1+2
3

再把mycalc.sh拷贝到可访问的目录下,如

代码语言:javascript
复制
sudo mv mycalc.sh /usr/bin

对于没有sudo权限的情况,那也可以变通下

代码语言:javascript
复制
mkdir -p ~/usr/bin

mv mycalc.sh ~/usr/bin

echo 'export PATH=HOME/usr/bin:PATH' >> ~/.bashrc

source /.bashrc

再alias一个顺手的命令名,比如拼音jisuan

代码语言:javascript
复制
 echo "alias jisuan='mycalc.sh'" >> /.bashrc

更多bc的用法,可以通过man bc查看,网上也有许多介绍资料。

解决进制问题

bc仍然需要手工指定进制,在表达式前,使用ibase参数和obase参数指定输入输出的进制。并且不支持混合进制,因为ibase每次只能指定一种进制。

代码语言:javascript
复制
zhuangqiubin@zhuangqiubin-PC:$ echo "10+10" | bc
20
zhuangqiubin@zhuangqiubin-PC:$ echo "ibase=16;10+10" | bc
32

但我们既然已经有了一个包装脚本mycalc.sh,那是不是可以把进制转换的工作交给它呢,当然可以。

我们可以让mycalc.sh先处理下表达式中的数字,约定0x开头为十六进制,不带前缀为十进制,0o开头为八进制,0b开头为二进制。

mycalc先将所有参数转换成统一的进制,如十进制,然后计算表达式的值,最终将结果再以多种进制的形式输出。这样我们就不同手工处理进制问题了。

至于输出,为了方便起见,可以多种进制一起输出,需要哪个用哪个即可

这里就不贴代码了,有兴趣可移步github https://github.com/zqb-all/smartbc,我们接着往下看,后面有更简单的方式。

使用示例

代码语言:javascript
复制
zhuangqiubin@zhuangqiubin-PC:$ type jisuan
jisuan 是 ~/mywork/mygithub/smartbc/smartbc&#39; 的别名 zhuangqiubin@zhuangqiubin-PC:~$ jisuan 10+10 Original EQUATION: 10+10 Decimal EQUATION: 10+10 base2 : 10100 base8 : 24 base10: 20 base16: 14 zhuangqiubin@zhuangqiubin-PC:~$ jisuan 10+0x10 Original EQUATION: 10+0x10 Decimal EQUATION: 10+16 base2 : 11010 base8 : 32 base10: 26 base16: 1A</code></pre></div></div><h3 id="e5p2d" name="%E6%9B%B4%E5%A5%BD%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%8C%E4%BD%BF%E7%94%A8python">更好的实现,使用python</h3><p>以上基于bc的计算器,已经可以满足我的需求了,也使用了一段时间。但其实还有更好的实现方式,使用python。</p><p>在命令行中,输入python,进入交互模式,即可像bc一样执行表达式,得到结果。更棒的是,原生支持混合进制,不需要自己写代码预处理表达式了。简单可靠。</p><p>代码及使用示例</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">zhuangqiubin@zhuangqiubin-PC:~$ type jisuan jisuan 是
/.pycalc.py' 的别名
zhuangqiubin@zhuangqiubin-PC:~$ cat ~/.pycalc.py
#!/usr/bin/env python2

import sys

equation=sys.argv[1]
result=eval(equation)
if isinstance(result, (float)):
print "Attention:only base10 is float, others change to int before type"
print "equation:",sys.argv[1]
print "base2 : ",str(bin(int(result)))
print "base8 : ",str(oct(int(result)))
print "base10: ",str((result))
print "base16: ",str(hex(int(result)))
zhuangqiubin@zhuangqiubin-PC:~$ jisuan 10+10
equation: 10+10
base2 : 0b10100
base8 : 024
base10: 20
base16: 0x14
zhuangqiubin@zhuangqiubin-PC:~$ jisuan 10+0x10
equation: 10+0x10
base2 : 0b11010
base8 : 032
base10: 26
base16: 0x1a

更多输出格式

一般,输出十六进制,十进制,二进制三种结果就足够用了。但如果有特殊需求,也可自己拓展。

比如,当需要核对寄存器,检查某个bit时,一个个去数二进制的第19位,是很费眼睛的一件事。

这个时候就需要更加直观的输出,可以一眼看到某个bit是0还是1。

那好办,给二进制加上下标好了。如下

代码

代码语言:javascript
复制
#!/usr/bin/env python2

import sys

def formatBinString(num):
result='bit: '
result_index='index: '
num_len=len(num)
if num_len > 32:
return ""
for i in num:
num_len-=1
result+=i
result+=' | '
result_index+=str(num_len).zfill(2)
result_index+='| '
return result+'\n'+result_index

equation=sys.argv[1]
result=eval(equation)
if isinstance(result, (float)):
print "Attention:only base10 is float, others change to int before type"

print "equation:",sys.argv[1]
print ""

print "base2 : ",str(bin(int(result)))
print "base8 : ",str(oct(int(result)))
print "base10: ",str((result))
print "base16: ",str(hex(int(result)))

print ""
print formatBinString(str(bin(int(result))[2:].zfill(32)))

效果

代码语言:javascript
复制
zhuangqiubin@zhuangqiubin-PC:~$ jisuan 10+0x10
equation: 10+0x10

base2 : 0b11010
base8 : 032
base10: 26
base16: 0x1a

bit: 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 |
index: 31| 30| 29| 28| 27| 26| 25| 24| 23| 22| 21| 20| 19| 18| 17| 16| 15| 14| 13| 12| 11| 10| 09| 08| 07| 06| 05| 04| 03| 02| 01| 00|