Shell脚本进阶

最后更新: 2026-01-14 作者: Linux Team
页面目录
目录

正则表达式

基本正则(BRE)

# 字符类
[abc]           # 匹配 a, b, 或 c
[^abc]          # 匹配除 a, b, c 外的字符
[a-z]           # 匹配小写字母
[A-Z]           # 匹配大写字母
[0-9]           # 匹配数字
[a-zA-Z0-9]     # 字母或数字

# 预定义字符类
[[:alpha:]]     # 字母
[[:digit:]]     # 数字
[[:alnum:]]     # 字母数字
[[:space:]]     # 空白字符
[[:upper:]]     # 大写字母
[[:lower:]]     # 小写字母

# 量词
*               # 零个或多个
\{n\}           # 恰好n个
\{n,\}          # n个或更多
\{n,m\}         # n到m个
.               # 任意单个字符

# 位置锚点
^               # 行首
$               # 行尾
\<              # 词首
\>              # 词尾

扩展正则(ERE)

# egrep 或 grep -E 使用
grep -E "pattern" file

# 量词
+               # 一个或多个
?               # 零个或一个
{n}             # 恰好n个
{n,}            # n个或更多
{n,m}           # n到m个

# 或运算
|               # 或
(abc|def)       # abc 或 def

常用正则模式

# IP地址
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
# 或
[0-9]{1,3}(\.[0-9]{1,3}){3}

# 邮箱
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}

# URL
https?://[a-zA-Z0-9.-]+(/[a-zA-Z0-9._~:/?#\[\]@!$&'()*+,;=-]*)?

# 日期 YYYY-MM-DD
[0-9]{4}-[0-9]{2}-[0-9]{2}

# 时间 HH:MM:SS
[0-9]{2}:[0-9]{2}:[0-9]{2}

# 手机号(中国大陆)
1[3-9][0-9]{9}

