🔄 第21课:循环与函数

阶段四:Shell脚本第3/6课

📚 课程目标

一、for循环

# 遍历列表
for fruit in apple banana cherry; do
    echo "水果: $fruit"
done

# C风格for
for ((i=1; i<=10; i++)); do
    echo "第 $i 次"
done

# 遍历文件
for file in /var/log/*.log; do
    [[ -f "$file" ]] && echo "$file: $(wc -l < "$file")行"
done

# 遍历数组
servers=("web1" "web2" "db1")
for s in "${servers[@]}"; do
    ping -c 1 -W 1 "$s" &>/dev/null && echo "$s ✅" || echo "$s ❌"
done

二、while/until循环

# while循环
count=0
while [[ $count -lt 10 ]]; do
    echo "count=$count"
    ((count++))
done

# 读取文件
while IFS= read -r line; do
    echo "行: $line"
done < /etc/hosts

# 无限循环
while true; do
    echo "运行中... $(date)"
    sleep 5
done

# until(条件为假时执行)
until [[ -f /tmp/done ]]; do
    sleep 1
done
echo "完成!"

三、循环控制

# break跳出循环
for i in $(seq 1 100); do
    [[ $i -eq 5 ]] && break
    echo "$i"
done

# continue跳过本次
for i in $(seq 1 10); do
    [[ $((i % 2)) -eq 0 ]] && continue
    echo "奇数: $i"
done

# break N跳出N层
for i in 1 2 3; do
    for j in a b c; do
        [[ "$i$j" == "2b" ]] && break 2
    done
done

四、函数

# 定义与调用
greet() {
    local name="$1"
    echo "你好, $name!"
}
greet "张三"

# 返回值(退出码)
is_root() { [[ $EUID -eq 0 ]]; }
is_root && echo "root" || echo "非root"

# 数值返回(0-255)
check_port() {
    ss -tlnp | grep -q ":$1 " && return 0 || return 1
}
check_port 80 && echo "80开放" || echo "80未开放"

4.1 变量作用域

GLOBAL="全局"

my_func() {
    local local_var="局部"    # local关键字
    local GLOBAL="覆盖"       # 局部覆盖全局
    echo "函数内: GLOBAL=$GLOBAL"
}
my_func
echo "函数外: GLOBAL=$GLOBAL"  # 全局(未被修改)

4.2 函数库

# lib/utils.sh
log() {
    local level="$1"; shift
    echo "[$(date '+%H:%M:%S')] [$level] $*"
}
info()  { log "INFO" "$@"; }
warn()  { log "WARN" "$@"; }
error() { log "ERROR" "$@" >&2; }

confirm() {
    read -p "$1 [y/N] " ans
    [[ "$ans" =~ ^[Yy]$ ]]
}

retry() {
    local max="$1"; shift
    local n=1
    while [[ $n -le $max ]]; do
        eval "$@" && return 0
        warn "尝试 $n/$max 失败"
        ((n++)); sleep 2
    done
    error "重试$max次仍失败"; return 1
}

# 使用: source lib/utils.sh

五、实战:批量检查

#!/bin/bash
set -euo pipefail

check_ssh() {
    ssh -o ConnectTimeout=5 -o BatchMode=yes "$1" "echo OK" &>/dev/null
}

SERVERS=("web1" "web2" "db1" "cache1")

echo "===== 服务器检查 ====="
for s in "${SERVERS[@]}"; do
    if check_ssh "$s"; then
        echo "$s: ✅ SSH可达"
        ssh "$s" "uptime; free -h | grep Mem; df -h /" 2>/dev/null
    else
        echo "$s: ❌ SSH不可达"
    fi
done

六、练习

📝 编写备份函数库

#!/bin/bash
# backup-lib.sh

backup_file() {
    local src="$1" dst="${2:-/backup}"
    local ts=$(date +%Y%m%d_%H%M%S)
    [[ ! -f "$src" ]] && return 1
    cp "$src" "$dst/${ts}_$(basename "$src")"
}

rotate_backups() {
    local dir="$1" keep="${2:-7}"
    ls -t "$dir" | tail -n +"$((keep+1))" |         while read f; do rm "$dir/$f"; done
}
🏆

成就解锁:循环函数师

✅ 掌握for/while/until

✅ 学会break/continue

✅ 掌握函数定义与参数

✅ 理解变量作用域

✅ 学会编写函数库

七、select菜单

# select创建交互菜单
PS3="请选择操作: "
select opt in "启动" "停止" "重启" "状态" "退出"; do
    case $opt in
        启动) systemctl start myapp ;;
        停止) systemctl stop myapp ;;
        重启) systemctl restart myapp ;;
        状态) systemctl status myapp ;;
        退出) break ;;
        *) echo "无效选择" ;;
    esac
done

八、并行执行

# 后台并行
for host in web1 web2 web3; do
    ssh "$host" "uptime" &
done
wait  # 等待所有后台任务完成

# 限制并发数
MAX_PARALLEL=5
count=0
for host in $(cat hosts.txt); do
    ssh "$host" "command" &
    ((count++))
    [[ $count -ge $MAX_PARALLEL ]] && wait -n && ((count--))
done
wait

# 使用xargs并行
cat hosts.txt | xargs -P 5 -I {} ssh {} "uptime"

九、信号处理

# 捕获信号
cleanup() {
    echo "\n正在清理..."
    rm -f "$TMPFILE"
    exit 0
}
trap cleanup SIGINT SIGTERM

TMPFILE=$(mktemp)
echo "按Ctrl+C停止"
while true; do
    echo "运行中..."
    sleep 1
done

附录:循环与函数学习资源

推荐书籍

书名作者重点
Linux命令行与Shell脚本编程大全BlumShell脚本全面覆盖
UNIX/Linux系统管理技术手册Nemeth系统管理圣经
Ansible: Up and RunningLintnerAnsible实战
鸟哥的Linux私房菜鸟哥中文经典入门

在线资源

认证路径

认证机构级别
LFCSLinux Foundation基础系统管理
LFCELinux Foundation高级系统工程师
RHCSARed HatRed Hat管理员
RHCERed HatRed Hat工程师
CKA/CKADCNCFKubernetes管理员/开发者

实践环境

常用速查表

# 文本处理速查
grep -E 'pattern' file     # 扩展正则搜索
sed -i 's/old/new/g' file  # 替换
awk '{print $1}' file     # 提取列
sort | uniq -c | sort -rn   # 频次统计
cut -d: -f1 file            # 切列
tr 'A-Z' 'a-z'             # 转小写

# 系统管理速查
systemctl status/start/stop/restart service
journalctl -u service -f    # 跟踪日志
df -h / du -sh *            # 磁盘使用
ps aux --sort=-%mem         # 内存排序
ss -tlnp                    # 监听端口
iptables -L -n              # 防火墙规则
crontab -e                  # 编辑定时任务
useradd -m -s /bin/bash user # 创建用户

# 网络诊断速查
ip addr / ip route          # 网络配置
ping / traceroute           # 连通测试
dig / nslookup              # DNS查询
curl -I url                 # HTTP头检查
nc -zv host port            # 端口测试
tcpdump -i eth0 port 80     # 抓包

十、实机验证记录

$ for i in 1 2 3; do echo "第${i}次"; done
第1次
第2次
第3次
$ seq 1 5 | while read n; do echo "数字: $n"; done
数字: 1
数字: 2
数字: 3
数字: 4
数字: 5

十一、生产环境循环模式

#!/bin/bash
# wait-for-service.sh - 等待服务就绪
HOST="$1"; PORT="$2"; TIMEOUT=60
elapsed=0
while [[ $elapsed -lt $TIMEOUT ]]; do
    if nc -z "$HOST" "$PORT" 2>/dev/null; then
        echo "✅ $HOST:$PORT 已就绪 (${elapsed}s)"
        exit 0
    fi
    echo "等待 $HOST:$PORT... (${elapsed}s)"
    sleep 2; ((elapsed+=2))
done
echo "❌ 超时: $HOST:$PORT 未就绪" >&2; exit 1

# retry-with-backoff.sh - 指数退避重试
retry_backoff() {
    local cmd="$1"; shift
    local max="$1"; shift
    local wait=1
    for ((i=1; i<=max; i++)); do
        if eval "$cmd"; then
            return 0
        fi
        echo "重试 $i/$max, 等待${wait}s..."
        sleep "$wait"
        wait=$((wait * 2))
        [[ $wait -gt 60 ]] && wait=60
    done
    return 1
}

十一、生产环境循环模式

#!/bin/bash
# wait-for-service.sh - 等待服务就绪
HOST="$1"; PORT="$2"; TIMEOUT=60; elapsed=0
while [[ $elapsed -lt $TIMEOUT ]]; do
    nc -z "$HOST" "$PORT" 2>/dev/null && echo "Ready (${elapsed}s)" && exit 0
    sleep 2; ((elapsed+=2))
done
echo "Timeout" >&2; exit 1

# retry_backoff - 指数退避
retry_backoff() {
    local cmd="$1" max="$2" wait=1
    for ((i=1; i<=max; i++)); do
        eval "$cmd" && return 0
        sleep "$wait"; wait=$((wait * 2))
        [[ $wait -gt 60 ]] && wait=60
    done
    return 1
}

专题:循环优化与函数式编程

在实际生产环境中,循环优化与函数式编程是系统管理员必须深入掌握的核心能力。本节将探讨常见场景、最佳实践和避坑指南。

常见问题与解决方案

问题1:脚本在生产环境运行异常

开发环境和生产环境的差异是脚本出错的常见原因。建议:

问题2:性能不达预期

优化策略:

问题3:可维护性差

改善方法:

最佳实践总结

#!/bin/bash
# 循环优化与函数式编程最佳实践模板
set -euo pipefail

# 1. 参数校验
[[ $# -lt 1 ]] && echo "Usage: $0 <arg>" >&2 && exit 1

# 2. 环境检查
require_cmd() {
    command -v "$1" >/dev/null || { echo "需要: $1" >&2; exit 1; }
}

# 3. 清理机制
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

# 4. 日志记录
log() { echo "[$(date +%T)] $*"; }
err() { log "ERROR: $*" >&2; exit 1; }

# 5. 主逻辑
main() {
    log "开始处理: $1"
    # 业务逻辑
    log "处理完成"
}

main "$@"

进阶方向