Shell脚本是Linux运维的"效率神器"------把重复的命令(如批量部署、日志清理、数据备份)写成脚本,一键执行就能替代手动操作,大幅提升工作效率。本文从零基础入手,详解Shell脚本核心语法:变量、条件判断(if-else)、循环(for/while)、函数,每个知识点搭配可直接运行的实战案例,覆盖搜索引擎高频检索需求(如Shell变量定义、if-else条件判断、for循环批量操作、Shell函数封装),适合运维新手快速上手。
一、Shell脚本入门:先搞懂"基础规则"
1. 什么是Shell脚本?
Shell脚本是"一系列Linux命令的集合",以.sh为后缀,通过解释器(默认bash)执行。简单来说:把你在终端手动敲的命令,按顺序写到文件里,添加执行权限后就能批量运行。
2. 脚本基本结构(必写3要素)
新建一个test.sh脚本,核心结构如下:
bash
#!/bin/bash # 声明解释器(必须写在第一行,指定用bash执行)
# 这是注释(#开头的行不会执行,用于说明脚本功能)
echo "Hello Shell!" # 执行的命令(输出字符串)
3. 脚本执行方式(3种常用)
bash
# 方式1:赋予执行权限后直接运行(推荐)
chmod +x test.sh # 添加可执行权限
./test.sh # 执行脚本(./表示当前目录)
# 方式2:通过bash解释器执行(无需执行权限)
bash test.sh
# 方式3:在当前Shell环境执行(会影响当前终端,慎用)
source test.sh
. test.sh # 与source等价
二、变量:Shell脚本的"数据容器"
变量是存储数据的载体,Shell脚本中变量无需声明类型,直接赋值即可,核心分为"自定义变量""系统变量""位置参数变量"三类。
1. 自定义变量(最常用)
(1)定义与赋值
语法:变量名=值(注意:等号两边无空格,空格会被识别为命令分隔)
bash
#!/bin/bash
# 定义变量(字符串、数字均可)
name="张三"
age=28
score=95.5
# 输出变量($变量名 或 ${变量名},{}用于区分变量边界)
echo "姓名:$name"
echo "年龄:${age}岁"
echo "分数:${score}"
(2)变量赋值进阶
bash
#!/bin/bash
# 1. 命令结果赋值(反引号`` 或 $(),推荐$())
ip=$(ifconfig eth0 | grep inet | awk '{print $2}') # 获取eth0网卡IP
echo "服务器IP:$ip"
# 2. 读取用户输入赋值(read命令)
read -p "请输入你的手机号:" phone # -p:提示信息
echo "你输入的手机号是:$phone"
# 3. 变量拼接
prefix="file_"
suffix=".log"
filename=${prefix}2025${suffix} # 拼接为file_2025.log
echo "文件名:$filename"
2. 系统变量(内置变量,直接用)
Shell内置了常用系统变量,无需定义,直接调用即可:
| 变量名 | 作用 | 示例 |
|---|---|---|
| $0 | 脚本文件名 | echo "脚本名:$0" → 输出test.sh |
| <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 − 1- </math>1−9 | 脚本位置参数(执行脚本时传入的参数) | ./test.sh 10 20 → <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 = 10 , 1=10, </math>1=10,2=20 |
| $# | 位置参数的个数 | ./test.sh 10 20 → $#=2 |
| $? | 上一条命令的执行状态(0=成功,非0=失败) | ls /tmp; echo $? → 输出0 |
| $USER | 当前登录用户 | echo "当前用户:$USER" |
| $PWD | 当前工作目录 | echo "当前路径:$PWD" |
实战示例(位置参数):
bash
#!/bin/bash
# 脚本名:param.sh
echo "脚本名:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "参数总数:$#"
# 执行:./param.sh 北京 上海
# 输出:
# 脚本名:./param.sh
# 第一个参数:北京
# 第二个参数:上海
# 参数总数:2
3. 变量使用注意事项
- 变量名只能包含字母、数字、下划线,且不能以数字开头(如
1name非法,name1合法); - 全局变量vs局部变量:默认定义的变量是全局的,函数内用
local声明局部变量; - 特殊字符转义:变量值包含空格/特殊字符时,用双引号包裹(如
path="/usr/local/bin")。
三、条件判断(if-else):让脚本"做选择"
条件判断是脚本的"逻辑核心"------根据条件(如文件是否存在、数值大小、字符串是否相等)执行不同命令,核心语法是if-else。
1. 基础语法结构
(1)单分支(满足条件才执行)
bash
if [ 条件表达式 ]; then
执行的命令
fi # 结束if(必须写)
(2)双分支(满足/不满足分别执行)
bash
if [ 条件表达式 ]; then
满足条件执行的命令
else
不满足条件执行的命令
fi
(3)多分支(多个条件判断)
bash
if [ 条件1 ]; then
条件1满足执行的命令
elif [ 条件2 ]; then
条件2满足执行的命令
else
所有条件都不满足执行的命令
fi
2. 条件表达式(核心:判断什么?)
条件表达式是if的"判断依据",常用3类判断:数值比较、字符串比较、文件判断。
(1)数值比较(整数)
| 运算符 | 作用 | 示例(判断10是否大于5) |
|---|---|---|
| -eq | 等于 | [ 10 -eq 5 ] → 假 |
| -ne | 不等于 | [ 10 -ne 5 ] → 真 |
| -gt | 大于 | [ 10 -gt 5 ] → 真 |
| -lt | 小于 | [ 10 -lt 5 ] → 假 |
| -ge | 大于等于 | [ 10 -ge 10 ] → 真 |
| -le | 小于等于 | [ 10 -le 15 ] → 真 |
实战示例(判断分数是否及格):
bash
#!/bin/bash
read -p "请输入考试分数:" score
if [ $score -ge 60 ]; then
echo "及格!"
elif [ $score -ge 80 ]; then
echo "优秀!"
else
echo "不及格!"
fi
(2)字符串比较
| 运算符 | 作用 | 示例 |
|---|---|---|
| = | 等于 | [ "abc" = "abc" ] → 真 |
| != | 不等于 | [ "abc" != "def" ] → 真 |
| -z | 字符串为空 | [ -z "$str" ] → 字符串为空时真 |
| -n | 字符串非空 | [ -n "$str" ] → 字符串非空时真 |
实战示例(判断用户输入是否为空):
bash
#!/bin/bash
read -p "请输入你的姓名:" name
if [ -z "$name" ]; then
echo "错误:姓名不能为空!"
elif [ "$name" = "admin" ]; then
echo "欢迎管理员!"
else
echo "你好,$name!"
fi
(3)文件判断(运维高频)
| 运算符 | 作用 | 示例(判断/tmp/test.log) |
|---|---|---|
| -f | 是否为普通文件 | [ -f /tmp/test.log ] → 文件存在且是普通文件则真 |
| -d | 是否为目录 | [ -d /tmp ] → 目录存在则真 |
| -e | 文件/目录是否存在 | [ -e /tmp/test.log ] → 存在则真 |
| -r | 是否有读权限 | [ -r /tmp/test.log ] → 有读权限则真 |
| -w | 是否有写权限 | [ -w /tmp/test.log ] → 有写权限则真 |
| -x | 是否有执行权限 | [ -x /tmp/script.sh ] → 有执行权限则真 |
实战示例(判断文件是否存在,不存在则创建):
bash
#!/bin/bash
file="/tmp/test.log"
if [ -f "$file" ]; then
echo "$file已存在,文件大小:$(du -h $file | awk '{print $1}')"
else
echo "$file不存在,正在创建..."
touch $file # 创建文件
echo "创建成功!"
fi
3. 条件判断避坑点
- 条件表达式的
[ ]两边必须有空格(如[ $score -ge 60 ],少空格会报错); - 变量值可能为空时,用双引号包裹(如
[ -z "$name" ],避免[ -z ]语法错误); - 多个条件组合:用
&&(且)、||(或),示例:if [ $age -gt 18 ] && [ $age -lt 60 ]; then。
四、循环:让脚本"重复做事"
循环是脚本的"效率核心"------批量处理文件、遍历服务器列表、重复执行命令都靠循环,Shell常用for和while循环。
1. for循环(遍历列表/范围)
(1)基础语法(遍历列表)
bash
for 变量 in 列表; do
执行的命令
done
实战示例1(遍历字符串列表):
bash
#!/bin/bash
# 遍历城市列表
cities="北京 上海 广州 深圳"
for city in $cities; do
echo "当前城市:$city"
done
# 输出:
# 当前城市:北京
# 当前城市:上海
# 当前城市:广州
# 当前城市:深圳
实战示例2(遍历数字范围):
bash
#!/bin/bash
# 遍历1-5(批量创建文件)
for i in {1..5}; do
touch /tmp/file_$i.txt # 创建file_1.txt到file_5.txt
echo "已创建:/tmp/file_$i.txt"
done
实战示例3(遍历文件(运维高频)):
bash
#!/bin/bash
# 遍历/tmp下所有.log文件,统计大小
for log in /tmp/*.log; do
if [ -f "$log" ]; then # 确保是文件(避免目录)
size=$(du -h $log | awk '{print $1}')
echo "$log → 大小:$size"
fi
done
(2)C风格for循环(数值循环)
bash
for (( 初始值; 条件; 步长 )); do
执行的命令
done
示例(计算1-100的和):
bash
#!/bin/bash
sum=0
for (( i=1; i<=100; i++ )); do
sum=$((sum + i)) # 数值计算(双括号)
done
echo "1-100的和:$sum" # 输出5050
2. while循环(条件满足就循环)
(1)基础语法
bash
while [ 条件表达式 ]; do
执行的命令
done
实战示例1(循环读取文件内容):
bash
#!/bin/bash
# 读取/etc/passwd的每一行(逐行处理)
file="/etc/passwd"
while read line; do
# 提取用户名(第一列,:分隔)
username=$(echo $line | cut -d: -f1)
echo "用户名:$username"
done < $file # < 表示从文件读取内容
实战示例2(无限循环+手动终止):
bash
#!/bin/bash
# 每隔5秒检查nginx服务是否运行
while true; do # true表示无限循环
if [ $(ps -ef | grep nginx | grep -v grep | wc -l) -eq 0 ]; then
echo "nginx已停止,正在重启..."
systemctl start nginx
else
echo "nginx运行正常($(date))"
fi
sleep 5 # 休眠5秒
done
# 终止方式:Ctrl+C
3. 循环控制(break/continue)
break:终止整个循环(跳出循环);continue:跳过当前循环,执行下一次循环。
示例(遍历1-10,遇到5终止循环):
bash
#!/bin/bash
for i in {1..10}; do
if [ $i -eq 5 ]; then
break # 终止循环
fi
echo "当前数字:$i"
done
# 输出1-4
示例(遍历1-10,跳过5):
bash
#!/bin/bash
for i in {1..10}; do
if [ $i -eq 5 ]; then
continue # 跳过5,继续下一次
fi
echo "当前数字:$i"
done
# 输出1-4、6-10
五、函数:让脚本"模块化"
函数是把重复的代码块封装成"可调用的命令",减少脚本冗余,方便维护------比如把"备份数据库""检查服务状态"封装成函数,按需调用。
1. 函数定义与调用
(1)基础语法
bash
# 定义函数
函数名() {
代码块
return 数值 # 返回值(可选,默认返回最后一条命令的执行状态)
}
# 调用函数
函数名 参数1 参数2
(2)实战示例(封装备份函数)
bash
#!/bin/bash
# 定义备份函数(参数1:备份目录,参数2:备份文件名)
backup_dir() {
local src_dir=$1 # 局部变量(仅函数内有效)
local backup_file=$2
if [ -d "$src_dir" ]; then
tar -zcf /tmp/$backup_file.tar.gz $src_dir
echo "备份成功:/tmp/$backup_file.tar.gz"
return 0 # 成功返回0
else
echo "错误:目录$src_dir不存在!"
return 1 # 失败返回1
fi
}
# 调用函数(备份/var/log目录)
backup_dir /var/log log_backup_2025
# 调用函数(备份/home目录)
backup_dir /home home_backup_2025
2. 函数参数与返回值
- 函数参数:调用时传入的参数,用
$1、$2获取(与脚本位置参数用法一致); - 函数返回值:
- 用
return返回(仅能返回0-255的整数,0=成功,非0=失败); - 若需返回字符串/数值,用
echo输出,调用时用变量=$(函数名)接收。
- 用
示例(返回数值计算结果):
bash
#!/bin/bash
# 定义加法函数
add() {
local a=$1
local b=$2
echo $((a + b)) # 用echo返回结果
}
# 调用函数,接收返回值
result=$(add 10 20)
echo "10+20=$result" # 输出30
3. 函数实战(运维常用)
封装"检查端口是否开放"函数:
bash
#!/bin/bash
# 定义检查端口函数(参数1:IP,参数2:端口)
check_port() {
local ip=$1
local port=$2
# 使用nc命令检查端口(需安装nc:yum install -y nc)
nc -z -w 2 $ip $port > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "$ip:$port 开放"
return 0
else
echo "$ip:$port 关闭"
return 1
fi
}
# 批量检查端口
check_port 192.168.1.100 80
check_port 192.168.1.100 3306
check_port 192.168.1.100 6379
六、Shell脚本实战案例(综合运用)
案例1:批量创建用户并设置密码
bash
#!/bin/bash
# 脚本名:create_users.sh
# 功能:批量创建用户,密码统一为123456(生产环境需改为随机密码)
# 定义用户列表
users="user1 user2 user3 user4"
# 循环创建用户
for user in $users; do
# 检查用户是否已存在
if id $user > /dev/null 2>&1; then
echo "$user已存在,跳过..."
continue
fi
# 创建用户
useradd $user
# 设置密码(echo "密码" | passwd --stdin 用户名)
echo "123456" | passwd --stdin $user > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "$user创建成功,密码:123456"
else
echo "$user创建失败!"
fi
done
案例2:日志清理脚本(保留7天内日志)
bash
#!/bin/bash
# 脚本名:clean_logs.sh
# 功能:删除/tmp下7天前的.log文件
log_dir="/tmp"
# 遍历7天前的.log文件
find $log_dir -name "*.log" -mtime +7 -type f | while read log_file; do
# 删除文件
rm -f $log_file
echo "已删除:$log_file"
done
echo "日志清理完成!"
七、Shell脚本避坑指南(新手必看)
- 语法错误 :
if的[ ]两边加空格、变量赋值等号无空格、循环/函数结尾的done/fi/}必须写; - 路径问题 :脚本中尽量用绝对路径(如
/tmp/test.log),避免相对路径导致执行失败; - 权限问题:脚本执行时需有对应权限(如创建文件需写权限,删除文件需删权限);
- 特殊字符 :处理包含空格的文件名/路径时,用双引号包裹(如
[ -f "$file" ]); - 日志输出 :重要操作添加日志(如
echo "[$(date)] 备份失败" >> /tmp/backup.log),方便排查问题; - 测试先行 :新脚本先在测试环境运行,用
bash -x 脚本名调试(显示执行过程)。
总结
Shell脚本的核心是"把复杂操作简单化、重复操作自动化"------变量存储数据,条件判断实现逻辑分支,循环处理批量任务,函数封装复用代码。学习时无需死记语法,重点是"多写多练":先从简单脚本(如批量创建文件、检查服务状态)入手,再逐步实现复杂功能(如自动化备份、批量部署)。
掌握这些基础语法后,可结合运维场景扩展(如结合crontab定时执行脚本、结合SSH批量管理服务器),真正实现"一键运维",大幅提升工作效率。