首页  /  Linux 进阶  /  正文

Shell 数学计算与脚本练习

Linux 进阶 2026-10-02📖 20 分钟👁 —
🐚 Linux 进阶 · Shell 编程第 4 / 10 页12345678910📖 完整导航 →

一、脚本编写前的回顾

1. 从一行命令到脚本

进阶部分要求具备脚本开发的能力。

利用分号可以执行多行命令,算是最简单的脚本。

如果要完成需求很多的工作,需要写一个 sh 脚本写入代码,然后执行该文件:

ubuntu@VM-0-9-ubuntu:~/shell$ cat time.sh
#! /bin/bash

echo "当前时间为: `date`"
ubuntu@VM-0-9-ubuntu:~/shell$ bash time.sh
当前时间为: Wed Jul 22 04:46:23 PM CST 2026
ubuntu@VM-0-9-ubuntu:~/shell$ cat echo_test.sh
#! /bin/bash
#
# 显示日期和登录用户
#
echo "当前时间:"
date
echo ""
echo "当前用户:"
who
ubuntu@VM-0-9-ubuntu:~/shell$ ./echo_test.sh
-bash: ./echo_test.sh: Permission denied
ubuntu@VM-0-9-ubuntu:~/shell$ chmod +x echo_test.sh
ubuntu@VM-0-9-ubuntu:~/shell$ ./echo_test.sh
当前时间:
Wed Jul 22 04:50:48 PM CST 2026

当前用户:
ubuntu   pts/0        2026-07-13 12:04 (123.115.197.163)
ubuntu   pts/2        2026-07-22 16:44 (123.115.196.109)

echo 可以输出变量:

ubuntu@VM-0-9-ubuntu:~/shell$ echo $USER
ubuntu
ubuntu@VM-0-9-ubuntu:~/shell$ echo $UID
1000
ubuntu@VM-0-9-ubuntu:~/shell$ echo $HOME
/home/ubuntu

\ 是转义符。

2. 脚本中的变量加载位置

不同方式执行脚本,产生的结果不同。

  1. source 和 . 在当前 shell 中加载变量,然后执行
cat make_var.sh 之后用 bash 跑,脚本里的 days、name 都没留在当前 shell,echo $days 是空的,要 source 才留住

在 bash 环境中执行了脚本,变量就定义到了 bash 环境中。

在当前 shell 里 ps -ef --forest,-bash 下面直接就是 ps,没有多出一层 bash
  1. bash 和 sh 执行脚本,开启子 shell 运行,变量也在子 shell 中加载,子 shell 退出后变量也就消失了

获取变量的值必须加 $。

3. shell 变量替换引用

shell 的一大特性就是从命令的结果中再次提取结果,适合编写脚本。

比如:

ubuntu@VM-0-9-ubuntu:~/shell$ $(date)
Command 'Wed' not found, did you mean:

  command 'sed' from deb sed (4.9-2ubuntu0.24.04.1)

  command 'med' from deb ncl-ncarg (6.6.2.dfsg.1-3)

  command 'zed' from deb zfs-zed (2.2.2-0ubuntu9.4)

  command 'jed' from deb jed (1:0.99.20~pre.180+dfsg-2)

  command 'ed' from deb ed (1.19-1)

  command 'red' from deb ed (1.19-1)

Try: sudo apt install <deb name>

这条命令把 date 的结果发给了 bash 解释器,bash 解释器不认识,报错了。

但是 echo "$(date)" 就可以,因为结果给了 echo。

反引号 ` date 和 $(date) 几乎一样,比如 echo "当前日期:$(date)"`。

() 这是开启子 shell 执行命令,但是不维持子 shell,执行完退出。

$ 取变量的符号。


二、数学运算与双小括号

shell 也支持数学运算,但是比其他编程语言简单很多,进阶内容更注重代码逻辑,需要多写多用。

下图中的符号很多,但常用的很好记:

算术运算符汇总:加减、乘除取余、幂运算、自增自减、逻辑运算、比较符号、位移、位运算、赋值运算
运算操作符与运算命令:《双小括号》、let、expr、bc、$[]、awk、declare 各自适合干什么

1. 双小括号

双小括号的四条用法

下面解释图中四条内容。

第一条:赋值过程写在括号内。

