CentOS Stream 9入门学习教程,从入门到精通,CentOS Stream 9 中的 Shell 编程 —语法详解与实战案例(11)

CentOS Stream 9 中的 Shell 编程 ---语法详解与实战案例


一、Shell 编程概述

Shell 是操作系统的命令解释器,Shell 脚本是将多个 Shell 命令按逻辑组合成的可执行文件。

1.1 Shell 脚本概述

  • 本质:文本文件,包含一系列命令
  • 作用:自动化任务、系统管理、批量处理
  • 常见 Shell:bash(最常用)、shzshksh
  • CentOS Stream 9 默认使用 bash

1.2 运行 Shell 脚本的几种方法

✅ 方法 1:赋予执行权限 + 直接运行(推荐)
bash 复制代码
chmod +x script.sh
./script.sh
✅ 方法 2:使用解释器显式调用
bash 复制代码
bash script.sh
sh script.sh
✅ 方法 3:使用 source 或 .(在当前 Shell 环境执行)
bash 复制代码
source script.sh
. script.sh

⚠️ 区别:

  • ./script.sh:新开子进程,变量不继承回父 Shell
  • source script.sh:在当前 Shell 执行,变量可保留
✅ 案例:对比不同执行方式对变量的影响
bash 复制代码
#!/bin/bash
# 文件名:test_exec.sh
echo "当前脚本 PID:$$"
MY_VAR="Hello from script"
export MY_VAR
echo "脚本内变量:$MY_VAR"
bash 复制代码
# 终端执行:
chmod +x test_exec.sh

# 方式1:子进程执行 → 变量不会影响当前终端
./test_exec.sh
echo "终端变量:$MY_VAR"  # 输出空

# 方式2:source 执行 → 变量保留
source test_exec.sh
echo "终端变量:$MY_VAR"  # 输出:Hello from script

二、Shell 语法基础


2.1 变量类型

Shell 中变量无需声明类型,默认为字符串,支持:

  • 局部变量:仅在当前脚本/函数中有效
  • 环境变量 :通过 export 导出,子进程可继承
  • 位置参数$0(脚本名)、$1$2...(参数)
  • 特殊变量$?(上条命令返回值)、$$(当前 PID)、$#(参数个数)

2.2 变量定义和访问

➤ 定义变量
bash 复制代码
name="Alice"
age=25
PI=3.14159

⚠️ 注意:

  • 等号 = 两边不能有空格
  • 变量名区分大小写
  • 首字符不能是数字
➤ 访问变量
bash 复制代码
echo $name
echo ${name}   # 推荐使用花括号,避免歧义
➤ 变量赋值技巧
bash 复制代码
# 默认值(变量未设置时使用)
echo ${USER_NAME:-"Guest"}

# 设置默认值(若未设置,则赋值)
echo ${USER_NAME:="DefaultUser"}

# 错误提示(若未设置则报错退出)
echo ${USER_NAME:?"USER_NAME 未定义!"}

