轻松上手Bash脚本:创建交互式计算器

1 引言

这篇博客将介绍如何使用 Bash 脚本实现一个功能全面的计算器,支持浮点数加减乘除以及阶乘计算。

本计算器脚本特别之处在于,它同时支持两种使用模式:交互模式和命令行模式。用户可以在终端中直接输入计算表达式进行一次性计算,或者进入交互式环境连续进行多次计算。同时,脚本允许通过命令行选项设置浮点数的计算精度,提升计算的灵活性和可用性。

本文将详细分析该脚本的实现过程,涵盖其核心功能、命令行参数的处理、错误处理机制等,并在最后附上完整源码,供读者学习与参考。

2 项目概述

本项目实现了一个基于 Bash 脚本的简单计算器,支持基本的四则运算(加、减、乘、除)以及阶乘计算。该计算器具有两种工作模式:交互模式和命令行模式。交互模式允许用户在程序运行时输入多个计算式,适用于快速连续的计算;命令行模式则允许用户通过传递参数一次性进行计算,适合批量处理任务。

2.1 主要功能:

  1. 四则运算:支持加法、减法、乘法、除法的基本运算。
  2. 阶乘计算:提供递归和循环两种方式计算阶乘,适用于非负整数。
  3. 小数精度设置:通过命令行参数设置计算结果的小数精度,默认为两位小数。
  4. 错误处理:包括无效的数值输入、无效的运算符、阶乘输入限制等。

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_recursivefactorial_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
}
递归实现的详细解析:
  1. 输入验证:首先,检查输入的数字是否是一个非负整数。如果输入无效,返回错误提示。
  2. 基本情况:当输入为 0 或 1 时,递归终止并返回 1(因为 0! = 11! = 1)。
  3. 递归调用:对于其他整数 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"
}
循环实现的详细解析:
  1. 输入验证:与递归方法一样,首先检查输入是否为有效的非负整数。
  2. 初始化结果变量:定义一个变量 result,初始值为 1。
  3. 循环计算:从 1 到 num,通过循环将每个整数与 result 相乘,逐步得到 n!
  4. 返回结果:计算完成后,输出 result 作为阶乘结果。

3 选择递归与循环法的依据:

  • 递归法:更简洁,符合数学表达式的自然定义,但对于较大数字,可能会因递归深度过大而导致栈溢出。
  • 循环法:适用于计算大数阶乘,避免了递归可能带来的性能问题,特别是对于大范围的整数,它的效率较高。

4 使用示例:

假设用户希望计算 5!,可以通过命令行模式输入:

bash calculator.sh 5 !

无论是使用递归还是循环方法,计算器都能返回正确的阶乘结果:

结果:120

Pasted image 20241128214058

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. 输入参数:

num1operatornum2 分别是用户输入的第一个数字、运算符和第二个数字。如果用户输入的是阶乘运算,那么 num2 并没有用到,因为阶乘只需要一个数值。

2. 输入验证

使用正则表达式检查 num1num2 是否为有效的数字(包括整数和浮点数)。如果运算符是阶乘(!),则只需要验证 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

Pasted image 20241128214128

除法计算: 用户输入:

bash calculator.sh 7 / 3

计算器输出:

结果:2.33

Pasted image 20241128214154

阶乘计算: 用户输入:

bash calculator.sh 5 !

计算器输出:

结果:120

Pasted image 20241128214058

6. 小数精度设置:

如果用户希望设置浮点数运算的精度,可以通过命令行选项 -s 来指定精度。例如,设置精度为 4 位小数:

bash calculator.sh -s 4 7 / 3

输出结果:

结果:2.3333

Pasted image 20241128214226

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 中。

  • :hs::分别代表支持的选项。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)都已被处理并移出。OPTINDgetopts 的一个内建变量,它指示下一个位置参数的索引。shift $((OPTIND - 1)) 会将位置参数左移,以便后续处理实际的计算参数。

例如,如果用户输入了 bash calculator.sh -s 4 5 + 3getopts 会处理 -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),并将输入的内容分别赋值给变量 num1operatornum2read 命令会根据用户输入的空格或换行来分割输入并自动赋值。

read -p "请输入计算式 (num1 运算符 num2) 或 'q' 退出: " num1 operator num2

4. 退出条件判断

在每次循环中,脚本会检查用户是否输入了退出命令 'q'。如果用户输入的任何部分是 'q',则脚本会输出 "退出程序" 并结束程序。

if [[ "$num1" == "q" || "$operator" == "q" || "$num2" == "q" ]]; then
    echo "退出程序"
    exit 0
fi

这里,|| 是逻辑“或”运算符,表示如果 num1operatornum2 中的任何一个为 'q',则跳出循环并退出程序。

5. 调用计算函数

如果用户没有输入 'q',则脚本调用 calculate 函数,传递用户输入的 num1operatornum2 来进行计算。

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
退出程序

Pasted image 20241128214536

4 总结

通过这篇博客,我们介绍了如何使用 Bash 脚本实现一个简易的命令行计算器,并实现了交互模式和命令行模式的功能。这个计算器支持加、减、乘、除以及阶乘运算,能够根据用户的输入实时计算结果。以下是本文的关键要点:

  1. 功能实现:我们使用了 Bash 脚本的基本语法和控制结构,如 caseifwhile 循环、函数和 getopts 命令来实现计算器的各项功能。通过递归和循环的方式,我们分别实现了阶乘的计算。

  2. 交互模式与命令行模式:通过判断命令行参数是否为空,脚本能够自动进入交互模式,允许用户实时进行多次计算。在命令行模式下,用户可以通过传递参数来直接进行单次计算。

  3. 命令行参数解析:我们使用了 getopts 来处理命令行参数,支持设置小数精度,并提供了帮助文档选项,方便用户快速查看脚本用法。需要注意的是,如果 -s 选项出现在运算式的最后,当前脚本会出现问题。为了避免这种情况,建议在命令行模式中将 -s 选项放在运算式之前,以确保精度设置能够正确生效。

  4. 简洁高效的用户体验:无论是在交互模式下连续计算,还是在命令行模式下进行单次运算,用户都能以简洁直观的方式得到想要的计算结果,极大地提高了脚本的实用性和灵活性。

  5. 扩展性与改进:这个计算器的基本框架已经搭建完成,但我们还可以进一步扩展它的功能。例如,可以加入更多数学运算、支持更高的精度、更复杂的错误处理机制,甚至是图形化界面等。

通过这篇文章,希望能够帮助大家掌握如何用 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"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值