Shell脚本基础教程:变量、条件判断、循环、函数实战(附案例)

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常用forwhile循环。

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脚本避坑指南(新手必看)

  1. 语法错误if[ ]两边加空格、变量赋值等号无空格、循环/函数结尾的done/fi/}必须写;
  2. 路径问题 :脚本中尽量用绝对路径(如/tmp/test.log),避免相对路径导致执行失败;
  3. 权限问题:脚本执行时需有对应权限(如创建文件需写权限,删除文件需删权限);
  4. 特殊字符 :处理包含空格的文件名/路径时,用双引号包裹(如[ -f "$file" ]);
  5. 日志输出 :重要操作添加日志(如echo "[$(date)] 备份失败" >> /tmp/backup.log),方便排查问题;
  6. 测试先行 :新脚本先在测试环境运行,用bash -x 脚本名调试(显示执行过程)。

总结

Shell脚本的核心是"把复杂操作简单化、重复操作自动化"------变量存储数据,条件判断实现逻辑分支,循环处理批量任务,函数封装复用代码。学习时无需死记语法,重点是"多写多练":先从简单脚本(如批量创建文件、检查服务状态)入手,再逐步实现复杂功能(如自动化备份、批量部署)。

掌握这些基础语法后,可结合运维场景扩展(如结合crontab定时执行脚本、结合SSH批量管理服务器),真正实现"一键运维",大幅提升工作效率。

相关推荐
无心水2 小时前
【Python实战进阶】5、Python字符串终极指南:从基础到高性能处理的完整秘籍
开发语言·网络·python·字符串·unicode·python实战进阶·python工业化实战进阶
2301_807583232 小时前
了解python,并编写第一个程序,常见的bug
linux·python
小白学大数据2 小时前
构建混合爬虫:何时使用Requests,何时切换至Selenium处理请求头?
爬虫·python·selenium·测试工具
2401_827560202 小时前
【Python脚本系列】PyAudio+librosa+dtw库录制、识别音频并实现点击(四)
python·语音识别
BBB努力学习程序设计2 小时前
Python自动化脚本:告别重复劳动
python·pycharm
BBB努力学习程序设计2 小时前
Python函数式编程:优雅的代码艺术
python·pycharm
2501_940943912 小时前
体系课\ Python Web全栈工程师
开发语言·前端·python
田姐姐tmner3 小时前
Python切片
开发语言·python
t***31653 小时前
爬虫学习案例3
爬虫·python·学习