# MAC地址
([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}

sed正则

# 基础替换
sed 's/old/new/' file

# 邮箱添加链接
sed -E 's/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/<a href="mailto:\1">\1<\/a>/g' file

# 提取IP
ifconfig | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'

# 清理日志时间戳
sed -E 's/^[A-Z][a-z]{2} [0-9]+ [0-9]{2}:[0-9]{2}:[0-9]{2} //' logfile

awk正则

# 正则匹配
awk '/pattern/ {print}' file
awk '$3 ~ /regex/ {print}' file
awk '$3 !~ /regex/ {print}' file

# 提取邮箱
awk -F',' '{for(i=1;i<=NF;i++) if($i ~ /@/) print $i}' file

# 验证IP格式
awk -F. '$1>=0 && $1<=255 && $2>=0 && $2<=255 && $3>=0 && $3<=255 && $4>=0 && $4<=255' file

数组进阶

数组基础回顾

# 定义数组
arr=(one two three)
arr[0]="first"
declare -a arr=(one two three)
declare -A arr=([key1]="value1" [key2]="value2")

# 访问
${arr[0]}           # 单个元素
${arr[@]}           # 所有元素
${!arr[@]}          # 所有索引
${#arr[@]}          # 数组长度

数组切片

arr=(zero one two three four five)

# 切片
${arr[@]:1:3}       # one two three
${arr[@]:2}         # two three four five
${arr[@]::3}        # zero one two

# 替换
${arr[@]/one/ONE}   # ONE two three four five
${arr[@]//[aeiou]/} # 移除元音

数组操作

# 追加元素
arr+=(four five)
arr=( "${arr[@]}" "new" )

# 删除元素
unset arr[2]                # 删除索引2
arr=( "${arr[@]}" )         # 重建数组

# 数组去重
arr=( $(printf '%s\n' "${arr[@]}" | sort -u) )

# 数组反转
arr=( "$(echo "${arr[@]}" | tac -s' ')" )
# 或
reverse() {
    local arr=("$@")
    local n=${#arr[@]}
    for ((i=n-1; i>=0; i--)); do
        echo "${arr[i]}"
    done
}

关联数组

# 定义
declare -A user
user[name]="John"
user[email]="john@example.com"
user[age]=30

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

# 按键排序遍历
for key in $(echo "${!user[@]}" | tr ' ' '\n' | sort); do
    echo "$key: ${user[$key]}"
done

# 键值对输出
printf '%s=%s\n' "${!user[@]}" "${user[@]}"

二维数组(模拟)

# 声明
declare -A matrix

# 赋值
for i in {0..2}; do
    for j in {0..2}; do
        matrix[$i,$j]=$((i * 3 + j + 1))
    done
done

# 访问
echo ${matrix[1,2]}

# 遍历
for key in "${!matrix[@]}"; do
    IFS=',' read -r i j <<< "$key"
    echo "matrix[$i,$j] = ${matrix[$key]}"
done | sort -t'[' -k2 -n

函数进阶

返回值

# return 限制(0-255)
check() {
    if [ "$1" -gt 10 ]; then
        return 0
    else
        return 1
    fi
}

# 使用echo返回字符串
get_date() {
    date +"%Y-%m-%d %H:%M:%S"
}
current=$(get_date)

# 返回多个值
get_stats() {
    local min=$1
    local max=$1
    local sum=0
    for val in "$@"; do
        ((val < min)) && min=$val
        ((val > max)) && max=$val
        ((sum+=val))
    done
    echo "$min $max $sum"
}

read min max sum <<< $(get_stats 5 2 8 1 9)
echo "Min: $min, Max: $max, Sum: $sum"

局部变量与全局变量

global_var="I'm global"

test_scope() {
    local local_var="I'm local"
    global_var="Modified global"
    echo "Inside: $global_var"
    echo "Inside: $local_var"
}

test_scope
echo "Outside: $global_var"
echo "Outside: $local_var"    # 空

递归函数

# 阶乘
factorial() {
    local n=$1
    if [ $n -le 1 ]; then
        echo 1
    else
        local prev=$(factorial $((n-1)))
        echo $((n * prev))
    fi
}
factorial 5    # 120

# 斐波那契
fib() {
    local n=$1
    if [ $n -le 1 ]; then
        echo $n
    else
        local a=$(fib $((n-1)))
        local b=$(fib $((n-2)))
        echo $((a + b))
    fi
}

# 目录递归
list_recursive() {
    local dir=${1:-.}
    for item in "$dir"/*; do
        if [ -d "$item" ]; then
            echo "DIR: $item"
            list_recursive "$item"
        else
            echo "FILE: $item"
        fi
    done
}

函数库

# colors.sh
#!/bin/bash

# 颜色代码
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1" >&2
}

progress_bar() {
    local percent=$1
    local width=50
    local filled=$((width * percent / 100))
    printf "\r["
    printf "%${filled}s" | tr ' ' '='
    printf "%$((width - filled))s" | tr ' ' '-'
    printf "] %3d%%" "$percent"
}

# string_utils.sh
#!/bin/bash

trim() {
    local var="$*"
    var="${var#"${var%%[![:space:]]*}"}"
    var="${var%"${var##*[![:space:]]}"}"
    echo "$var"
}

to_lower() {
    echo "$@" | tr '[:upper:]' '[:lower:]'
}

to_upper() {
    echo "$@" | tr '[:lower:]' '[:upper:]'
}

starts_with() {
    [[ "$1" == "$2"* ]]
}

ends_with() {
    [[ "$1" == *"$2" ]]
}

# date_utils.sh
#!/bin/bash

timestamp() {
    date +"%Y-%m-%d %H:%M:%S"
}

timestamp_file() {
    date +"%Y%m%d_%H%M%S"
}

date_add_days() {
    date -d "$1 + $2 days" +"%Y-%m-%d"
}

date_diff() {
    local d1=$(date -d "$1" +%s)
    local d2=$(date -d "$2" +%s)
    echo $(( (d1 - d2) / 86400 ))
}

# 使用函数库
#!/bin/bash
source ./colors.sh
source ./string_utils.sh
source ./date_utils.sh

log_info "Starting process..."
log_success "Completed!"

命名空间(模拟)

# 使用前缀模拟命名空间
namespace::init() {
    declare -gA namespace__data
}

namespace::set() {
    namespace__data[$1]="$2"
}

namespace::get() {
    echo "${namespace__data[$1]}"
}

namespace::delete() {
    unset namespace__data[$1]
}

namespace::clear() {
    declare -gA namespace__data
}

namespace::keys() {
    echo "${!namespace__data[@]}"
}

namespace::values() {
    echo "${namespace__data[@]}"
}

高级I/O

here文档

# 基本语法
cat << EOF
Content line 1
Content line 2
Variable: $var
Command: $(date)
EOF

# 禁止变量替换
cat << 'EOF'
Variable: $var
EOF

# 缩进(使用-)
cat <<- EOF
    Indented content
    More content
EOF

# 写入文件
cat << EOF > file.txt
Line 1
Line 2
EOF

# 追加
cat << EOF >> file.txt
Line 3
EOF

here字符串

# 基本用法
cat <<< "Hello World"
read -r var <<< "some value"

# 变量赋值
read -r city <<< "New York"
echo $city    # New York

read高级用法

# 读取字段到数组
read -a words <<< "one two three four"
echo "${words[0]}"    # one

# 超时读取
read -t 5 -p "Enter value: " value

# 分隔符
IFS=':' read -r user pass uid gid comment home shell <<< "root:x:0:0:root:/root:/bin/bash"

# 不转义反斜杠
IFS= read -r line < file

# 读取密码
read -s -p "Password: " password

文件描述符

# 标准文件描述符
# 0 - 标准输入 (stdin)
# 1 - 标准输出 (stdout)
# 2 - 标准错误 (stderr)

# 重定向
command > file          # stdout到文件
command 2> file         # stderr到文件
command > file 2>&1     # 两者都到文件
command &> file         # 同上(更简洁)
command &>> file        # 追加

# 管道错误处理
set -o pipefail

# tee重定向
command | tee file.txt
command | tee -a file.txt        # 追加

# 交换stdout和stderr
command 3>&1 1>&2 2>&3

# 临时重定向
echo "message" > /dev/null 2>&1

进程替换

# 语法
<(command)
>(command)

# 示例
diff <(sort file1) <(sort file2)
while IFS= read -r line; do
    echo "$line"
done < <(grep "pattern" file)

# 替换变量
cat <(echo "header"; cat file) <(echo "footer")

高级技巧

命令执行超时

# 使用timeout
timeout 10 command
timeout 5s ./slow_script.sh

# 自定义超时函数
timeout_handler() {
    local timeout=$1
    shift
    "$@" &
    local pid=$!
    (
        sleep $timeout
        kill -0 $pid 2>/dev/null && kill -TERM $pid
    ) &
    local timer=$!
    wait $pid
    local status=$?
    kill -0 $timer 2>/dev/null && kill $timer
    return $status
}

信号处理

#!/bin/bash
# trap.sh

trap 'echo "Received SIGINT"; exit 1' INT
trap 'echo "Received SIGTERM"; cleanup; exit' TERM

cleanup() {
    echo "Cleaning up..."
    # 清理操作
    rm -f /tmp/lock
}

# 忽略信号
trap '' TSTP    # 忽略 Ctrl+Z
trap '' QUIT   # 忽略 Ctrl+\
trap '' HUP    # 忽略挂起

# 调试模式
trap 'echo "DEBUG: $LINENO: $BASH_COMMAND"' DEBUG
trap 'echo "EXIT: exiting with code $?"' EXIT

进度显示

#!/bin/bash

progress() {
    local current=$1
    local total=$2
    local width=40
    local percent=$((current * 100 / total))
    local completed=$((width * current / total))
    local remaining=$((width - completed))
    
    printf "\r["
    printf "%${completed}s" | tr ' ' '='
    printf "%${remaining}s" | tr ' ' '-'
    printf "] %3d%%" "$percent"
    
    [ $current -eq $total ] && echo
}

# 使用示例
total=100
for i in $(seq 1 $total); do
    progress $i $total
    sleep 0.05
done

并行处理

# 后台并行
for i in {1..10}; do
    process_item $i &
done
wait

# xargs并行
cat items.txt | xargs -P 4 -I {} process {}

# GNU parallel
sudo apt install parallel
cat items.txt | parallel -P 4 process {}

# 限制并发数
semaphore=0
max_jobs=4

run_job() {
    local job=$1
    (
        process "$job"
        ((semaphore--))
    ) &
    ((semaphore++))
}

for item in items; do
    while [ $semaphore -ge $max_jobs ]; do
        wait -n
    done
    run_job "$item"
done
wait

实战案例

日志分析脚本

#!/bin/bash
# analyze_logs.sh

set -euo pipefail

# 配置
LOG_FILE=${1:-/var/log/syslog}
OUTPUT_DIR="./report"

# 函数
log() {
    echo "[$(date '+%H:%M:%S')] $1"
}

analyze_errors() {
    log "Analyzing errors..."
    grep -iE "error|fail|critical" "$LOG_FILE" | \
        awk '{print $5}' | \
        sort | uniq -c | sort -rn | head -10
}

analyze_ip() {
    log "Analyzing IP addresses..."
    grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' "$LOG_FILE" | \
        sort | uniq -c | sort -rn | head -10
}

analyze_requests() {
    log "Analyzing requests..."
    awk '{print $7}' "$LOG_FILE" | \
        sort | uniq -c | sort -rn | head -20
}

generate_report() {
    mkdir -p "$OUTPUT_DIR"
    
    {
        echo "==================================="
        echo "    Log Analysis Report"
        echo "    Generated: $(date)"
        echo "==================================="
        echo
        echo "Top 10 Errors:"
        analyze_errors
        echo
        echo "Top 10 IP Addresses:"
        analyze_ip
        echo
        echo "Top 20 Requests:"
        analyze_requests
    } > "$OUTPUT_DIR/report_$(date +%Y%m%d_%H%M%S).txt"
    
    log "Report generated!"
}

# 主程序
log "Starting log analysis..."
generate_report
log "Analysis complete!"

课后练习

实践任务
  1. 编写正则表达式验证邮箱、手机号、IP地址
  2. 创建包含多个函数的工具库
  3. 编写支持参数的日志分析脚本
  4. 实现带超时和信号处理的脚本
  5. 使用并行处理优化脚本性能
  6. 编写完整的系统监控脚本

下一篇预告:我们将学习Shell脚本实战,通过真实案例掌握自动化运维技能。