# 删除匹配前缀/后缀
filename="report.txt"
echo ${filename%.txt}   # → report
echo ${filename#report.} # → txt

✅ 案例:安全获取用户输入并设置默认值

bash 复制代码
#!/bin/bash
read -p "请输入用户名(默认 admin): " INPUT_USER
USERNAME=${INPUT_USER:-"admin"}  # 若为空则用 admin
echo "最终用户名:$USERNAME"

2.3 引号的使用

引号类型 说明 示例
单引号 ' 原样输出,不解析变量和命令 echo '$HOME'$HOME
双引号 " 解析变量和命令 echo "$HOME"/home/alice
反引号 ````` 命令替换(旧式,不推荐) echo "当前目录:pwd"
$() 命令替换(推荐) echo "当前目录:$(pwd)"

✅ 案例:引号使用对比

bash 复制代码
#!/bin/bash
name="Alice"
today=$(date +%Y-%m-%d)

echo 'Hello $name, today is $today'   # → Hello $name, today is $today
echo "Hello $name, today is $today"   # → Hello Alice, today is 2025-09-16
echo "当前用户:$(whoami)"            # → 当前用户:alice

2.4 命令替换

将命令的输出赋值给变量。

➤ 语法
bash 复制代码
output=$(command)
# 或(旧式,不推荐)
output=`command`

✅ 案例:获取系统信息并格式化输出

bash 复制代码
#!/bin/bash
HOSTNAME=$(hostname)
KERNEL=$(uname -r)
UPTIME=$(uptime -p)

echo "🖥️  主机名:$HOSTNAME"
echo "🐧 内核版本:$KERNEL"
echo "⏱️  运行时间:$UPTIME"

2.5 输入

read 命令读取用户输入
bash 复制代码
read variable
read -p "提示信息: " variable
read -s variable          # 静默输入(密码)
read -t 5 variable        # 5秒超时

✅ 案例:带超时和默认值的输入

bash 复制代码
#!/bin/bash
echo "⏳ 请在5秒内输入文件名(默认:backup.tar.gz):"
read -t 5 -p "> " FILENAME

if [ -z "$FILENAME" ]; then
    FILENAME="backup.tar.gz"
    echo "⏰ 超时,使用默认值:$FILENAME"
else
    echo "✅ 输入文件名:$FILENAME"
fi

2.6 输出

echo 输出
bash 复制代码
echo "Hello"
echo -e "第一行\n第二行"  # 启用转义
echo -n "不换行"          # 不换行
printf 格式化输出(推荐用于复杂格式)
bash 复制代码
printf "姓名:%s,年龄:%d\n" "Alice" 25

✅ 案例:格式化输出磁盘使用率

bash 复制代码
#!/bin/bash
DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')

printf "📊 根分区使用率:%3d%%\n" "$DISK_USAGE"

if [ "$DISK_USAGE" -gt 80 ]; then
    printf "\033[31m⚠️  磁盘空间紧张!\033[0m\n"
elif [ "$DISK_USAGE" -gt 60 ]; then
    printf "\033[33m🔶 磁盘使用中等\033[0m\n"
else
    printf "\033[32m✅ 磁盘空间充足\033[0m\n"
fi

2.7 数组

➤ 索引数组(从0开始)
bash 复制代码
# 定义
fruits=("apple" "banana" "cherry")

# 访问
echo ${fruits[0]}        # apple
echo ${fruits[@]}        # 所有元素
echo ${#fruits[@]}       # 元素个数

# 遍历
for fruit in "${fruits[@]}"; do
    echo "水果:$fruit"
done
➤ 关联数组(需 declare -A)
bash 复制代码
declare -A user
user[name]="Alice"
user[age]=25
user[email]="alice@example.com"

echo "姓名:${user[name]}"
echo "邮箱:${user[email]}"

# 遍历键值对
for key in "${!user[@]}"; do
    echo "$key: ${user[$key]}"
done

✅ 案例:使用关联数组存储服务器信息

bash 复制代码
#!/bin/bash
declare -A SERVERS

SERVERS["web01"]="192.168.1.10"
SERVERS["db01"]="192.168.1.11"
SERVERS["cache01"]="192.168.1.12"

echo "🌐 服务器列表:"
for name in "${!SERVERS[@]}"; do
    printf "%-10s → %s\n" "$name" "${SERVERS[$name]}"
done

三、表达式

Shell 中表达式主要用于条件判断,使用 test 命令或 [ ][[ ]]


四、Shell 控制结构


4.1 分支结构:if 语句

➤ 基本语法
bash 复制代码
if [ condition ]; then
    commands
elif [ condition ]; then
    commands
else
    commands
fi
➤ 使用 [[ ]](推荐,功能更强)
bash 复制代码
if [[ $var == "yes" ]]; then
    echo "确认"
fi

✅ 案例:判断用户输入是否为数字

bash 复制代码
#!/bin/bash
read -p "请输入一个数字: " INPUT

# 使用正则判断是否为数字
if [[ $INPUT =~ ^[0-9]+$ ]]; then
    echo "✅ $INPUT 是有效数字"
else
    echo "❌ $INPUT 不是数字"
fi

4.2 循环结构:for 语句

➤ 列表循环
bash 复制代码
for item in item1 item2 item3; do
    echo $item
done
➤ C 风格循环
bash 复制代码
for ((i=1; i<=5; i++)); do
    echo "第 $i 次循环"
done
➤ 遍历文件
bash 复制代码
for file in *.txt; do
    echo "处理文件:$file"
done

✅ 案例:批量重命名 .log 文件

bash 复制代码
#!/bin/bash
i=1
for file in *.log; do
    if [ -f "$file" ]; then
        newname="log_$(printf "%03d" $i).log"
        mv "$file" "$newname"
        echo "重命名:$file → $newname"
        ((i++))
    fi
done

4.3 循环结构:while 和 until 语句

➤ while 循环(条件为真时执行)
bash 复制代码
count=1
while [ $count -le 5 ]; do
    echo "计数:$count"
    ((count++))
done
➤ until 循环(条件为假时执行)
bash 复制代码
count=1
until [ $count -gt 5 ]; do
    echo "计数:$count"
    ((count++))
done

✅ 案例:猜数字游戏

bash 复制代码
#!/bin/bash
target=$((RANDOM % 100 + 1))
guess=0

echo "🎮 猜数字游戏(1-100)"

while [ $guess -ne $target ]; do
    read -p "请输入你的猜测: " guess
    if [ $guess -lt $target ]; then
        echo "太小了!"
    elif [ $guess -gt $target ]; then
        echo "太大了!"
    else
        echo "🎉 恭喜你,猜对了!答案是 $target"
    fi
done

五、Shell 函数


5.1 函数的定义

bash 复制代码
function_name() {
    commands
}

# 或
function function_name {
    commands
}

5.2 函数调用与参数传递

函数内使用 $1, $2, ... 获取参数,$@ 获取所有参数。

✅ 案例:带参数的问候函数

bash 复制代码
#!/bin/bash
greet() {
    local name=${1:-"Guest"}  # 局部变量 + 默认值
    local time=$(date +%H)
    local msg=""

    if [ $time -lt 12 ]; then
        msg="早上好"
    elif [ $time -lt 18 ]; then
        msg="下午好"
    else
        msg="晚上好"
    fi

    echo "👋 $msg, $name!"
}

# 调用
greet "Alice"
greet "Bob"
greet          # 使用默认值

5.3 函数的返回值

  • 使用 return 返回整数(0-255),通常 0 表示成功
  • 使用 echo 输出字符串,调用时用 $() 捕获

✅ 案例:计算两数之和(两种返回方式)

bash 复制代码
#!/bin/bash
# 方式1:return 返回状态码(不推荐用于数值)
add_return() {
    local sum=$(( $1 + $2 ))
    return $sum  # ❌ 错误!只能返回 0-255,且用于状态
}

# 方式2:echo 输出结果(推荐)
add_echo() {
    echo $(($1 + $2))
}

# 调用
result=$(add_echo 15 25)
echo "15 + 25 = $result"

# 错误示范(超过255会溢出)
add_return 200 100
echo "返回值:$?"  # 输出 44(300 % 256 = 44)

✅ 正确做法:用 echo 返回值,用 $? 返回状态

bash 复制代码
safe_divide() {
    if [ $2 -eq 0 ]; then
        echo "错误:除数不能为零" >&2
        return 1
    else
        echo $(($1 / $2))
        return 0
    fi
}

result=$(safe_divide 10 2)
if [ $? -eq 0 ]; then
    echo "结果:$result"
else
    echo "计算失败"
fi

六、Shell 进阶


6.1 test 命令及其别名

test 命令用于条件判断,等价于 [ ]

bash 复制代码
test -f file && echo "存在"
[ -f file ] && echo "存在"

[[ ]] 是 bash 扩展,支持更多功能(模式匹配、逻辑组合等)。


6.2 数值比较运算符

运算符 说明 示例
-eq 等于 [ $a -eq $b ]
-ne 不等于 [ $a -ne $b ]
-lt 小于 [ $a -lt $b ]
-le 小于等于 [ $a -le $b ]
-gt 大于 [ $a -gt $b ]
-ge 大于等于 [ $a -ge $b ]

✅ 案例:成绩等级判断

bash 复制代码
#!/bin/bash
read -p "请输入成绩(0-100): " score

if [ $score -ge 90 ]; then
    grade="A"
elif [ $score -ge 80 ]; then
    grade="B"
elif [ $score -ge 70 ]; then
    grade="C"
elif [ $score -ge 60 ]; then
    grade="D"
else
    grade="F"
fi

echo "成绩等级:$grade"

6.3 逻辑运算符

运算符 说明 示例
! [ ! -f file ]
-a 与(旧) [ $a -gt 0 -a $a -lt 10 ]
-o 或(旧) [ $a -eq 1 -o $a -eq 2 ]
&& 与(推荐) [[ $a -gt 0 && $a -lt 10 ]]
` `

✅ 案例:验证用户名和密码

bash 复制代码
#!/bin/bash
read -p "用户名: " USER
read -s -p "密码: " PASS
echo

if [[ "$USER" == "admin" && "$PASS" == "secret123" ]]; then
    echo -e "\n✅ 登录成功!"
else
    echo -e "\n❌ 用户名或密码错误!"
fi

6.4 字符串比较和检测运算符

运算符 说明 示例
=== 等于 [[ $str1 == $str2 ]]
!= 不等于 [[ $str1 != $str2 ]]
-z 字符串长度为0 [[ -z $str ]]
-n 字符串长度非0 [[ -n $str ]]
=~ 正则匹配(仅[[) [[ $email =~ @ ]]

✅ 案例:邮箱格式验证

bash 复制代码
#!/bin/bash
read -p "请输入邮箱: " email

if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "✅ 邮箱格式正确"
else
    echo "❌ 邮箱格式错误"
fi

6.5 文件测试运算符

运算符 说明 示例
-e 文件存在 [ -e file ]
-f 普通文件 [ -f file ]
-d 目录 [ -d dir ]
-r 可读 [ -r file ]
-w 可写 [ -w file ]
-x 可执行 [ -x file ]
-s 文件非空 [ -s file ]
-nt file1 比 file2 新 [ file1 -nt file2 ]

✅ 案例:安全文件备份脚本

bash 复制代码
#!/bin/bash
SOURCE="/etc/nginx/nginx.conf"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/nginx.conf.$DATE"

# 检查源文件
if [ ! -f "$SOURCE" ]; then
    echo "❌ 源文件不存在:$SOURCE"
    exit 1
fi

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 检查备份目录可写
if [ ! -w "$BACKUP_DIR" ]; then
    echo "❌ 备份目录不可写:$BACKUP_DIR"
    exit 1
fi

# 执行备份
cp "$SOURCE" "$BACKUP_FILE"

if [ $? -eq 0 ]; then
    echo "✅ 备份成功:$BACKUP_FILE"
    ls -l "$BACKUP_FILE"
else
    echo "❌ 备份失败!"
fi

七、综合案例:自动化任务初探索


🎯 综合案例 1:自动化备份脚本(带日志和清理)

功能:备份指定目录,保留最近7天备份,记录日志

bash 复制代码
#!/bin/bash
# 文件名:auto_backup.sh
# 功能:自动化备份 + 日志 + 清理旧备份

# ===== 配置 =====
SOURCE_DIR="/var/www/html"
BACKUP_DIR="/backup"
RETENTION_DAYS=7
LOG_FILE="/var/log/backup.log"

# ===== 函数定义 =====
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

create_backup() {
    local date_stamp=$(date +%Y%m%d_%H%M%S)
    local backup_file="$BACKUP_DIR/backup_${date_stamp}.tar.gz"
    
    log_message "开始备份:$SOURCE_DIR → $backup_file"
    
    # 创建备份
    tar -czf "$backup_file" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")" 2>/dev/null
    
    if [ $? -eq 0 ]; then
        log_message "✅ 备份成功:$(du -h "$backup_file" | cut -f1)"
        return 0
    else
        log_message "❌ 备份失败!"
        return 1
    fi
}

cleanup_old_backups() {
    log_message "清理 $RETENTION_DAYS 天前的备份..."
    find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete -print | \
    while read file; do
        log_message "🗑️  删除旧备份:$file"
    done
}

# ===== 主程序 =====
log_message "=== 备份任务开始 ==="

# 检查目录
mkdir -p "$BACKUP_DIR"
if [ ! -d "$SOURCE_DIR" ]; then
    log_message "❌ 源目录不存在:$SOURCE_DIR"
    exit 1
fi

# 执行备份
create_backup
if [ $? -ne 0 ]; then
    exit 1
fi

# 清理旧备份
cleanup_old_backups

log_message "=== 备份任务完成 ==="
echo

💡 使用方法:

bash 复制代码
chmod +x auto_backup.sh
sudo ./auto_backup.sh
# 可添加到 crontab 定时执行

🎯 综合案例 2:批量用户管理脚本

功能:从文件读取用户名,批量创建/删除用户,设置密码

bash 复制代码
#!/bin/bash
# 文件名:batch_user_manager.sh
# 用法:./batch_user_manager.sh create users.txt
#      ./batch_user_manager.sh delete users.txt

ACTION=$1
USER_FILE=$2

if [ $# -ne 2 ]; then
    echo "用法:$0 {create|delete} 用户文件"
    echo "用户文件格式:每行一个用户名"
    exit 1
fi

if [ ! -f "$USER_FILE" ]; then
    echo "❌ 用户文件不存在:$USER_FILE"
    exit 1
fi

create_user() {
    local username=$1
    if id "$username" >/dev/null 2>&1; then
        echo "⚠️  用户已存在:$username"
    else
        useradd -m -s /bin/bash "$username"
        echo "$username:DefaultPass123!" | chpasswd
        echo "✅ 创建用户:$username"
    fi
}

delete_user() {
    local username=$1
    if id "$username" >/dev/null 2>&1; then
        userdel -r "$username" 2>/dev/null
        echo "🗑️  删除用户:$username"
    else
        echo "⚠️  用户不存在:$username"
    fi
}

# 主循环
while IFS= read -r username; do
    # 跳过空行和注释
    [[ -z "$username" || "$username" =~ ^# ]] && continue
    
    case $ACTION in
        create)
            create_user "$username"
            ;;
        delete)
            delete_user "$username"
            ;;
        *)
            echo "❌ 未知操作:$ACTION"
            exit 1
            ;;
    esac
done < "$USER_FILE"

echo "🎉 批量用户操作完成!"

💡 用户文件示例(users.txt):

复制代码
alice
bob
charlie
# 这是注释
david

💡 使用:

bash 复制代码
chmod +x batch_user_manager.sh
sudo ./batch_user_manager.sh create users.txt
sudo ./batch_user_manager.sh delete users.txt

🎯 综合案例 3:日志分析与告警脚本

功能:分析 Nginx 访问日志,统计 IP 访问次数,对高频 IP 发出警告

bash 复制代码
#!/bin/bash
# 文件名:log_analyzer.sh
# 功能:分析日志,统计IP访问量,对异常IP告警

LOG_FILE=${1:-"/var/log/nginx/access.log"}
THRESHOLD=${2:-100}  # 默认阈值:100次
REPORT_FILE="/tmp/ip_report_$(date +%Y%m%d).txt"

echo "📊 分析日志文件:$LOG_FILE"
echo "🚨 告警阈值:$THRESHOLD 次访问"

# 检查日志文件
if [ ! -f "$LOG_FILE" ]; then
    echo "❌ 日志文件不存在:$LOG_FILE"
    exit 1
fi

# 提取IP并统计
echo "⏳ 正在统计IP访问次数..."
awk '{print $1}' "$LOG_FILE" | \
sort | \
uniq -c | \
sort -nr > "$REPORT_FILE"

echo "✅ 统计完成,报告保存至:$REPORT_FILE"
echo

# 显示前10名
echo "🔝 访问量前10的IP:"
head -10 "$REPORT_FILE"
echo

# 检查异常IP
echo "🔍 检查异常访问IP(> $THRESHOLD 次):"
while read count ip; do
    if [ "$count" -gt "$THRESHOLD" ]; then
        echo -e "\033[31m⚠️  告警:IP $ip 访问 $count 次\033[0m"
        # 可在此处添加邮件告警或防火墙封禁
        # sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="$ip" drop'
    fi
done < "$REPORT_FILE"

echo
echo "📈 分析完成!"

💡 使用:

bash 复制代码
chmod +x log_analyzer.sh
sudo ./log_analyzer.sh /var/log/nginx/access.log 50

✅ Shell 编程最佳实践

  1. 脚本开头#!/bin/bash 明确解释器
  2. 变量引用 :始终使用 "${var}" 避免空格问题
  3. 条件判断 :优先使用 [[ ]] 而非 [ ]
  4. 错误处理 :检查命令返回值 $?
  5. 函数封装:提高代码复用性
  6. 日志记录:重要操作记录日志
  7. 权限检查:执行前验证文件/目录权限
  8. 参数验证:检查参数个数和合法性
  9. 临时文件 :使用 mktemp 创建安全临时文件
  10. 退出状态:脚本结束返回适当状态码(0=成功)

📚 附录:Shell 速查表

类别 语法/命令 说明
变量 name="value" 定义变量
echo "$name" 使用变量
${name:-default} 默认值
输入 read -p "提示" var 读取用户输入
输出 echo -e "Hello\nWorld" 启用转义
printf "%s %d\n" "Name" 25 格式化输出
数组 arr=("a" "b" "c") 索引数组
declare -A map; map[key]="value" 关联数组
条件判断 if [[ $a -eq $b ]]; then ... fi if 语句
循环 for i in {1..5}; do ... done for 循环
while [[ cond ]]; do ... done while 循环
函数 func() { ... } 定义函数
result=$(func arg) 获取函数返回值
文件测试 [ -f file ] 文件存在且为普通文件
数值比较 [ $a -gt $b ] 大于
字符串 [[ $str == "hello" ]] 字符串相等
正则 [[ $email =~ @ ]] 正则匹配

这份文档覆盖了 CentOS Stream 9 Shell 编程的全部核心知识点 + 语法细节 + 实用案例 + 综合项目,所有代码均含详细注释,可直接用于教学、自学或生产环境参考。

相关推荐
mzhan0172 小时前
Linux: gcc: pkgconf: 谁添加的-I选项
linux·make·gcc·pkgconf
烤麻辣烫2 小时前
黑马大事件学习-15(前端登录页面)
前端·css·vue.js·学习·html
葱卤山猪2 小时前
【Qt】 TCP套接字通信学习文档
qt·学习·tcp/ip
数据门徒2 小时前
《人工智能现代方法(第4版)》 第11章 自动规划 学习笔记
人工智能·笔记·学习
怀旧,3 小时前
【Linux系统编程】9. 进程控制(上)
linux·运维·服务器
很㗊3 小时前
BSP之以太网接口学习笔记
linux·驱动开发·笔记·学习
wadesir3 小时前
Debian dd命令详解(磁盘备份与恢复完整教程)
linux·运维·debian
YJlio3 小时前
Active Directory 工具学习笔记(10.9):AdInsight——命令行选项与自动化采集模板
笔记·学习·自动化
询问QQ:4877392783 小时前
用NSGA - II算法在Matlab中实现微电网多目标优化调度
linux