1 引言
这篇博客将介绍如何使用 Bash 脚本实现一个功能全面的计算器,支持浮点数加减乘除以及阶乘计算。
本计算器脚本特别之处在于,它同时支持两种使用模式:交互模式和命令行模式。用户可以在终端中直接输入计算表达式进行一次性计算,或者进入交互式环境连续进行多次计算。同时,脚本允许通过命令行选项设置浮点数的计算精度,提升计算的灵活性和可用性。
本文将详细分析该脚本的实现过程,涵盖其核心功能、命令行参数的处理、错误处理机制等,并在最后附上完整源码,供读者学习与参考。
2 项目概述
本项目实现了一个基于 Bash 脚本的简单计算器,支持基本的四则运算(加、减、乘、除)以及阶乘计算。该计算器具有两种工作模式:交互模式和命令行模式。交互模式允许用户在程序运行时输入多个计算式,适用于快速连续的计算;命令行模式则允许用户通过传递参数一次性进行计算,适合批量处理任务。
2.1 主要功能:
- 四则运算:支持加法、减法、乘法、除法的基本运算。
- 阶乘计算:提供递归和循环两种方式计算阶乘,适用于非负整数。
- 小数精度设置:通过命令行参数设置计算结果的小数精度,默认为两位小数。
- 错误处理:包括无效的数值输入、无效的运算符、阶乘输入限制等。
2.2 使用模式:
- 交互模式:如果不传入任何命令行参数,用户可以进入交互模式,实时输入计算式并获得结果。输入 “q” 退出程序。
- 命令行模式:通过命令行参数传递计算式和选项进行一次性计算,支持设置精度和选择运算符。
本脚本通过解析命令行选项(如 -h
查看帮助文档、-s
设置精度),使得用户能够灵活地使用计算器进行各种计算任务。接下来,我们将详细介绍脚本的各个组成部分和实现原理。
3 功能细节
3.1 显示帮助文档 (show_help
函数)
在计算器脚本中,帮助文档的功能是为了引导用户如何正确使用脚本。用户可以通过 -h
参数或在交互模式下查看帮助,了解支持的运算符、如何输入计算式、如何设置小数精度等。以下是该函数的具体内容和功能:
show_help() {
echo "简单计算器 支持浮点数加、减、乘、除和阶乘 by DuChuan"
echo "支持的运算符:+ - \"*\" / !(乘法请用引号包裹\"*\")"
echo ""
echo "交互模式:"
echo " 如果不传入任何参数,进入交互模式,您可以连续进行计算。"
echo " 输入计算式(num1 运算符 num2)进行计算,输入 'q' 退出。"
echo ""
echo "命令行模式:"
echo " 直接传入 num1、运算符和 num2 进行一次性计算。"
echo " 例如:bash calculator.sh -s 3 3 + 5"
echo ""
echo "其他帮助:"
echo " -h 显示帮助文档。"
echo " '!' 用于计算阶乘,例如:bash calculator.sh 5 !"
echo " -s 设置小数精度(默认为2)命令行模式需要在算式前设置。例如:bash calculator.sh -s 3 3.5 * 2"
}
3.2 阶乘计算 (factorial_recursive
和 factorial_for
函数)
在这个计算器脚本中,阶乘计算是一个非常重要的功能。阶乘是一个数学运算,表示一个非负整数的连乘积,通常表示为 n!
。例如,5 的阶乘(5!)等于 5 × 4 × 3 × 2 × 1 = 120。在脚本中,我们实现了两种方法来计算阶乘:递归法和循环法。
1. 递归法 (factorial_recursive
函数)
递归是一种常见的编程技术,其中函数调用自身来解决问题。在阶乘计算中,递归的基本公式是:n! = n × (n-1)!
,直到 0! = 1
为止。
factorial_recursive() {
num=$1
if [[ ! "$num" =~ ^[0-9]+$ ]] || [ "$num" -lt 0 ]; then
echo "错误:阶乘只能对非负整数进行计算。"
return 1
fi
# 基本情况:0的阶乘为1
if [ "$num" -le 1 ]; then
echo 1
else
# 递归计算 n! = n * (n-1)!
local prev=$(factorial_recursive $(( $num - 1 )))
echo $(( $num * prev ))
fi
}
递归实现的详细解析:
- 输入验证:首先,检查输入的数字是否是一个非负整数。如果输入无效,返回错误提示。
- 基本情况:当输入为 0 或 1 时,递归终止并返回 1(因为
0! = 1
和1! = 1
)。 - 递归调用:对于其他整数
n
,通过调用factorial_recursive
函数来计算(n-1)!
,然后将结果与n
相乘,得到n!
。
2. 循环法 (factorial_for
函数)
除了递归方法外,我们还使用了循环来实现阶乘计算。循环方法的优点在于避免了递归调用可能带来的栈溢出问题,特别是在计算较大阶乘时。
factorial_for() {
num=$1
result=1
if [[ ! "$num" =~ ^[0-9]+$ ]] || [ "$num" -lt 0 ]; then
echo "错误:阶乘只能对非负整数进行计算。"
return 1
fi
if [ "$num" -le 1 ]; then
echo result
else
for(( i=1; i<=$num; i++))
do
result=$(( result * i))
done
fi
echo "$result"
}
循环实现的详细解析:
- 输入验证:与递归方法一样,首先检查输入是否为有效的非负整数。
- 初始化结果变量:定义一个变量
result
,初始值为 1。 - 循环计算:从 1 到
num
,通过循环将每个整数与result
相乘,逐步得到n!
。 - 返回结果:计算完成后,输出
result
作为阶乘结果。
3 选择递归与循环法的依据:
- 递归法:更简洁,符合数学表达式的自然定义,但对于较大数字,可能会因递归深度过大而导致栈溢出。
- 循环法:适用于计算大数阶乘,避免了递归可能带来的性能问题,特别是对于大范围的整数,它的效率较高。
4 使用示例:
假设用户希望计算 5!
,可以通过命令行模式输入:
bash calculator.sh 5 !
无论是使用递归还是循环方法,计算器都能返回正确的阶乘结果:
结果:120
3.3 计算函数 (calculate
函数)
calculate
函数是该 Bash 计算器脚本的核心部分,负责执行所有的计算操作。它根据用户输入的数字、运算符及小数精度来决定采用哪种计算方式(加法、减法、乘法、除法或阶乘计算),并输出计算结果。
以下是 calculate
函数的完整代码及其详细解析:
calculate() {
num1=$1
operator=$2
num2=$3
# 判断是否输入了有效的数值
if ! [[ "$num1" =~ ^[0-9]+(\.[0-9]+)?$ ]] || ! [[ "$num2" =~ ^[0-9]+(\.[0-9]+)?$ ]] && [ "$operator" != "!" ]; then
echo "错误:请输入有效的数值。"
return 1
fi
case $operator in
"+"|"-"|"*"|"/")
result=$(awk 'BEGIN{printf "%.'$decimal_precision'f\n",'$num1' '$operator' '$num2'}')
;;
"!")
result=$(factorial_for $num1)
;;
*)
echo "错误:不支持的运算符 $operator。请使用 +, -, *, / 或 !。"
return 1
;;
esac
echo "结果:$result"
}
1. 输入参数:
num1
、operator
和 num2
分别是用户输入的第一个数字、运算符和第二个数字。如果用户输入的是阶乘运算,那么 num2
并没有用到,因为阶乘只需要一个数值。
2. 输入验证
使用正则表达式检查 num1
和 num2
是否为有效的数字(包括整数和浮点数)。如果运算符是阶乘(!
),则只需要验证 num1
是否为有效的数字。
如果输入无效,输出错误信息并终止函数。
if ! [[ "$num1" =~ ^[0-9]+(\.[0-9]+)?$ ]] || ! [[ "$num2" =~ ^[0-9]+(\.[0-9]+)?$ ]] && [ "$operator" != "!" ]; then
echo "错误:请输入有效的数值。"
return 1
fi
3. 选择运算类型
使用 case
语句判断运算符 operator
,根据不同的运算符选择相应的计算方式:
- 加法、减法、乘法、除法:使用
awk
执行浮点数计算,awk
中的printf
用于格式化输出结果,保留指定的小数精度(通过decimal_precision
参数控制)。
result=$(awk 'BEGIN{printf "%.'$decimal_precision'f\n",'$num1' '$operator' '$num2'}')
这里,$decimal_precision
是在命令行中指定的精度(默认为 2),用于控制输出结果的小数位数。
bc
只有除法运算时可以指定精度,所以使用awk
函数。
- 阶乘计算:如果运算符是
!
,则调用factorial_for
函数进行阶乘计算,并将num1
传递给它。
result=$(factorial_for $num1)
- 错误处理:如果输入的运算符不在支持的范围内(即不是
+
、-
、*
、/
或!
),则输出错误信息并提示用户使用有效的运算符。
echo "错误:不支持的运算符 $operator。请使用 +, -, *, / 或 !。"
return 1
4. 输出结果
最后,calculate
函数输出计算结果。如果计算成功,显示 "结果:$result"
,否则显示错误信息。
5. 示例使用:
加法计算: 用户输入:
bash calculator.sh 3 + 5
计算器输出:
结果:8.00
除法计算: 用户输入:
bash calculator.sh 7 / 3
计算器输出:
结果:2.33
阶乘计算: 用户输入:
bash calculator.sh 5 !
计算器输出:
结果:120
6. 小数精度设置:
如果用户希望设置浮点数运算的精度,可以通过命令行选项 -s
来指定精度。例如,设置精度为 4 位小数:
bash calculator.sh -s 4 7 / 3
输出结果:
结果:2.3333
3.4 命令行参数解析
在 Bash 脚本中,命令行参数解析是一个非常重要的功能,它允许用户在运行脚本时传递不同的参数,以定制脚本的行为。在这个计算器脚本中,我们通过 getopts
命令来解析命令行选项和参数,实现对小数精度设置、显示帮助文档等功能的支持。
以下是命令行参数解析部分的代码及其详细解析:
# 解析命令行参数
while getopts ":hs:" opt; do
case $opt in
h)
show_help
exit 0
;;
s)
decimal_precision=$OPTARG
if [[ ! "$decimal_precision" =~ ^[0-9]+$ ]] || [ "$decimal_precision" -lt 0 ]; then
echo "错误:精度必须是一个非负整数。"
exit 1
fi
;;
\?)
echo "无效的选项: -$OPTARG"
echo ""
show_help
exit 1
;;
esac
done
shift $((OPTIND - 1))
1. getopts
的使用:
getopts
是 Bash 提供的一个内建命令,用于解析命令行选项(也叫标志),它能逐个处理传递给脚本的选项并执行相应的操作。命令行参数解析的流程如下:
while getopts ":hs:" opt; do
:这一行通过 getopts
解析命令行选项。getopts
会逐一检查每个选项并把当前的选项保存在变量 opt
中。
:h
和s:
:分别代表支持的选项。h
是一个不带参数的选项(即显示帮助文档),而s:
表示-s
选项需要一个附带参数(即小数精度)。opt
是用来存储当前选项的变量,每次getopts
解析一个选项,opt
会保存当前选项的字符。
2. 处理各个选项:
-h
选项:如果用户输入-h
,表示请求帮助文档。我们调用show_help
函数显示帮助信息并退出脚本。
h)
show_help
exit 0
;;
-s
选项:如果用户输入-s
,表示希望设置小数精度。此时,getopts
会将-s
后面的参数(精度值)赋给$OPTARG
。接着,我们检查这个精度值是否为非负整数,如果不符合条件则输出错误信息并退出。
s)
decimal_precision=$OPTARG
if [[ ! "$decimal_precision" =~ ^[0-9]+$ ]] || [ "$decimal_precision" -lt 0 ]; then
echo "错误:精度必须是一个非负整数。"
exit 1
fi
;;
- 无效选项:如果用户输入了脚本未定义的选项,
getopts
会将?
存入opt
变量。在这种情况下,输出错误信息并显示帮助文档。
\?)
echo "无效的选项: -$OPTARG"
echo ""
show_help
exit 1
;;
3. shift $((OPTIND - 1))
的作用:
shift
命令用于移动位置参数。在 getopts
完成选项解析后,所有的选项(如 -s
)都已被处理并移出。OPTIND
是 getopts
的一个内建变量,它指示下一个位置参数的索引。shift $((OPTIND - 1))
会将位置参数左移,以便后续处理实际的计算参数。
例如,如果用户输入了 bash calculator.sh -s 4 5 + 3
,getopts
会处理 -s 4
选项,然后通过 shift
将位置参数从 5 + 3
移动到 $1
和 $2
,使得后续的 calculate
函数可以处理这些参数。
4. 命令行参数解析流程示例:
假设用户运行脚本时输入了以下命令:
bash calculator.sh -s 4 7 / 3
-s 4
表示设置小数精度为 4 位。7 / 3
是计算的算式。
解析过程如下:
getopts
解析到-s
选项,并将4
赋给decimal_precision
。shift
命令移除-s 4
,剩下的位置参数为7
、/
和3
,传递给calculate
函数进行处理。
输出结果:
结果:2.3333
3.5 交互模式部分
交互模式是该计算器脚本的一个重要功能,它允许用户在脚本运行时通过命令行输入多个计算式,实时获得计算结果,而无需每次运行脚本。在交互模式下,用户可以连续进行计算,直到输入退出命令(如 q
)为止。这种模式特别适合那些希望快速进行多次计算的用户。
以下是交互模式部分的代码及其详细解析:
# 如果没有输入算式,则进入交互模式
if [ $# -eq 0 ]; then
echo "进入交互模式,输入数字和运算符进行计算,输入 'q' 退出"
while true; do
# 读取用户输入
read -p "请输入计算式 (num1 运算符 num2) 或 'q' 退出: " num1 operator num2
# 检查是否输入退出命令
if [[ "$num1" == "q" || "$operator" == "q" || "$num2" == "q" ]]; then
echo "退出程序"
exit 0
fi
# 调用计算函数
calculate "$num1" "$operator" "$num2"
done
fi
1. 判断是否进入交互模式:
交互模式的激活是基于脚本启动时是否提供了命令行参数来判断的。如果用户没有提供任何参数(即没有传递计算式),脚本将自动进入交互模式。
if [ $# -eq 0 ]; then
这里,$#
表示脚本接受的参数个数。如果参数个数为零,表示没有传入算式,脚本将进入交互模式。
2. 欢迎信息:
当脚本进入交互模式时,首先输出欢迎信息,提示用户输入计算式或 q
来退出程序。
echo "进入交互模式,输入数字和运算符进行计算,输入 'q' 退出"
3. 循环读取用户输入:
进入一个无限循环,通过 read
命令获取用户的输入。在每次循环中,脚本提示用户输入计算式(例如 3 + 5
),并将输入的内容分别赋值给变量 num1
、operator
和 num2
。read
命令会根据用户输入的空格或换行来分割输入并自动赋值。
read -p "请输入计算式 (num1 运算符 num2) 或 'q' 退出: " num1 operator num2
4. 退出条件判断:
在每次循环中,脚本会检查用户是否输入了退出命令 'q'
。如果用户输入的任何部分是 'q'
,则脚本会输出 "退出程序"
并结束程序。
if [[ "$num1" == "q" || "$operator" == "q" || "$num2" == "q" ]]; then
echo "退出程序"
exit 0
fi
这里,||
是逻辑“或”运算符,表示如果 num1
、operator
或 num2
中的任何一个为 'q'
,则跳出循环并退出程序。
5. 调用计算函数:
如果用户没有输入 'q'
,则脚本调用 calculate
函数,传递用户输入的 num1
、operator
和 num2
来进行计算。
calculate "$num1" "$operator" "$num2"
calculate
函数会根据运算符执行对应的计算操作,并输出计算结果。
6. 循环继续:
如果用户没有输入退出命令,脚本会继续在循环中读取下一次输入,直到用户选择退出。
7. 示例使用:
用户输入计算式:
请输入计算式 (num1 运算符 num2) 或 'q' 退出: 283.324 - 456.231
结果:-172.91
用户继续计算:
请输入计算式 (num1 运算符 num2) 或 'q' 退出: 13.43 * 4.56
结果:61.24
用户选择退出:
请输入计算式 (num1 运算符 num2) 或 'q' 退出: q
退出程序
4 总结
通过这篇博客,我们介绍了如何使用 Bash 脚本实现一个简易的命令行计算器,并实现了交互模式和命令行模式的功能。这个计算器支持加、减、乘、除以及阶乘运算,能够根据用户的输入实时计算结果。以下是本文的关键要点:
-
功能实现:我们使用了 Bash 脚本的基本语法和控制结构,如
case
、if
、while
循环、函数和getopts
命令来实现计算器的各项功能。通过递归和循环的方式,我们分别实现了阶乘的计算。 -
交互模式与命令行模式:通过判断命令行参数是否为空,脚本能够自动进入交互模式,允许用户实时进行多次计算。在命令行模式下,用户可以通过传递参数来直接进行单次计算。
-
命令行参数解析:我们使用了
getopts
来处理命令行参数,支持设置小数精度,并提供了帮助文档选项,方便用户快速查看脚本用法。需要注意的是,如果-s
选项出现在运算式的最后,当前脚本会出现问题。为了避免这种情况,建议在命令行模式中将-s
选项放在运算式之前,以确保精度设置能够正确生效。 -
简洁高效的用户体验:无论是在交互模式下连续计算,还是在命令行模式下进行单次运算,用户都能以简洁直观的方式得到想要的计算结果,极大地提高了脚本的实用性和灵活性。
-
扩展性与改进:这个计算器的基本框架已经搭建完成,但我们还可以进一步扩展它的功能。例如,可以加入更多数学运算、支持更高的精度、更复杂的错误处理机制,甚至是图形化界面等。
通过这篇文章,希望能够帮助大家掌握如何用 Bash 脚本编写简单的工具,同时为进一步学习 Bash 编程打下基础。希望你们能通过这个实践项目,增强对脚本语言的理解,提升编程技能。
5 附上源码
#!/bin/bash
# 显示帮助文档
show_help() {
echo "简单计算器 支持浮点数加、减、乘、除和阶乘 by DuChuan"
echo "支持的运算符:+ - \"*\" / !(乘法请用引号包裹\"*\")"
echo ""
echo "交互模式:"
echo " 如果不传入任何参数,进入交互模式,您可以连续进行计算。"
echo " 输入计算式(num1 运算符 num2)进行计算,输入 'q' 退出。"
echo ""
echo "命令行模式:"
echo " 直接传入 num1、运算符和 num2 进行一次性计算。"
echo " 例如:bash calculator.sh -s 3 3 + 5"
echo ""
echo "其他帮助:"
echo " -h 显示帮助文档。"
echo " '!' 用于计算阶乘,例如:bash calculator.sh 5 !"
echo " -s 设置小数精度(默认为2)命令行模式需要在算式前设置。例如:bash calculator.sh -s 3 3.5 * 2"
}
factorial_recursive() {
num=$1
if [[ ! "$num" =~ ^[0-9]+$ ]] || [ "$num" -lt 0 ]; then
echo "错误:阶乘只能对非负整数进行计算。"
return 1
fi
# 基本情况:0的阶乘为1
if [ "$num" -le 1 ]; then
echo 1
else
# 递归计算 n! = n * (n-1)!
local prev=$(factorial_recursive $(( $num - 1 )))
echo $(( $num * prev ))
fi
}
factorial_for() {
num=$1
result=1
if [[ ! "$num" =~ ^[0-9]+$ ]] || [ "$num" -lt 0 ]; then
echo "错误:阶乘只能对非负整数进行计算。"
return 1
fi
if [ "$num" -le 1 ]; then
echo result
else
for(( i=1; i<=$num; i++))
do
result=$(( result * i))
done
fi
echo "$result"
}
calculate() {
num1=$1
operator=$2
num2=$3
# 判断是否输入了有效的数值
if ! [[ "$num1" =~ ^[0-9]+(\.[0-9]+)?$ ]] || ! [[ "$num2" =~ ^[0-9]+(\.[0-9]+)?$ ]] && [ "$operator" != "!" ]; then
echo "错误:请输入有效的数值。"
return 1
fi
case $operator in
"+"|"-"|"*"|"/")
result=$(awk 'BEGIN{printf "%.'$decimal_precision'f\n",'$num1' '$operator' '$num2'}')
;;
"!")
result=$(factorial_for $num1)
;;
"*")
echo "错误:不支持的运算符 $operator。请使用 +, -, * 或 /。"
return 1
;;
esac
echo "结果:$result"
}
decimal_precision=2
# 解析命令行参数
while getopts ":hs:" opt; do
case $opt in
h)
show_help
exit 0
;;
s)
decimal_precision=$OPTARG
if [[ ! "$decimal_precision" =~ ^[0-9]+$ ]] || [ "$decimal_precision" -lt 0 ]; then
echo "错误:精度必须是一个非负整数。"
exit 1
fi
;;
\?)
echo "无效的选项: -$OPTARG"
echo ""
show_help
exit 1
;;
esac
done
shift $((OPTIND - 1))
# 如果没有输入算是,则进入交互模式
if [ $# -eq 0 ]; then
echo "进入交互模式,输入数字和运算符进行计算,输入 'q' 退出"
while true; do
# 读取用户输入
read -p "请输入计算式 (num1 运算符 num2) 或 'q' 退出: " num1 operator num2
# 检查是否输入退出命令
if [[ "$num1" == "q" || "$operator" == "q" || "$num2" == "q" ]]; then
echo "退出程序"
exit 0
fi
# 调用计算函数
calculate "$num1" "$operator" "$num2"
done
fi
# 检查输入是否符合格式
if [ $# -ne 3 ] && [ $# -ne 2 ]; then
echo "错误:请输入 num1 运算符 num2 或 num1 运算符(阶乘)。"
echo ""
show_help
exit 1
fi
# 调用计算函数
calculate "$1" "$2" "$3" "$decimal_precision"