Shell 编程1
文章目录
- Shell 编程1
- if 条件语句
- 单分支结构
- 双分支结构
- if 条件语句实践
- Shell 函数的知识与实践
- Shell 函数介绍
- Shell 函数的执行
- Shell 函数的基础实践
- 函数的递归调用
- 求1+2+3+...+10 的和
- 求1\*2\*3\*...\*10 的阶乘
- fork 炸弹
- case 条件语句的应用实践
- case 条件语句实践
- case 语句企业级生产案例
- 本章小结
- while 循环和 until 循环的应用实践
- 当型和直到型循环的基本范例
- 让 Shell 脚本在后台运行的知识
- 企业生产实战:while 循环语句实践
- 本章小结
Shell 编程1
if 条件语句
if条件语句是Linux运维人员在实际生产工作中使用得最频繁也是最重要的语句,因此,请务必重视if条件语句的知识,并牢固掌握。
单分支结构
前文曾讲解过的文件条件表达式:
[ ! -d /tmp/wan ] && mkdir /tmp/wan
等价于下面的if条件语句:
if [ ! -d /tmp/wan ];thenmkdir /tmp/wan
fi
双分支结构
前文的文件测试条件表达式
[ -d /tmp/wan ] && echo /tmp/wan is exist || mkdir /tmp/wan
就相当于下面的双分支的if条件语句:
if [ -d /tmp/wan ];thenecho /tmp/wan is exist
elsemkdir /tmp/wan
fi
if 条件语句实践
示例1:检测sshd服务是否运行,如果未运行则启动sshd服务。
# 准备文件
[root@server ~ 14:14:21]# vim ssh_ctl.sh
#!/bin/bash
systemctl is-active sshd &>/dev/null
if [ $? -ne 0 ];thenecho "sshd is not running, I'll start sshd."systemctl start sshd
fi# 验证
[root@server ~ 14:36:09]# systemctl stop sshd
[root@server ~ 14:36:58]# bash ssh_ctl
sshd is not running,I'll start sshd.
示例2:检测sshd服务是否运行,如果未运行则启动sshd服务;如果运行,输出 “Running”。
# 准备文件
[root@server ~ 15:00:22]# vim ssh_ctl_1.sh
#!/bin/bash
systemctl is-active sshd &>/dev/null
if [ $? -ne 0 ];thenecho "sshd is not running."echo -n "Starting sshd ... ..."systemctl start sshd && echo DONE
elseecho "sshd is running"
fi# 验证
[root@server ~ 15:02:03]# bash ssh_ctl_1.sh
sshd is running
[root@server ~ 15:02:12]# systemctl stop sshd
[root@server ~ 15:02:49]# bash ssh_ctl_1.sh
sshd is not running.
Starting sshd ... ...DONE
示例3:通过传参控制sshd服务。
# 准备文件
[root@server ~ 15:02:52]# vim ssh_ctl_2.sh
#!/bin/bash
if [ "$1" == "start" ];thensystemctl start sshd
elif [ "$1" == "stop" ];thensystemctl stop sshd
elif [ "$1" == "status" ];thensystemctl status sshd
elif [ "$1" == "restart" ];thensystemctl restart sshd
elseecho "Usage: $0 start|stop|status|restart "
fi
# 或者
#!/bin/bash
if [ "$1" = "start" -o "$1" = "stop" -o "$1" = "status" -o "$1" = "restart" ];thensystemctl $1 sshd
elseecho "Usage: $0 start|stop|status|restart"
fi# 验证
[root@server ~ 15:03:50]# bash ssh_ctl_2.sh
Usage: ssh_ctl_2.sh start|stop|status|restart
[root@server ~ 15:04:17]# bash ssh_ctl_2.sh status
● sshd.service - OpenSSH server daemonLoaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)Active: active (running) since 五 2025-10-10 15:02:52 CST; 1min 40s agoDocs: man:sshd(8)man:sshd_config(5)Main PID: 106902 (sshd)CGroup: /system.slice/sshd.service└─106902 /usr/sbin/sshd -D10月 10 15:02:52 server systemd[1]: Starting OpenSS...
10月 10 15:02:52 server sshd[106902]: Server listen...
10月 10 15:02:52 server sshd[106902]: Server listen...
10月 10 15:02:52 server systemd[1]: Started OpenSSH...
Hint: Some lines were ellipsized, use -l to show in full.
综合示例:每3分钟检查一次系统可用内存,如果空闲内存低于100M时给root用户发邮件。
对于开发程序而言,一般来说应该遵循下面的3步法则。
分析需求
明白开发需求,是完成程序的大前提,因此,分析需求至关重要,一切不以需求为主的程序开发,都是不倡导的!
设计思路
设计思路就是根据需求,把需求进行拆解,分模块逐步实现,例如本题可以分为如下几步:
- 获取当前系统剩余内存的值(先在命令行实现)。
- 配置邮件报警(可采用第三方邮件服务器)。
- 判断取到的值是否小于100MB,如果小于100MB,就报警(采用if语句)。
- 编码实现Shell脚本。
- 加入crond定时任务,每三分钟检查一次。
编码实现
编码实现就是具体的编码及调试过程,工作中很可能需要先在测试环境下调试,调试好了,再发布到生产环境中。
本例的最终实现过程如下:
- 获取可用内存大小。
[root@server ~ 15:06:08]# free -mtotal used free shared buff/cache available
Mem: 3931 168 3611 11 151 3564
Swap: 2047 0 2047[root@server ~ 15:06:13]# free -m | awk 'NR==2 { print $4 }'
3611
发邮件的客户端常见的有mail或mutt;服务端有sendmail服务(CentOS5下默认的)、postfix服务(CentOS6下默认的)。这里不使用本地的邮件服务。
编写Shell脚本:check_memory.sh。
# 准备文件
[root@server ~ 16:19:30]# vim check_memory.sh
#!/bin/bash
memory_available_size=$(free -m | awk '/Mem/ {print $NF}')
memory_min_size=10000
if ((memory_free_size<memory_min_size));thenecho "Current available memory size is ${memory_available_size}" | \mail -s "memory size is lower" root
fi
加入到计划任务
[root@server ~ 16:19:39]# crontab -e
* * * * * /root/bin/check_memory.sh
Shell 函数的知识与实践
Shell 函数介绍
函数也有类似于别名的作用,例如可简化程序的代码量,让程序更易读、易改、易用。
简单地说,函数的作用就是将程序里多次被调用的相同代码组合起来(函数体),并为其取一个名字(即函数名),其他所有想重复调用这部分代码的地方都只需要调用这个名字就可以了。当需要修改这部分重复代码时,只需要改变函数体内的一份代码即可实现对所有调用的修改,也可以把函数独立地写到文件里,当需要调用函数时,再加载进来使用。
使用 Shell 函数的优势整理如下:
- 把相同的程序段定义成函数,可以减少整个程序的代码量,提升开发效率。
- 增加程序的可读性、易读性,提升管理效率。
- 可以实现程序功能模块化,使得程序具备通用性(可移植性)。
对于Shell来说,Linux系统里的近2000个命令可以说都是Shell的函数,所以,Shell的函数也是很多的,这一点需要读者注意。
Shell 函数的执行
Shell的函数分为最基本的函数和可以传参的函数两种,其执行方式分别说明如下。
执行不带参数的函数时,直接输人函数名即可(注意不带小括号)。
格式如下:
函数名
有关执行函数的重要说明:
- 执行Shell 函数时,函数名前的function和函数后的小括号都不要带。
- 函数的定义必须在要执行的程序前面定义或加载。
- Shell执行系统中各种程序的执行顺序为:系统别名->函数->系统命令->可执行文件。
- 函数执行时,会和调用它的脚本共用变量,也可以为函数设定局部变量及特殊位置参数。
- 在Shell 函数里面,return命令的功能与exit类似,return的作用是退出函数,而exit是退出脚本文件。
- return语句会返回一个退出值(即返回值)给调用函数的当前程序,而exit会返回一个退出值(即返回值)给执行程序的当前Shell。
- 如果将函数存放在独立的文件中,被脚本加载使用时,需要使用source或来加载。
- 在函数内一般使用local定义局部变量,这些变量离开函数后就会消失。
带参数的函数执行方法,格式如下:
函数名 参数1 参数2
函数后接参数的说明:
- Shell 的位置参数($1、2…、2…、2…、#、∗、*、∗、?及$@)都可以作为函数的参数来使用。
- 此时父脚本的参数临时地被函数参数所掩盖或隐藏。
- $0 比较特殊,它仍然是父脚本的名称。
- 当函数执行完成时,原来的命令行脚本的参数即可恢复。
- 函数的参数变量是在函数体里面定义的。
Shell 函数的基础实践
hello函数
[root@server ~ 16:40:47]# vim fun1.sh
#!/bin/bash
function hello () {echo "Hello World ! "
}
hello
[root@server ~ 16:42:35]# bash fun1.sh
Hello World !# 函数必须先定义,后调用
[root@server ~ 16:42:46]# vim fun2.sh
#!/bin/bash
hello
function hello () {echo "Hello World !"
}
[root@server ~ 16:43:18]# bash fun2.sh
fun2.sh: line 2: hello: command not found
调用外部函数
[root@server ~ 16:43:52]# cat >> mylib << 'EOF'
function hello () {echo "Hello World !"
}
EOF[root@server ~ 16:44:32]# vim fun3.sh
#!/bin/bash
if [ -r mylib ];thensource mylibhello
elseecho mylib is not exist exit 1
fi[root@server ~ 16:44:51]# bash fun3.sh
Hello World !
带参数的函数
[root@server ~ 16:47:12]# vim fun4.sh
#!/bin/bash
function print () {if [ "$1" == "PASS" ];thenecho -e '\033[1;32mPASS\033[0;39m'elif [ "$1" == "FAIL" ];thenecho -e '\033[1;31mFAIL\033[0;39m'elif [ "$1" == "DONE" ];thenecho -e '\033[1;35mDONE\033[0;39m'elseecho "Usage: print PASS|FAIL|DONE"fi
}
read -p "请输入你想要打印的内容:" str
print $str[root@server ~ 16:47:51]# bash fun4.sh
请输入你想要打印的内容:`PASS`
PASS
[root@server ~ 16:48:04]# bash fun4.sh
请输入你想要打印的内容:hello
Usage: print PASS|FAIL|DONE
函数的递归调用
求1+2+3+…+10 的和
[root@server ~ 16:49:16]# vim sum
#!/bin/bash
function sum () {
if [ $1 == 1 ];thensum=1
elsesum=$[ $1 + $(sum $[ $1 - 1 ] ) ]
fi
echo $sum
}
read -p "请输入一个你想计算和的整数:" num
sum $num
求1*2*3*…*10 的阶乘
[root@server ~ 16:49:30]# vim factorial
#!/bin/bash
function fact () {
if [ $1 == 1 ];thenfact=1
elsefact=$[ $1 * $(fact $[ $1 - 1 ] ) ]
fi
echo $fact
}
read -p "输入一个你想计算阶乘的整数:" num
fact $num
fork 炸弹
:(){ :|:& };:
解释如下:
:() # 定义函数,函数名为":",即每当输入":"时就会自动调用{}内代码
{ # ":"函数起始字元: # 用递归方式调用":"函数本身| # 使用管道一次产生两个函数调用: # 另一次递归调用的":"函数& # 放后台运行,以便产生更多的子进程
} # ":"函数终止
; # ":"函数定义结束后将要进行的操作...
: # 调用":"函数,"引爆"fork炸弹
fork 炸弹原因:无限制的启用新进程,直到消耗完所有计算资源。
解决办法:限制用户进程数量。
例如:限制100个进程
[root@server ~ 16:49:41]# ulimit -u 100
case 条件语句的应用实践
case 条件语句相当于多分支的if/elif/else条件语句,但是它比这些条件语句看起来更规范更工整,常被应用于实现系统服务启动脚本等企业应用场景中。
在case语句中,程序会将case获取的变量的值与表达式部分的值1、值2、值3等逐个进行比较:
- 如果获取的变量值和某个值(例如值1)相匹配,就会执行值(例如值1)后面对应的指令(例如指令1,其可能是一组指令),直到执行到双分号(;;)才停止,然后再跳出case语句主体,执行case语句(即esac字符)后面的其他命令。
- 如果没有找到匹配变量的任何值,则执行“*)”后面的指令(通常是给使用者的使用提示),直到遇到双分号(此处的双分号可以省略)或esac结束,这部分相当于if多分支语句中最后的else语句部分。
- 另外,case语句中表达式对应值的部分,还可以使用管道等更多功能来匹配。
case 条件语句实践
判断用户输入的数字是否是1、2、3。
[root@server ~ 16:50:43]# vim case1.sh
#!/bin/bash
read -p "请输入一个1-3之间数字:" num
case $num in1)echo "您输入的数字是:$num";;2)echo "您输入的数字是:$num";;3)echo "您输入的数字是:$num";;*)echo "请输入一个1-3之间数字。";;
esac# 执行验证
[root@server ~ 16:53:48]# bash case1.sh
请输入一个1-3之间数字:2
您输入的数字是:2
[root@server ~ 16:54:06]# bash case1.sh
请输入一个1-3之间数字:6
请输入一个1-3之间数字。
case 语句企业级生产案例
控制sshd服务
[root@server ~ 16:56:09]# vim sshd
#!/bin/bash
case $1 instart)systemctl $1 sshd;;stop)systemctl $1 sshd;;status)systemctl $1 sshd;;*)echo "Usage: $0 start|stop|status";;
esac
管理用户
通过传参的方式往 /etc/users 里添加用户,具体要求如下。
命令用法为:
Usage: user-mgr [ [-add|-a ] | [ -d|-del ] | [ -s|-search ] ] username
传参要求为:
- 参数为-add|-a时,表示添加后面接的用户名。如果有同名的用户, 则不能添加。
- 参数为-del|-d时,表示删除后面接的用户名。如果用户不存在,提示用户不存在。
- 参数为-search|-s时,表示查找后面接的用户名。 如果用户不存在,提示用户不存在。
- 没有用户时应给出明确提示。
/etc/users 保存用户清单,格式如下:
username: wan username: wang
/etc/users 不能被外部其他程序直接删除及修改。
[root@server ~ 16:58:19]# vim user
#!/bin/bash[ $UID -ne 0 ] && echo 'Please run as root' && exit 1users_info_file=/etc/users
[ -f ${users_info_file} ] || touch ${users_info_file}if [ $# -ne 2 ];thenecho "Usage: user-mgr [ [-add|-a ] | [ -d|-del ] | [ -s|-search ] ] username"exit 2
fiaction=$1
username=$2case $action in-s|-search)if grep -q "username: $username" ${users_info_file};thenecho "$username is exist."
else
"user" 47L, 1109C
#!/bin/bash[ $UID -ne 0 ] && echo 'Please run as root' && exit 1users_info_file=/etc/users
[ -f ${users_info_file} ] || touch ${users_info_file}if [ $# -ne 2 ];thenexit 2
fiaction=$1
username=$2case $action in-s|-search)if grep -q "username: $username" ${users_info_file};thenecho "$username is exist."
elseecho "$username is not exist."
fi
;;echo "$username is exist."
else
#!/bin/bash[ $UID -ne 0 ] && echo 'Please run as root' && exit 1users_info_file=/etc/users
[ -f ${users_info_file} ] || touch ${users_info_file}if [ $# -ne 2 ];thenecho "Usage: user-mgr [ [-add|-a ] | [ -d|-del ] | [ -s|-search ] ] username"exit 2
fiaction=$1
username=$2case $action in-s|-search)if grep -q "username: $username" ${users_info_file};thenecho "$username is exist."
else
执行验证
# 查找用户
[root@server ~ 16:59:10]# bash user -s wan
wan is not exist.# 添加用户
[root@server ~ 16:59:30]# bash user -a wan
wan has been added.
[root@server ~ 16:59:45]# bash user -s wan
wan is exist.
[root@server ~ 16:59:47]# cat /etc/users
username: wan# 删除用户
[root@server ~ 17:00:24]# bash user -d wan
wan has been deleted.
[root@server ~ 17:00:36]# cat /etc/users# 其他测试:普通用户执行
[wan@server ~ 17:01:10]$ bash /root/user
bash: /root/user: 权限不够
本章小结
- case 语句比较适合变量值较少且为固定的数字或字符串集合的情况,如果变量的值是已知固定的start/stop/restart 等元素,那么采用case语句来实现就比较适合。
- case语句和if条件语句的常见应用场景
- case主要是写服务的启动脚本,一般情况下,传参不同且具有少量的字符串,其适用范围较窄。
- if就是取值判断、比较,应用面比case更广。几乎所有的case语句都可以用if条件语句来实现。
- case语句就相当于多分支的if/elif/else语句,但是case语句的优势是更规范、易读。
while 循环和 until 循环的应用实践
循环语句命令常用于重复执行一条指令或一组指令,直到条件不再满足时停止,Shell脚本语言的循环语句常见的有while、until、for及select循环语句。
while 循环语句主要用来重复执行一组命令或语句,在企业实际应用中,常用于守护进程或持续运行的程序,除此以外,大多数循环都会用后文即将讲解的for循环语句。
当型和直到型循环的基本范例
竖向打印54321
while格式:
[root@server ~ 17:05:16]# vim while.sh
#!/bin/bash
i=5
while ((i>0))
doecho $i((i--))
done
until格式:
[root@server ~ 17:05:24]# vim until.sh
#!/bin/bash
i=5
until ((i==0))
doecho $i((i--))
done
计算1+2+3+…+99+100的和
[root@server ~ 17:06:18]# vim while1.sh
#!/bin/bash
i=1
sum=0
while ((i<=100))
do((sum+=i))# let sum=sum+i((i++))# let i++
done
echo "1+2+3+...+99+100=$sum"
计算5的阶乘
[root@server ~ 17:07:49]# vim while2.sh
#!/bin/bash
i=1
sum=1
while ((i<=5))
do((sum*=i))((i++))
done
echo "5的阶乘为:$sum"
猴子吃桃。
- 猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。
- 第二天早上又将第一天剩下的桃子吃掉一半,又多吃了一个。
- 以后每天早上都吃了前一天剩下的一半零一个。
- 到第 10 天早上想再吃时,发现只剩下一个桃子了。
问:一共有多少个桃子?
[root@server ~ 17:09:56]# vim peach.sh
#!/bin/bashpeach=1
i=1
day=1while ((i<10))
dopeach=$[ (peach+1) *2]let i++
doneecho "peach=$peach"
在实际工作中,一般会通过客户端SSH连接服务器,因此可能就会有在脚本或命令执行期间不能中断的需求,若中断,则会前功尽弃,更要命的是会破坏系统数据。
下面是防止脚本执行中断的几个可行方法:
使用
sh /server/scripts/while_01.sh &
命令,即使用&
在后台运行脚本。使用
nohup /server/scripts/uptime.sh &
命令,即使用nohup
加&在后台运行脚本。利用
screen
保持会话,然后再执行命令或脚本,即使用screen
保持当前会话状态。
让 Shell 脚本在后台运行的知识
脚本运行的相关用法和说明:
sh whilel.sh &
,把脚本whilel.sh放到后台执行(在后台运行脚本时常用的方法)。ctl+c
,停止执行当前脚本或任务。ctl+z
,暂停执行当前脚本或任务。bg
,把当前脚本或任务放到后台执行。bg可以理解为background。fg
,把当前脚本或任务放到前台执行,如果有多个任务,可以使用fg加任务编号调出对应的脚本任务,如fg 2
,调出第二个脚本任务。fg可以理解为frontground。jobs
,查看当前执行的脚本或任务。kill
,关闭执行的脚本任务,即以“kill %任务编号”的形式关闭脚本。任务编号,可以通过jobs命令获得。
企业生产实战:while 循环语句实践
需求:
后台监视sshd服务,一旦发现sshd服务未运行,就start sshd。
每隔3秒检测一次。不允许使用计划任务(cron)。
[root@server ~ 17:11:01]# vim while3.sh
#!/bin/bash
while true
dosystemctl is-active sshd &>/dev/nullreturn_count=$?if (( return_count!=0 ));thensystemctl start sshd elsesleep 3fi
done
后台检测sshd服务,如果未运行,则重启sshd服务。
while格式:
[root@server ~ 17:15:09]# vim while4.sh
#!/bin/bash
while true
do systemctl is-active sshd.service &>/dev/nullif [ $? == 0 ];then systemctl restart sshd.service &>/dev/nullfisleep 5
done
until格式:
[root@server ~ 17:16:49]# vim until2.sh
#!/bin/bash
until false
do systemctl is-active sshd.service &>/dev/nullif [ $? == 0 ];then systemctl restart sshd.service &>/dev/nullfisleep 5
done
本章小结
- while 循环结构及相关语句综合实践小结
- while 循环的特长是执行守护进程,以及实现我们希望循环持续执行不退出的应用,适合用于频率小于1分钟的循环处理,其他的while 循环几乎都可以被后面即将要讲到的for循环及定时任务crond功能所替代。
- case语句可以用if语句来替换,而在系统启动脚本时传入少量固定规则字符串的情况下,多用case语句,其他普通判断多用if语句。
- 一句话场景下,if语句、for语句最常用,其次是while(守护进程)、case(服务启动脚本)。
- Shell脚本中各个语句的使用场景
- 条件表达式,用于简短的条件判断及输出(文件是否存在,字符串是否为空等)
- if取值判断,多用于不同值数量较少的情况。
- for最常用于正常的循环处理中while多用于守护进程、无限循环(要加sleep和usleep控制频率)场景。
- case多用于服务启动脚本中,打印菜单可用select语句,不过很少见,一般用cat的here文档方法来替代。
- 函数的作用主要是使编码逻辑清晰,减少重复语句开发。.