Linux运维必备之shell脚本编写规范与执行方式详解
作者:着迷不白
一、Shell脚本概述
1.1 什么是Shell脚本
- Shell脚本本质上是一个文件,里面定义了多个Linux命令、变量以及逻辑控制(判断、循环等)
- 实现自动化运维,批量重复操作,减少管理员工作量
- 基于Linux命令,解释性语言,上手简单
1.2 学习Shell脚本的前提
- 熟悉Linux常用命令
- 熟悉常用服务的部署与配置(如Apache、Nginx、DNS等)
- 熟练文本处理工具:
grep、awk、sed - 熟悉Vim编辑器
二、Shell脚本编写规范
2.1 基本规范
- 文件后缀:以
.sh结尾 - 第一行声明解释器:
#!/bin/bash或#!/bin/sh - 注释:使用
#表示注释行,不参与执行 - 脚本开头建议包含信息:
#!/bin/bash # Author: 作者名 # Mail: 联系方式 # Date: 日期 # Describe: 脚本描述 # Warning: 警告信息 # Version: 版本号 # Modify: 修改记录
- 代码缩进:提高可读性
- 尽量不使用中文注释(避免编码问题)
2.2 快速生成注释模板(.vimrc配置)
在~/.vimrc中添加以下内容,新建.sh文件时自动生成头部注释:
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()"
func SetTitle()
if expand("%:e") == 'sh'
call setline(1,"#!/bin/bash")
call setline(2,"##############################################################")
call setline(3, "# File Name: ".expand("%"))
call setline(4, "# Version: V1.0")
call setline(5, "# Author: yourname")
call setline(6, "# Email: your@email.com")
call setline(7, "# Created Time : ".strftime("%F %T"))
call setline(8, "# Description:")
call setline(9,"##############################################################")
call setline(10, "")
endif
endfunc2.3 注释方式
- 单行注释:以
#开头 - 多行注释:使用
<<EOF ... EOF(EOF可替换为其他标识)
<<EOF 这是多行注释 不会被执行 EOF
三、Shell脚本的执行方式
3.1 四种执行方式
| 方式 | 语法 | 是否需要x权限 | 执行环境 |
| 相对路径 | ./script.sh | 需要 | 子Shell |
| 绝对路径 | /root/script.sh | 需要 | 子Shell |
| source或点 | source script.sh 或 . script.sh | 不需要(需要r权限) | 当前Shell |
| bash/sh | bash script.sh 或 sh script.sh | 不需要(需要r权限) | 子Shell |
3.2 注意事项
- 子Shell vs 当前Shell:在子Shell中定义的变量、环境更改不会影响父Shell;使用
source或.执行时,变量会在当前Shell中生效 - 相对/绝对路径执行需要给脚本添加执行权限:
chmod a+x script.sh
四、变量与参数
4.1 位置化变量(位置参数)
用于在执行脚本时传入参数:
| 变量 | 含义 |
$0 | 脚本文件本身的名字 |
$1 | 第一个参数 |
$2 | 第二个参数 |
| ... | ... |
${10} | 第十个及以上参数需用花括号 |
示例:创建用户并设置密码的脚本
#!/bin/bash # useradd.sh useradd $1 echo $2 | passwd --stdin $1
执行:./useradd.sh zhangsan redhat
4.2 预定义变量
| 变量 | 含义 |
$? | 上一条命令的退出状态码(0成功,非0失败) |
$# | 传递给脚本的参数个数 |
$* | 所有参数(作为一个字符串) |
$@ | 所有参数(每个作为独立字符串) |
$$ | 当前Shell进程的PID |
4.3 read命令 – 从键盘读取输入
语法:read [选项] [变量名]
| 选项 | 说明 |
-p "提示信息" | 输出提示信息 |
-s | 隐藏输入(常用于密码) |
-t 秒数 | 超时自动跳过 |
| 不指定变量名 | 数据存入环境变量REPLY |
示例:
#!/bin/bash read -p "请输入用户名: " username read -s -p "请输入密码: " password useradd $username echo $password | passwd --stdin $username echo "$username 创建成功,密码已设置"
4.4 exit命令
- 直接执行
exit会退出当前终端或脚本 - 在脚本中遇到
exit立即终止脚本 exit n:指定退出码(n为数字,0表示成功,1-255表示失败)- 退出码可通过
echo $?查看
示例:检查密码中是否包含连续数字12345,若包含则退出
#!/bin/bash
read -p "请输入密码: " pass
echo $pass | grep 12345 -q
if [ $? -eq 0 ]; then
echo "密码不能包含12345"
exit 1
fi
echo "密码合法"五、逻辑运算符与复合指令
5.1 逻辑运算符
| 运算符 | 含义 | 规则 |
&& | 逻辑与 | 前一条命令成功(返回0)才执行后一条 |
| ` | ` | |
! | 逻辑非 | 对返回状态取反 |
示例:
# 创建用户并设置密码 useradd luojiyu && echo 1 | passwd --stdin luojiyu # 如果用户存在则修改密码,否则创建 useradd devops || echo 1 | passwd --stdin devops # 判断IP是否通,根据结果写入不同文件 ping -c4 10.10.10.1 &>/dev/null && echo 10.10.10.1 > /opt/ip-ok.txt || echo 10.10.10.1 > /opt/ip-error.txt
5.2 复合指令
; :顺序执行多条命令,无条件执行所有命令
command1; command2; command3
(命令;命令) :在子Shell中执行,括号开头不需要空格,最后命令不用分号
(cd /tmp; pwd)
{ 命令;命令; } :在当前Shell中执行,花括号开头必须有空格,每条命令以分号结尾
{ cd /tmp; pwd; } 六、条件测试语句
条件测试用于判断表达式的真假,返回值为0表示真,非0表示假。
6.1 三种测试语法
| 语法 | 说明 |
test 条件表达式 | 常用在终端临时测试 |
[ 条件表达式 ] | 脚本中常用,注意空格 |
[[ 条件表达式 ]] | 支持正则和通配符,更强大 |
示例:
test root = 123 ; echo $? # 返回1 [ 1 -eq 1 ]; echo $? # 返回0 [[ "abc" == a* ]]; echo $? # 支持通配符,返回0
6.2 文件属性运算符
| 运算符 | 含义 |
-e 文件 | 文件存在 |
-f 文件 | 是普通文件 |
-d 文件 | 是目录 |
-s 文件 | 文件非空 |
-r 文件 | 可读 |
-w 文件 | 可写 |
-x 文件 | 可执行 |
-L 文件 | 是链接文件 |

