Work the Shell - Solve:命令行计算器

作者:Dave Taylor

关于 Linux 和之前的 UNIX,一直困扰我的一件事是,没有一个像样的命令行计算器可用。你知道,就是那种你可以输入solve 5+8或者,更好的是,solve 33/5并得到答案的东西。

有 expr,但它几乎没什么用,我一直感到困惑的是,直到今天它还局限于整数数学。从来没有人将其功能扩展到 shell 脚本编程最基本的功能之外。

有 bc,它具有我们所需的功能,但它肯定是 Linux 全系列程序中最奇怪的界面之一,没有什么比意外地陷入 bc 并且无法退出更令人沮丧的了!

第三个选择是 dc,所谓的桌面计算器(真的,这就是 dc 的意思),但它也从根本上存在缺陷,因为它使用 RPN(逆波兰表示法——真的,它是以波兰数学家扬·武卡谢维奇的名字命名的)。不确定那是什么?好吧,这里有一个它不起作用的演示

$ dc 1+1
Could not open file 1+1: No such file or directory
$ dc -e 1+1
dc: stack empty
$ dc -e 1 + 1
Could not open file +: No such file or directory

在如此基本的数学任务上感到厌倦,你真的关心学习一种全新的表示法来计算 1+1=3 吗?不,是 2 吗?

在这三个选择中,没有一个能满足要求,但 bc 确实显示出希望,因为它可以处理浮点数,并且能够指定你想要的十进制后精度。学习它晦涩难懂的表示法,你就可以计算 1+1

$ bc
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
1+1
2
quit
$  	

bc 的挑战在于改进你与它的交互方式——在实用程序“前面”放置一个包装程序,以便你可以将其用作快速而简陋的命令行实用程序。

按照设计那样使用它有两个问题,正如你在这里看到的

$ bc 1+1
File 1+1 is unavailable.

以及这里

$ bc -q
3/2
1
quit
$

(-q 选项去除了 FSF 介绍性标头。)

默认情况下,bc 只提供整数结果,因此,尽管你我都明白 3/2 = 1.5,但 bc 将其显示为 1,这使得它对于任何精度计算都毫无用处。

但是,与其他计算替代方案不同,bc 确实有能力更精确一些。关键是你必须指定刻度,即你想看到的十进制小数点后的位数。添加它,事情就会改变

$ bc -q
11/7
1
scale=8
11/7
1.57142857
quit
$

我们面临的挑战是找到一种编写 shell 脚本包装器的方法,该包装器不仅允许我们从命令行进行简单的计算,而且还可以将它们作为浮点计算来解决。目标是能够输入类似solve 11/7,并使其显示 1.57142857。

关于包装器和说唱歌手

在这一点上,考虑到我的标题,我有一种想写一些押韵俚语的冲动,但我知道我的编辑不会让我得逞,所以你是安全的。尽管如此,包装器是一个重要的概念,也是 Linux 作为操作系统如此强大的重要原因之一。

在许多方面,UNIX 和 Linux 提供了你所需的所有工具、基本的构建块,而 shell 脚本编程的目的之一是添加外观,即可用用户界面的愉悦感。如果你仔细想想,这正是我们用 solve 脚本所做的事情。实际上,在 shell 脚本中进行数学运算将非常棘手,但我们当然可以将一个简单的查询转换为更复杂的命令序列,从而使 bc 输出我们想要的内容。

然而,挑战在于我们不仅仅是添加一个命令标志或转换一个表达式;我们需要捕获请求的公式,并将其注入到我们通过标准输入 (stdin) 馈送到底层 Linux 实用程序的命令序列中。

我通过使用所谓的“此处文档”来做到这一点,它在脚本中用 << 符号表示。回想一下,像这样的表示法wc < letter.txt调用 wc 命令,并将 letter.txt 的内容用作命令的 stdin。结果是文件中的字符数、单词数和行数,就像我实际上逐字逐句地输入了文件一样。

<< 表示法是一种方便的方式,可以对调用命令的标准输入进行类似的重新映射,但基于实际存在于命令序列中的材料,而不是单独的文件。

因此,紧跟在 << 符号后面的字符序列是结束标记,而不是文件名。它的工作原理如下

cat << EndOfInput
This is a sample of the kind of 
trick you can do with a here document. 
Why is this cool?  Because you can also 
expand variables ($PATH) 
and do other spiffo shell script 
hijinks.
EndOfInput

运行这个小脚本片段(作为一个脚本),你将看到以下内容

$ sh samplepscript.sh
This is a sample of the kind of 
trick you can do with a here document. 
Why is this cool?  Because you can also 
expand variables (/bin:/sbin:/usr/bin:/usr/sbin) 
and do other spiffo shell script 
hijinks.

在我们的例子中,这也意味着你可以将命令行参数移动到发送到核心 Linux 命令(如 bc)的命令序列的中间。例如

#!/bin/sh

bc << EOF
scale=4
$@
quit
EOF

信不信由你,这就是我们编写具有浮点功能的命令行计算器的挑战的初步解决方案。查看一下

$ sh solve.sh 1+1
2
$ sh solve.sh 11/7
1.5714

下个月,我们将深入研究有用的改进,并使其成为我们 Linux 工具包的完整补充。到时见!

Dave Taylor 是 UNIX 领域的 26 年资深人士,The Elm Mail System 的创建者,以及最近畅销书 Wicked Cool Shell ScriptsTeach Yourself Unix in 24 Hours 的作者,以及其他 16 本技术书籍。他的主要网站是 www.intuitive.com,他还提供技术支持,网址为 AskDaveTaylor.com

加载 Disqus 评论