# i=i+1 是把字符串 "i+1" 赋给变量,((i=i+1)) 才是真正的数学运算
i=0
i=i+1           # i 现在是字符串 "i+1",不是 1
echo $i         # 输出 i+1

i=0
((i=i+1))       # i 现在是 1
echo $i         # 输出 1

第二条:赋值过程写在括号外加 $($ 很多情况都是“取出值”的意思)。

第三条:支持 &&、||、!、== 等数学符号。

# 真为1,假为0
# 执行状态概念中,返回0为成功,其余失败
# 逻辑判断概念中,1为真,0为假
ubuntu@VM-0-9-ubuntu:~/shell$ echo $((6>7))
0

第四条:输出表达式的结果必须加 $。

2. 小数怎么办

shell 的基础命令仅支持整数运算,小数需要 bc 这样的命令才支持:

echo "3.14 * 2.5" | bc      # 输出 7.85
echo "scale=2; 10/3" | bc    # scale=2 保留两位小数,输出 3.33

三、数值计算脚本

1. ++ 和 --

++ 是加 1,-- 是减 1,前后位置运算有区别。

++a 是先计算加 1,然后赋值给 a,a++ 是先对 a 操作再加 1。

# C都学过,这里不多废话了
ubuntu@VM-0-9-ubuntu:~/shell$ a=0
ubuntu@VM-0-9-ubuntu:~/shell$ echo $((++a))
1
ubuntu@VM-0-9-ubuntu:~/shell$ echo $$((a--))
-bash: syntax error near unexpected token `('
ubuntu@VM-0-9-ubuntu:~/shell$ echo $((a--))
1
ubuntu@VM-0-9-ubuntu:~/shell$ echo $a
0

2. 对用户输入判断的脚本开发演示

需求:接收用户输入的数字,对运算符号判断,计算结果输出。

  1. 接收用户输入
  2. 判断输入内容是数字
  3. 判断运算符号
  4. 计算并输出

脚本名称最好直观。

函数就是把代码写的功能打包封装,调用函数就会执行代码。

写这个脚本之前先熟悉一下 sed:

# sed 's/旧内容/新内容/选项'
# s 是替换命令,/ 是分隔符,g 是全局替换
# 一行内所有匹配都替换,不加 g 只换第一个
echo "hello world hello" | sed 's/hello/hi/'      # 只换第一个 → hi world hello
echo "hello world hello" | sed 's/hello/hi/g'     # 全换 → hi world hi
sed -i 's/80/8080/g' nginx.conf                   # -i 直接修改文件
sed -n '5,10p' access.log                          # 只输出第5-10行
sed '/error/d' access.log                          # 删除含 error 的行

sed 's/[0-9]//g'
#    │  │     ││
#    │  │     │└── g:全局替换(删除所有数字)
#    │  │     └─── 替换为空(删除)
#    │  └───────── 正则:匹配一个数字
#    └──────────── 替换命令

实现:

ubuntu@VM-0-9-ubuntu:~/shell$ cat calcalation.sh
#! /bin/bash

#函数
print_usage(){
	printf "Please enter an unteger\n"
	# 给脚本执行结果赋予退出码,不论成功与否都会输出指定的
	exit 1

}


# 接收用户输入,-p后面写给用户看到的
# read -p 提示信息 接收用户输入的变量
read -p "Please input your number: " firstnum


# 对用户输入判断
# 中括号里面前后必须空格
# -n是if语句,对字符串判断,字符串为空条件不成立,反之成立
# sed是替换,把0到9数字都替换为空,删除所有非数字内容
if [ -n "`echo $firstnum|sed 's/[0-9]//g'`"  ]
	then
		print_usage
fi

# 上述代码判断用户的输入
# 输入运算符
read -p "input operator: " operator

# 判断运算符,限制加减乘除,其余无效

if [  "$operator" != "+"   ] &&[  "$operator" != "-"   ]&&[  "$operator" != "*"   ]&&[  "$operator" != "/"   ]
  then
    echo "仅支持+-*/"
fi

# 对第二个变量处理
read -p "please input second number: " secondnum

if [ -n "`echo $secondnum|sed 's/[0-9]//g'`"  ]
        then
                print_usage
fi

# 计算
echo "$firstnum $operator $secondnum结果是: $(($firstnum $operator $secondnum))" 

执行:

# 输入错误
ubuntu@VM-0-9-ubuntu:~/shell$ bash calcalation.sh 
Please input your number: wq33
Please enter an unteger
ubuntu@VM-0-9-ubuntu:~/shell$ echo $?
1

# 运算
ubuntu@VM-0-9-ubuntu:~/shell$ bash calcalation.sh 
Please input your number: 123
input operator: +
please input second number: 456
123 + 456结果是: 579

3. Let 命令计算

$1 表示接收用户输入的第一个参数:

ubuntu@VM-0-9-ubuntu:~/shell$ cat calculation2.sh
#! /bin/bash

echo $(($1))
ubuntu@VM-0-9-ubuntu:~/shell$ bash calculation2.sh 
0
# linux通过空格分隔参数
ubuntu@VM-0-9-ubuntu:~/shell$ bash calculation2.sh 3*9
27
ubuntu@VM-0-9-ubuntu:~/shell$ bash calculation2.sh "3 * 9"
27

脚本开发必然是需要经验的,越熟练写的越牛逼,初期注重实现功能,不去追求效率。

let 命令介绍

let 命令执行的效果等于 (( )),但是效率更低,因为后者是内置命令。

赋值必须按照语法规范:

ubuntu@VM-0-9-ubuntu:~/shell$ num=8
ubuntu@VM-0-9-ubuntu:~/shell$ num=num+8
ubuntu@VM-0-9-ubuntu:~/shell$ echo $num
num+8

# 使用let
ubuntu@VM-0-9-ubuntu:~/shell$ num=5
ubuntu@VM-0-9-ubuntu:~/shell$ let num=num+4
ubuntu@VM-0-9-ubuntu:~/shell$ echo $num
9

# 规范语法
ubuntu@VM-0-9-ubuntu:~/shell$ num=5
ubuntu@VM-0-9-ubuntu:~/shell$ num=$((num+4))
ubuntu@VM-0-9-ubuntu:~/shell$ echo $num
9

四、Nginx 存活检测脚本

1. 思路

脚本开发思路:想好功能作用、写伪代码。

先定义变量,存储变化的值,便于后期维护,比如 nginx_version=1.7。

出现很多次的数值、字符可以设为变量,以后统一修改也很方便:

把反复出现的版本号抽成变量,后面统一用 ${mysql_version} 引用

安装服务 yum -y install nginx-${nginx_version},然后启动、修改配置文件、重启服务。

2. 循环和 wget

shell 的循环语法(后面详细介绍),while 无限循环的格式:

while true;do echo "你好";sleep 1;done

wget 参数:

wget -c -P /下载/路径 -O 新名字.tar.gz https://168kaguyachi.site
#    │  │             │
#    │  │             └── 下载后重命名
#    │  └── 下载到指定目录
#    └── 断点续传

wget --timeout=$timeout --tries=1 http://168kaguyachi.site -q -O /dev/null
#    │                       |                              │   │
#    │                       |                              │   └── 输出到黑洞
#    │                       |                              └── 安静模式,不打印日志
#    │                       | 
#    │             只试1次,失败不重试
#    └── 超时时间,防止卡死

# 常见用法
wget URL                        # 直接下载,最简单
wget -c URL                     # 断点续传,中断了接着下
wget -P /目录 URL               # 下载到指定目录
wget -O 新文件名 URL            # 下载并重命名

-O 在 wget 里就是指向输出文件,本质上就是重命名。因为 Linux 里所有设备都是文件,把输出目标指向 /dev/null(黑洞文件),就等于把下载内容重定向到这个特殊设备,结果就是下载了不存任何东西。-O 既用来改文件名,也用来指向特殊文件设备,并不矛盾。

3. 完整脚本

ubuntu@VM-0-9-ubuntu:~/shell$ cat check_nginx_status.sh 
#! /bin/bash

# 版本变量

CheckUrl(){

    timeout=5
    # 计数器
    fails=0
    success=0

    # 循环执行命令
    while true
	do
	    wget --timeout=$timeout --tries=1 http://168kaguyachi.site -q -O /dev/null
	# 如果命令执行成功$?为0
	# if的条件参数-ne是不等于
	if [ $? -ne 0  ]
	    then
		let fails=fails+1 # 失败次数加1
	else
	    	let success+=1
	fi

	# 判断成功次数>=1时,该网站正常运行
	# -ge是大于等于
	if [ $success -ge 1 ] 
	    then
		echo "该网站存活"
		# 状态码返回
		exit 0
	fi

	# 失败次数>2,告警
	if [ $fails -ge 2  ];
	    then
		echo "挂了"
	        exit 2
	fi
    done

}
# 函数定义后要调用执行
CheckUrl

ubuntu@VM-0-9-ubuntu:~/shell$ bash check_nginx_status.sh 
该网站存活
ubuntu@VM-0-9-ubuntu:~/shell$ echo $?
0

想找花括号的另一个,光标选中,% 可以定位(即 shift+5)。


五、expr 命令

如果不知道命令怎么使用可以 命令 --help 查看参数用法。

expr 基于空格区分参数,在 shell 中一些元字符(* . ?)都是有特殊含义的。

1. 四个常用功能

计算:

ubuntu@VM-0-9-ubuntu:~/shell$ expr 3*5
3*5
ubuntu@VM-0-9-ubuntu:~/shell$ expr 5 * 3
expr: syntax error: unexpected argument ‘calcalation.sh’
ubuntu@VM-0-9-ubuntu:~/shell$ expr 5 \* 3
15
# 需要转译

求长度:

ubuntu@VM-0-9-ubuntu:~/shell$ expr length llll
4

逻辑判断:

ubuntu@VM-0-9-ubuntu:~/shell$ expr 8 \> 6
1
ubuntu@VM-0-9-ubuntu:~/shell$ expr 8 \< 6
0

模式匹配:: 匹配操作(匹配参数),返回匹配的字符长度;.* 是正则(匹配内容),匹配从字符串开头 . 到后面任意内容 * 的部分。

. 匹配任意一个字符,* 把前面的 . 重复 0 到无限次,所以 .* 就是匹配任意长度、任意内容的东西。

统计文件字符个数:

# 后面不加东西匹配所有字符
ubuntu@VM-0-9-ubuntu:~/shell$ expr ky.png ":" ".*"
6
ubuntu@VM-0-9-ubuntu:~/shell$ expr kyky.png ":" ".*"
8
# 匹配指定字符
ubuntu@VM-0-9-ubuntu:~/shell$ expr kyky.png ":" ".*y"
4
ubuntu@VM-0-9-ubuntu:~/shell$ expr kyky.png ":" ".*k"
3
# 匹配到到最后一位

基于这个特性写脚本:判断以 jpg 为后缀的文件。

ubuntu@VM-0-9-ubuntu:~/sub_str$ expr  yc.jpg  ":"  ".*\.jpg"
6
# 仅匹配到最后一位
# .jpg四个符号必须一样才能成功匹配,就是正则的匹配模式
ubuntu@VM-0-9-ubuntu:~/sub_str$ expr  yc.jpggggg  ":"  ".*\.jpg"
6
ubuntu@VM-0-9-ubuntu:~/sub_str$ expr  yc.jppg  ":"  ".*\.jpg"
0

2. 判断文件名后缀

shell 中 0 为假,非 0 则真。

需求 1:执行脚本传入文件名,判断该文件名是否是 jpg。

ubuntu@VM-0-9-ubuntu:~/shell$ cat fail_houzhui.sh
#! /bin/bash

if expr "$1" ":" ".*\.jpg" &> /dev/null
	then
		echo "确认为jpg文件"
	else
		echo "不是jpg文件"
fi

ubuntu@VM-0-9-ubuntu:~/shell$ bash fail_houzhui.sh 鸡哥.jpg
确认为jpg文件
ubuntu@VM-0-9-ubuntu:~/shell$ bash fail_houzhui.sh 鸡哥.png
不是jpg文件

3. 找出长度小于 5 的词

需求 2:找出长度小于 5 的词。利用 for 遍历:

# for的基础用法:
ubuntu@VM-0-9-ubuntu:~/shell$ cat length_word.sh
#! /bin/bash

for word in  I an Yu chao, I teach you to learn linux.
do
	echo $word
done
ubuntu@VM-0-9-ubuntu:~/shell$ bash length_word.sh
I
an
Yu
chao,
I
teach
you
to
learn
linux.

if 中 -le 小于等于,-lt 小于(less than):

ubuntu@VM-0-9-ubuntu:~/shell$ cat length_word.sh
#! /bin/bash

for word in  I an Yu chao, I teach you to learn linux.
do
	if [ `expr length $word` -lt 5 ]
		then
			echo $word
	fi
done
ubuntu@VM-0-9-ubuntu:~/shell$ bash length_word.sh
I
an
Yu
I
you
to
ubuntu@VM-0-9-ubuntu:~/shell$ 

六、数学计算收尾

1. bc 计算器

交互式操作(输入命令看到结果):

# bc进入交互模式
ubuntu@VM-0-9-ubuntu:~/shell$ 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.6*88
140.8
# 小数除法不会列出小数
1.22/2
0

结合管道符计算

# echo不支持计算,识别为字符
ubuntu@VM-0-9-ubuntu:~/shell$ echo "4*4"
4*4
# 通过管道符交给bc可以识别
ubuntu@VM-0-9-ubuntu:~/shell$ echo "4*4" | bc
16
# 支持小数
ubuntu@VM-0-9-ubuntu:~/shell$ echo "4*4.88" | bc
19.52
# 支持变量
ubuntu@VM-0-9-ubuntu:~/shell$ num=5
ubuntu@VM-0-9-ubuntu:~/shell$ result=`echo $num*4|bc`
ubuntu@VM-0-9-ubuntu:~/shell$ echo $result
20

2. 计算 1-1000 总和

echo {1..1000} 可以列出 1-1000,想办法加和,空格替换为加号:

tr "被替" "替换"
ubuntu@VM-0-9-ubuntu:~/shell$ echo {1..20}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# 空格替换为+
ubuntu@VM-0-9-ubuntu:~/shell$ echo {1..20} | tr " " "+"
1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20
# 交给bc
ubuntu@VM-0-9-ubuntu:~/shell$ echo {1..100} | tr " " "+" | bc
5050

seq 可以快速生成序列,-s 指定分隔符,默认换行为分隔符(\n):

ubuntu@VM-0-9-ubuntu:~/shell$ seq 5
1
2
3
4
5
ubuntu@VM-0-9-ubuntu:~/shell$ seq -s "+" 5
1+2+3+4+5
# 使用双括号
ubuntu@VM-0-9-ubuntu:~/shell$ echo $((`seq -s + 100`))
5050
# 使用expr,稍微复杂一些
# expr是接收多个参数计算的,现在生成的只是一个整体
# xargs可以分隔参数
ubuntu@VM-0-9-ubuntu:~/shell$ seq -s + 100 | xargs expr
1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23+24+25+26+27+28+29+30+31+32+33+34+35+36+37+38+39+40+41+42+43+44+45+46+47+48+49+50+51+52+53+54+55+56+57+58+59+60+61+62+63+64+65+66+67+68+69+70+71+72+73+74+75+76+77+78+79+80+81+82+83+84+85+86+87+88+89+90+91+92+93+94+95+96+97+98+99+100
# expr空格分隔的运算才能计算,所以seq可以主动分隔
ubuntu@VM-0-9-ubuntu:~/shell$ seq -s " + " 100|xargs expr
5050

3. awk 和中括号

awk 计算(支持小数计算):

ubuntu@VM-0-9-ubuntu:~/shell$ echo "3.2 2.2" | awk '{print $1+$2}'
5.4
ubuntu@VM-0-9-ubuntu:~/shell$ echo "3.2 2.2" | awk '{print $1*$2}'
7.04
ubuntu@VM-0-9-ubuntu:~/shell$ echo "3.2 2.2" | awk '{print $1*6.6+$2}'
23.32

中括号计算:

ubuntu@VM-0-9-ubuntu:~/shell$ num=3
ubuntu@VM-0-9-ubuntu:~/shell$ res=$[num+4]
ubuntu@VM-0-9-ubuntu:~/shell$ echo $res
7

这一页算数的路子基本就齐了:整数运算优先 (( )),小数交给 bc 或者 awk, expr 强在模式匹配和求长度,$[...] 是老写法,能用但没必要特意去用。