示例:
# 如果/tmp/passwd不存在则拷贝 [ -e /tmp/passwd ] || cp /etc/passwd /tmp/passwd # 如果useradd有执行权限则拷贝并修改所有者 [ -x /usr/sbin/useradd ] && cp /usr/sbin/useradd /tmp && chown devops /tmp/useradd # 如果文件没有读权限则添加 [ -r /tmp/passwd ] || chmod a+r /tmp/passwd
6.3 数字比较运算符
| 运算符 | 含义 |
-eq | 等于 |
-ne | 不等于 |
-gt | 大于 |
-ge | 大于等于 |
-lt | 小于 |
-le | 小于等于 |
注意:不要使用数学符号>、<等在条件测试中,它们会被解释为重定向
示例:判断根分区使用率是否超过10%
disk_num=$(df | grep -w / | awk '{print $5}' | tr -d %)
if [ $disk_num -ge 10 ]; then
rm -rf /tmp/*
echo "tmp目录已清空"
else
echo "disk free,使用率: ${disk_num}%"
fi6.4 字符串比较运算符
| 运算符 | 含义 |
= 或 == | 相等 |
!= | 不等 |
-z 字符串 | 字符串为空(长度0) |
-n 字符串 | 字符串非空 |
示例:
# 判断输入是否为root read -p "输入用户名: " name [ "$name" = "root" ] && echo "UID: $(id -u root)" || exit # 检查参数是否为空 [ -z $1 ] && echo "请传入参数" || echo "参数是: $1"
6.5 布尔运算符(用于[ ]中)
| 运算符 | 含义 |
-a | 逻辑与(AND) |
-o | 逻辑或(OR) |
! | 逻辑非 |
在[[ ]]中可使用&&、||、!更直观。
示例:
# 用户名是zhangsan 且 UID>=1000 [ "$username" = "zhangsan" -a $useruid -ge 1000 ] && echo ok || echo error # 使用[[ ]] [[ "$username" = "zhangsan" && $useruid -ge 1000 ]] && echo ok
七、判断语句
7.1 if单分支
语法:
if 条件表达式; then
执行语句
fi
# 第二种写法
if 条件表达式
then
执行语句
fi示例:用户已存在则退出
#!/bin/bash
read -p "输入用户名: " name
if id $name &>/dev/null; then
echo "$name 已存在,不能创建"
exit
fi
useradd $name7.2 if双分支
语法:
if 条件表达式; then
语句1
else
语句2
fi示例:用户存在则重置密码,否则创建
#!/bin/bash
read -p "输入用户名: " name
if id $name &>/dev/null; then
read -p "重置密码: " pass
echo $pass | passwd --stdin $name
else
useradd $name
echo 1 | passwd --stdin $name
fi示例:备份脚本(判断目录是否存在)
#!/bin/bash
read -p "要备份的文件路径: " src
read -p "备份目录路径: " dst
if [ -d "$dst" ]; then
chmod 777 $dst
else
mkdir $dst
fi
cp -a $src $dst/$(basename $src)-$(date +%F).bak7.3 if多分支
语法:
if 条件1; then
语句1
elif 条件2; then
语句2
else
语句3
fi示例:成绩等级判断
read -p "请输入成绩(0-100): " score
if [ $score -ge 85 ]; then
echo "优秀 - A"
elif [ $score -ge 70 ]; then
echo "良好 - B"
elif [ $score -ge 60 ]; then
echo "合格 - C"
else
echo "不合格 - D"
fi7.4 case多分支
用于多值匹配,比多个elif更清晰。
语法:
case 变量 in
模式1)
命令
;;
模式2)
命令
;;
*)
默认命令
;;
esac示例:成绩等级(case版本)
read -p "请输入成绩(0-100): " score
case $score in
8[5-9]|9[0-9]|100)
echo "优秀 - A"
;;
7[0-9]|8[0-4])
echo "良好 - B"
;;
6[0-9])
echo "合格 - C"
;;
*)
echo "不合格 - D"
;;
esac模式支持:
|表示或[0-9]表示范围*表示任意
八、循环语句
8.1 for循环
语法:
for 变量名 in 列表; do
命令
done四种列表生成方式:
# 1. 直接列出
for i in 1 2 3 4 5; do echo $i; done
# 2. 大括号(支持数字和字母)
for i in {1..5}; do echo $i; done
for c in {a..z}; do echo $c; done
# 3. seq命令
for i in $(seq 1 5); do echo $i; done
# 4. 命令执行结果
for file in $(ls *.sh); do echo $file; doneC语言风格for循环:
for ((i=1; i<=10; i++)); do
echo $i
done示例1:批量创建用户(从文件读取)
for user in $(cat userlist.txt); do
useradd $user
echo 123456 | passwd --stdin $user
done示例2:扫描局域网在线主机
for i in {1..254}; do
ip="192.168.1.$i"
ping -c2 -W1 $ip &>/dev/null
if [ $? -eq 0 ]; then
echo "$ip is up"
else
echo "$ip is down"
fi
done8.2 while循环
当条件为真时循环,条件为假时退出。
语法:
while 条件表达式; do
命令
done示例:读取文件每一行(三种方式)
# 方式1:exec重定向
exec < file.txt
while read line; do
echo $line
done
# 方式2:管道
cat file.txt | while read line; do
echo $line
done
# 方式3:重定向结尾(推荐)
while read line; do
echo $line
done < file.txt示例:从iplist文件读取IP和端口
# iplist内容:
# 192.168.1.101 22
# 192.168.1.102 80
while read line; do
IP=$(echo $line | awk '{print $1}')
PORT=$(echo $line | awk '{print $2}')
echo "IP: $IP, PORT: $PORT"
done < iplist8.3 until循环
与while相反:条件为假时进入循环,为真时退出。
语法:
until 条件表达式; do
命令
done示例:等待某个文件出现
until [ -f /tmp/ready ]; do
echo "等待文件..."
sleep 2
done
echo "文件已出现"8.4 break和continue
break:立即跳出当前循环continue:跳过本次循环剩余语句,进入下一次迭代
示例:
# break: 当i=4时跳出
for i in {1..10}; do
if [ $i -eq 4 ]; then
break
fi
echo $i
done
# 输出: 1 2 3
# continue: 当i=4时跳过
for i in {1..10}; do
if [ $i -eq 4 ]; then
continue
fi
echo $i
done
# 输出: 1 2 3 5 6 7 8 9 10九、综合实践案例
9.1 一键部署Nginx脚本框架
#!/bin/bash
# 检查是否root用户
if [ $EUID -ne 0 ]; then
echo "请使用root用户执行"
exit 1
fi
# 安装依赖
yum install -y gcc make pcre-devel zlib-devel
# 下载并编译安装Nginx
cd /usr/local/src
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar xf nginx-1.24.0.tar.gz
cd nginx-1.24.0
./configure --prefix=/usr/local/nginx
make && make install
# 启动
/usr/local/nginx/sbin/nginx
echo "Nginx部署完成"9.2 安全创建用户脚本
#!/bin/bash
read -p "请输入用户名: " username
if id $username &>/dev/null; then
echo "用户已存在"
exit 1
fi
read -s -p "请输入密码: " password
read -s -p "确认密码: " password2
echo
if [ "$password" != "$password2" ]; then
echo "两次密码不一致"
exit 1
fi
useradd $username
echo $password | passwd --stdin $username &>/dev/null
echo "用户 $username 创建成功"9.3 系统监控脚本
#!/bin/bash
# 监控CPU、内存、磁盘使用率
cpu=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
mem=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100}')
disk=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
echo "CPU使用率: $cpu%"
echo "内存使用率: $mem%"
echo "根分区使用率: $disk%"
if [ $disk -gt 80 ]; then
echo "警告:磁盘空间不足"
fi十、总结
| 知识点 | 核心要点 |
| 编写规范 | #!/bin/bash, 注释, .sh后缀 |
| 执行方式 | 路径需x权限;source在当前shell;其他在子shell |
| read | -p提示,-s隐藏,-t超时 |
| 条件测试 | [ ],文件 -f/-d,数字 -eq/-gt,字符串 =/-z |
| 逻辑符 | &&(成功则执行),||(失败则执行) |
| 判断 | if单/双/多分支,case多值匹配 |
| 循环 | for(列表/C风格),while(条件真循环),until(条件假循环) |
| 控制 | break跳出,continue跳过本次 |
掌握以上内容,即可编写大部分日常运维所需的Shell脚本。实践中多动手、多调试,利用set -x调试模式查看执行过程。
以上就是Linux运维必备之shell脚本编写规范与执行方式详解的详细内容,更多关于Linux shell脚本的资料请关注脚本之家其它相关文章!
