Shell 高级主题详解

Shell高级主题简介

Shell脚本编程不仅仅是简单的命令组合,它还包含许多强大的高级功能。掌握这些高级主题可以让您编写更高效、更强大的脚本。

正则表达式

强大的模式匹配工具,用于文本搜索、替换和验证。

sed命令

流编辑器,用于对文本进行非交互式的编辑和处理。

awk编程

强大的文本处理语言,特别适合处理结构化数据。

函数编程

创建可重用的代码块,提高脚本的模块化和可维护性。

信号处理

处理系统信号,实现优雅的脚本终止和资源清理。

进程管理

控制子进程,实现并行处理和作业控制。

学习建议

这些高级主题是Shell脚本编程的精髓。建议按顺序学习,先掌握正则表达式基础,然后是sed和awk,最后学习更高级的函数编程和进程管理。

正则表达式

正则表达式是强大的模式匹配语言,在Shell脚本中广泛用于文本处理。

正则表达式工作流程
输入文本
正则模式
模式匹配
匹配结果

基本正则表达式语法

元字符 说明 示例 匹配
. 匹配任意单个字符 a.c abc, aac, axc
* 匹配前一个字符0次或多次 ab*c ac, abc, abbc
+ 匹配前一个字符1次或多次 ab+c abc, abbc
? 匹配前一个字符0次或1次 ab?c ac, abc
^ 匹配字符串开头 ^hello hello world
$ 匹配字符串结尾 world$ hello world
[] 字符集合 [aeiou] a, e, i, o, u
[^] 否定字符集合 [^0-9] 非数字字符
| 或操作 cat|dog cat 或 dog
() 分组 (ab)+ ab, abab
{n} 精确匹配n次 a{3} aaa
{n,} 匹配至少n次 a{2,} aa, aaa
{n,m} 匹配n到m次 a{2,4} aa, aaa, aaaa
\d 数字字符 \d+ 123, 45
\w 单词字符 \w+ hello, abc123
\s 空白字符 \s+ 空格, 制表符
regex_examples.sh
#!/bin/bash

# 正则表达式示例

echo "=== 基本正则表达式演示 ==="

# 测试文本
text="Hello, my email is user@example.com and my phone is 123-456-7890. Visit https://www.example.com for more info."

echo "原始文本:"
echo "$text"
echo

# 使用grep进行正则匹配
echo "1. 匹配邮箱地址:"
echo "$text" | grep -Eo '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'

echo -e "\n2. 匹配电话号码:"
echo "$text" | grep -Eo '[0-9]{3}-[0-9]{3}-[0-9]{4}'

echo -e "\n3. 匹配URL:"
echo "$text" | grep -Eo 'https?://[a-zA-Z0-9./?=_-]+'

echo -e "\n4. 匹配单词:"
echo "$text" | grep -Eo '\b[a-zA-Z]{4,}\b' | head -5

echo -e "\n=== 在条件判断中使用正则 ==="

# 在[[ ]]中使用正则匹配
email="user@example.com"

if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "有效的邮箱地址: $email"
else
echo "无效的邮箱地址: $email"
fi

# 提取匹配组
text="Date: 2023-11-16, Time: 14:30:25"

if [[ "$text" =~ ([0-9]{4}-[0-9]{2}-[0-9]{2}).*([0-9]{2}:[0-9]{2}:[0-9]{2}) ]]; then
date="${BASH_REMATCH[1]}"
time="${BASH_REMATCH[2]}"
echo "日期: $date"
echo "时间: $time"
fi

echo -e "\n=== 常用正则模式 ==="

# 验证各种格式
validate_pattern() {
local pattern=$1
local test_string=$2
local description=$3

if [[ "$test_string" =~ $pattern ]]; then
    echo "✅ $description: '$test_string'"
else
    echo "❌ $description: '$test_string'"
fi
}

# 测试各种模式
echo "验证用户名(字母开头,3-15字符):"
validate_pattern '^[a-zA-Z][a-zA-Z0-9_]{2,14}$' "john_doe123" "有效用户名"
validate_pattern '^[a-zA-Z][a-zA-Z0-9_]{2,14}$' "1john" "无效用户名"

echo -e "\n验证密码(至少8字符,包含大小写和数字):"
validate_pattern '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,}$' "Password123" "强密码"
validate_pattern '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,}$' "password" "弱密码"

echo -e "\n验证IP地址:"
validate_pattern '^([0-9]{1,3}\.){3}[0-9]{1,3}$' "192.168.1.1" "有效IP"
validate_pattern '^([0-9]{1,3}\.){3}[0-9]{1,3}$' "256.300.1.1" "无效IP"

echo -e "\n=== 高级正则技巧 ==="

# 使用正则进行复杂文本处理
log_data="2023-11-16 14:30:25 [INFO] User login successful from 192.168.1.100
2023-11-16 14:31:10 [ERROR] Database connection failed
2023-11-16 14:32:45 [WARN] High memory usage detected"

echo "日志分析:"
echo "$log_data" | while read line; do
if [[ "$line" =~ \[(INFO|WARN|ERROR)\].*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) ]]; then
    level="${BASH_REMATCH[1]}"
    ip="${BASH_REMATCH[2]:-N/A}"
    echo "级别: $level, IP: $ip"
fi
done

# 使用正则提取和转换
csv_data="name,age,city
John,25,New York
Alice,30,London
Bob,35,Tokyo"

echo -e "\nCSV数据处理:"
echo "$csv_data" | tail -n +2 | while read line; do
if [[ "$line" =~ ^([^,]+),([^,]+),([^,]+)$ ]]; then
    name="${BASH_REMATCH[1]}"
    age="${BASH_REMATCH[2]}"
    city="${BASH_REMATCH[3]}"
    echo "姓名: $name, 年龄: $age, 城市: $city"
fi
done
正则表达式速查表:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
邮箱地址验证
^([0-9]{3}-)?[0-9]{3}-[0-9]{4}$
美国电话号码格式
^(https?://)?([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(/.*)?$
URL验证
^[0-9]{4}-[0-9]{2}-[0-9]{2}$
日期格式 (YYYY-MM-DD)

sed流编辑器

sed是一个强大的流编辑器,用于对文本进行非交互式的编辑。

sed工作流程
读取输入
模式空间
执行命令
输出结果

sed基本语法和命令

命令 说明 示例
s/pattern/replacement/ 替换命令 s/foo/bar/
p 打印行 1,5p
d 删除行 /pattern/d
a 在行后追加 3a\new line
i 在行前插入 2i\new line
c 替换行 /pattern/c\new line
y 字符转换 y/abc/ABC/
q 退出 10q
sed_examples.sh
#!/bin/bash

# sed命令示例

echo "=== sed基本操作 ==="

# 创建测试文件
cat > test.txt << EOF
1. Apple
2. Banana
3. Cherry
4. Date
5. Elderberry
6. Fig
7. Grape
EOF

echo "原始文件内容:"
cat test.txt
echo

echo "1. 替换操作:"
echo "   - 替换第一个匹配:"
sed 's/Banana/Orange/' test.txt

echo -e "\n   - 全局替换:"
sed 's/berry/fruit/g' test.txt

echo -e "\n   - 替换第2行的内容:"
sed '2s/.*/2. Blueberry/' test.txt

echo -e "\n2. 删除操作:"
echo "   - 删除第3行:"
sed '3d' test.txt

echo -e "\n   - 删除包含'berry'的行:"
sed '/berry/d' test.txt

echo -e "\n   - 删除第2-4行:"
sed '2,4d' test.txt

echo -e "\n3. 插入和追加:"
echo "   - 在第3行前插入:"
sed '3i\2.5. Blackberry' test.txt

echo -e "\n   - 在第3行后追加:"
sed '3a\3.5. Blueberry' test.txt

echo -e "\n4. 打印特定行:"
echo "   - 打印第2-4行:"
sed -n '2,4p' test.txt

echo -e "\n   - 打印包含'berry'的行:"
sed -n '/berry/p' test.txt

echo -e "\n=== 高级sed技巧 ==="

# 创建配置文件示例
cat > config.conf << EOF
# Database configuration
db_host=localhost
db_port=3306
db_name=testdb
db_user=admin
db_pass=oldpassword

# Server configuration
server_host=127.0.0.1
server_port=8080
debug_mode=true
EOF

echo "配置文件修改:"
echo "原始配置:"
cat config.conf
echo

echo "1. 修改数据库密码:"
sed -i 's/db_pass=oldpassword/db_pass=newpassword/' config.conf

echo "2. 注释掉debug模式:"
sed -i 's/^debug_mode=true/# debug_mode=true/' config.conf

echo "3. 取消注释(如果存在):"
sed -i 's/^# db_host/db_host/' config.conf

echo "修改后的配置:"
cat config.conf
echo

echo "=== 使用sed处理日志文件 ==="

# 创建日志文件示例
cat > app.log << EOF
[2023-11-16 14:30:25] INFO: User login successful
[2023-11-16 14:31:10] ERROR: Database connection failed
[2023-11-16 14:32:45] WARN: High memory usage detected
[2023-11-16 14:33:20] INFO: Backup completed
[2023-11-16 14:34:15] ERROR: File not found
EOF

echo "日志分析:"
echo "1. 提取错误日志:"
sed -n '/ERROR/p' app.log

echo -e "\n2. 移除时间戳:"
sed 's/\[.*\] //' app.log

echo -e "\n3. 提取时间信息:"
sed -n 's/.*\[\([0-9:-]\{19\}\)\].*/\1/p' app.log

echo -e "\n4. 给错误日志添加标记:"
sed '/ERROR/s/^/🚨 /' app.log

echo -e "\n=== 复杂的sed脚本 ==="

# 使用多个sed命令
echo "多命令执行:"
sed -e 's/Apple/Red Apple/' \
-e 's/Banana/Yellow Banana/' \
-e '/Cherry/d' \
test.txt

echo -e "\n使用sed脚本文件:"

# 创建sed脚本
cat > process.sed << EOF
# sed脚本示例
s/Apple/Fruit: Apple/
s/Banana/Fruit: Banana/
/berry/ s/$/ (berry fruit)/
1,3s/^/👉 /
EOF

echo "使用sed脚本处理:"
sed -f process.sed test.txt

echo -e "\n=== 就地编辑和备份 ==="

# 创建测试文件
cp test.txt test_backup.txt

echo "就地编辑(创建备份):"
sed -i.bak 's/^[0-9]\./Item: /' test_backup.txt

echo "修改后的文件:"
cat test_backup.txt

echo -e "\n原始备份文件:"
cat test_backup.txt.bak

# 清理
rm -f test.txt config.conf app.log process.sed test_backup.txt test_backup.txt.bak
sed常用命令速查:
替换命令
sed 's/old/new/' file - 替换第一个old为new
sed 's/old/new/g' file - 替换所有old为new
sed 's/old/new/2' file - 替换第二个old为new
sed 's/old/new/ig' file - 忽略大小写全局替换
地址范围
sed '10d' file - 删除第10行
sed '10,20d' file - 删除10-20行
sed '/pattern/d' file - 删除匹配行
sed '/start/,/end/d' file - 删除start到end之间的行
高级用法
sed -n '1,5p' file - 只打印1-5行
sed -i.bak 's/old/new/g' file - 就地编辑并备份
sed -f script.sed file - 使用sed脚本文件
sed '/pattern/{n; s/old/new/;}' file - 匹配行的下一行执行命令

awk编程语言

awk是一种强大的文本处理语言,特别适合处理结构化数据。

awk工作流程
BEGIN块
处理每行
END块

awk基本语法和内置变量

内置变量 说明 示例
NR 当前记录号(行号) NR==1
NF 当前记录的字段数 print NF
FS 字段分隔符 FS=","
OFS 输出字段分隔符 OFS="\t"
RS 记录分隔符 RS="\n\n"
ORS 输出记录分隔符 ORS="\n---\n"
$0 整行内容 print $0
$1, $2, ... 第1、2...个字段 print $1
awk_examples.sh
#!/bin/bash

# awk命令示例

echo "=== awk基本操作 ==="

# 创建测试数据
cat > data.txt << EOF
Alice 25 Engineer 5000
Bob 30 Manager 8000
Charlie 28 Developer 6000
Diana 35 Director 12000
Eve 22 Intern 3000
Frank 40 Consultant 10000
EOF

echo "原始数据:"
cat data.txt
echo

echo "1. 基本打印:"
echo "   - 打印整行:"
awk '{print $0}' data.txt

echo -e "\n   - 打印特定列:"
awk '{print $1, $4}' data.txt

echo -e "\n   - 打印行号:"
awk '{print NR, $0}' data.txt

echo -e "\n2. 条件过滤:"
echo "   - 年龄大于30岁:"
awk '$2 > 30 {print $1, $2}' data.txt

echo -e "\n   - 薪资高于7000:"
awk '$4 > 7000 {print $1, $4}' data.txt

echo -e "\n   - 职位是Developer:"
awk '$3 == "Developer" {print $0}' data.txt

echo -e "\n3. 模式匹配:"
echo "   - 名字以A或B开头:"
awk '$1 ~ /^[AB]/ {print $0}' data.txt

echo -e "\n   - 包含字母'i':"
awk '/i/ {print $0}' data.txt

echo -e "\n=== 高级awk功能 ==="

echo "4. BEGIN和END块:"
awk 'BEGIN {print "开始处理数据..."; total=0}
 {total += $4; print $1, $4}
 END {print "处理完成"; printf "总薪资: %d\n", total}' data.txt

echo -e "\n5. 内置函数:"
echo "   - 字符串函数:"
awk '{print toupper($1), tolower($3), length($1)}' data.txt

echo -e "\n   - 数学函数:"
awk '{print $1, $4, log($4), sqrt($4)}' data.txt

echo -e "\n6. 数组使用:"
echo "   - 按职位统计人数:"
awk '{count[$3]++} END {for (job in count) print job, count[job]}' data.txt

echo -e "\n   - 按职位统计平均薪资:"
awk '{sum[$3] += $4; count[$3]++}
 END {for (job in sum) printf "%s: %.2f\n", job, sum[job]/count[job]}' data.txt

echo -e "\n=== 处理CSV数据 ==="

# 创建CSV数据
cat > employees.csv << EOF
Name,Age,Department,Salary,JoinDate
John,25,Engineering,5000,2020-01-15
Alice,30,Marketing,6000,2019-03-20
Bob,28,Engineering,5500,2021-06-10
Carol,35,Sales,7000,2018-11-05
David,32,Engineering,6500,2019-09-12
EOF

echo "CSV数据处理:"
echo "原始CSV:"
cat employees.csv
echo

echo "1. 处理表头:"
awk -F, 'NR==1 {print "表头:", $0}' employees.csv

echo -e "\n2. 工程部门员工:"
awk -F, '$3 == "Engineering" {print $1, $4}' employees.csv

echo -e "\n3. 薪资统计:"
awk -F, 'NR>1 {total += $4; count++}
     END {printf "平均薪资: %.2f\n", total/count}' employees.csv

echo -e "\n4. 入职年份统计:"
awk -F, 'NR>1 {year = substr($5, 1, 4); years[year]++}
     END {for (y in years) print y "年入职:", years[y] "人"}' employees.csv

echo -e "\n=== 复杂文本处理 ==="

# 创建日志文件
cat > server.log << EOF
[INFO] 2023-11-16 14:30:25 User alice logged in from 192.168.1.100
[ERROR] 2023-11-16 14:31:10 Database connection timeout
[WARN] 2023-11-16 14:32:45 High memory usage: 85%
[INFO] 2023-11-16 14:33:20 User bob logged out
[ERROR] 2023-11-16 14:34:15 File not found: /var/www/index.html
[INFO] 2023-11-16 14:35:30 Backup completed successfully
EOF

echo "日志分析:"
echo "1. 错误统计:"
awk '/\[ERROR\]/ {count++} END {print "错误数量:", count}' server.log

echo -e "\n2. 提取IP地址:"
awk 'match($0, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) {print substr($0, RSTART, RLENGTH)}' server.log

echo -e "\n3. 按小时统计日志:"
awk '{split($3, time, ":"); hours[time[1]]++}
 END {for (h in hours) print h "时:", hours[h] "条日志"}' server.log

echo -e "\n4. 自定义输出格式:"
awk 'BEGIN {printf "%-10s %-8s %s\n", "级别", "时间", "消息"}
 /\[/ {level = substr($1, 2, length($1)-2);
       time = $2;
       message = "";
       for(i=3;i<=NF;i++) message = message $i " ";
       printf "%-10s %-8s %s\n", level, time, message}' server.log

echo -e "\n=== awk脚本文件 ==="

# 创建awk脚本
cat > analysis.awk << 'EOF'
# awk脚本示例 - 员工数据分析
BEGIN {
FS = ","
print "=== 员工报告 ==="
total_salary = 0
count = 0
}

NR == 1 {
# 跳过表头
next
}

{
# 处理数据行
name = $1
age = $2
department = $3
salary = $4
join_date = $5

# 累加统计
total_salary += salary
count++
dept_count[department]++
dept_salary[department] += salary

# 输出符合条件的员工
if (salary > 6000) {
    print "高薪员工:", name, salary
}
}

END {
print "\n=== 统计信息 ==="
printf "员工总数: %d\n", count
printf "平均薪资: %.2f\n", total_salary/count

print "\n=== 部门统计 ==="
for (dept in dept_count) {
    avg = dept_salary[dept] / dept_count[dept]
    printf "%-12s: %2d 人, 平均薪资: %8.2f\n", dept, dept_count[dept], avg
}
}
EOF

echo "使用awk脚本分析数据:"
awk -f analysis.awk employees.csv

# 清理
rm -f data.txt employees.csv server.log analysis.awk
awk常用模式速查:
基本模式
awk '/pattern/' file - 匹配模式的行
awk 'NR==1' file - 第一行
awk '$1 > 100' file - 第一个字段大于100的行
awk 'NF > 5' file - 字段数大于5的行
字段操作
awk '{print $1}' - 打印第一个字段
awk '{print $NF}' - 打印最后一个字段
awk '{print $(NF-1)}' - 打印倒数第二个字段
awk -F: '{print $1}' - 使用冒号分隔符
高级功能
awk 'BEGIN{FS=","} {...}' - 在BEGIN块设置分隔符
awk '{sum+=$1} END{print sum}' - 计算总和
awk '!seen[$0]++' - 去除重复行
awk 'length($0) > 80' - 打印长度大于80的行

Shell函数编程

函数是Shell脚本中实现代码重用和模块化的关键工具。

函数定义和使用

functions_examples.sh
#!/bin/bash

# Shell函数示例

echo "=== 基本函数定义 ==="

# 方式1: 使用function关键字
function greet {
echo "Hello, $1!"
}

# 方式2: 不使用function关键字
say_goodbye() {
local name=$1
echo "Goodbye, $name!"
}

# 调用函数
greet "Alice"
say_goodbye "Bob"

echo -e "\n=== 函数参数和返回值 ==="

# 参数处理函数
show_params() {
echo "函数名称: $0"
echo "参数个数: $#"
echo "所有参数: $@"
echo "第一个参数: $1"
echo "第二个参数: $2"

# 使用shift处理参数
while [ $# -gt 0 ]; do
    echo "参数: $1"
    shift
done
}

show_params "apple" "banana" "cherry"

# 返回值函数
calculate_sum() {
local sum=0
for num in "$@"; do
    sum=$((sum + num))
done
return $sum
}

calculate_sum 10 20 30
echo "计算结果: $?"

echo -e "\n=== 局部变量和全局变量 ==="

global_var="我是全局变量"

variable_demo() {
local local_var="我是局部变量"
global_var="我在函数中修改了全局变量"
echo "函数内 - 局部变量: $local_var"
echo "函数内 - 全局变量: $global_var"
}

echo "函数调用前 - 全局变量: $global_var"
variable_demo
echo "函数调用后 - 全局变量: $global_var"
# echo "尝试访问局部变量: $local_var"  # 这会报错

echo -e "\n=== 高级函数特性 ==="

# 函数返回字符串
get_timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}

# 函数返回数组
create_array() {
local arr=("$@")
echo "${arr[@]}"
}

# 使用命令替换获取函数输出
current_time=$(get_timestamp)
echo "当前时间: $current_time"

# 数组返回
my_array=($(create_array "one" "two" "three"))
echo "数组内容: ${my_array[@]}"

echo -e "\n=== 函数库示例 ==="

# 数学函数库
math_functions() {
# 加法
add() {
    echo $(($1 + $2))
}

# 减法
subtract() {
    echo $(($1 - $2))
}

# 乘法
multiply() {
    echo $(($1 * $2))
}

# 除法
divide() {
    if [ $2 -eq 0 ]; then
        echo "错误: 除数不能为0" >&2
        return 1
    fi
    echo $(($1 / $2))
}

# 幂运算
power() {
    local result=1
    local base=$1
    local exponent=$2

    for ((i=0; i&2
    return 1
fi

local result=$((numerator / denominator))
echo $result
return 0
}

# 测试错误处理
if result=$(safe_division 10 2); then
echo "除法成功: $result"
else
echo "除法失败"
fi

if result=$(safe_division 10 0); then
echo "除法成功: $result"
else
echo "除法失败"
fi

echo -e "\n=== 递归函数 ==="

# 阶乘函数(递归)
factorial() {
local n=$1

# 基本情况
if [ $n -le 1 ]; then
    echo 1
    return
fi

# 递归情况
local prev=$(factorial $((n-1)))
echo $((n * prev))
}

echo "递归计算阶乘:"
echo "5! = $(factorial 5)"
echo "10! = $(factorial 10)"

# 斐波那契数列(递归)
fibonacci() {
local n=$1

if [ $n -le 1 ]; then
    echo $n
    return
fi

local a=$(fibonacci $((n-1)))
local b=$(fibonacci $((n-2)))
echo $((a + b))
}

echo -e "\n斐波那契数列:"
for i in {0..10}; do
echo -n "$(fibonacci $i) "
done
echo

echo -e "\n=== 函数作为参数 ==="

# 高阶函数:接受函数作为参数
apply_operation() {
local operation=$1
local a=$2
local b=$3

$operation $a $b
}

# 定义一些操作函数
square() {
echo $(($1 * $1))
}

cube() {
echo $(($1 * $1 * $1))
}

# 使用高阶函数
echo "高阶函数示例:"
echo "5的平方: $(apply_operation square 5)"
echo "3的立方: $(apply_operation cube 3)"

echo -e "\n=== 实用函数库 ==="

# 日志函数
log() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$timestamp] [$level] $message" >&2
}

# 文件操作函数
backup_file() {
local file=$1
local backup_dir=${2:-"./backup"}

if [ ! -f "$file" ]; then
    log "ERROR" "文件不存在: $file"
    return 1
fi

mkdir -p "$backup_dir"
local backup_name="${file}.$(date +%Y%m%d_%H%M%S).bak"
cp "$file" "$backup_dir/$backup_name"

if [ $? -eq 0 ]; then
    log "INFO" "文件备份成功: $backup_name"
    return 0
else
    log "ERROR" "文件备份失败: $file"
    return 1
fi
}

# 字符串处理函数
string_utils() {
# 字符串反转
reverse_string() {
    local str=$1
    echo "$str" | rev
}

# 字符串长度
string_length() {
    local str=$1
    echo ${#str}
}

# 转换为大写
to_upper() {
    local str=$1
    echo "$str" | tr '[:lower:]' '[:upper:]'
}

# 转换为小写
to_lower() {
    local str=$1
    echo "$str" | tr '[:upper:]' '[:lower:]'
}
}

# 加载字符串工具
string_utils

# 测试字符串函数
echo "字符串处理:"
echo "反转 'hello': $(reverse_string 'hello')"
echo "'hello' 长度: $(string_length 'hello')"
echo "大写 'hello': $(to_upper 'hello')"
echo "小写 'HELLO': $(to_lower 'HELLO')"

# 测试备份函数
echo "创建测试文件..."
echo "test content" > test_file.txt
backup_file "test_file.txt" "test_backup"

# 清理
rm -f test_file.txt
rm -rf test_backup
函数编程最佳实践:
  • 使用local关键字声明局部变量
  • 为函数提供清晰的命名和文档
  • 使用返回值表示成功/失败状态
  • 将相关的函数组织成函数库
  • 使用set -e在函数中启用错误检测
  • 避免在函数中修改全局变量

综合示例

通过实际脚本示例展示高级主题的综合应用。

日志分析系统

使用正则表达式、awk、sed和函数创建一个完整的日志分析系统。

log_analyzer.sh
#!/bin/bash

# 综合示例:日志分析系统

# 配置
LOG_FILE=${1:-"/var/log/syslog"}
OUTPUT_DIR="./log_analysis_$(date +%Y%m%d_%H%M%S)"
DEBUG=${DEBUG:-0}

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# 日志函数
log() {
local level=$1
local message=$2

if [ $DEBUG -ge 1 ]; then
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local color=$NC

    case $level in
        "ERROR") color=$RED ;;
        "WARN") color=$YELLOW ;;
        "INFO") color=$BLUE ;;
        "DEBUG") color=$CYAN ;;
    esac

    echo -e "${color}[$timestamp] [$level] ${message}${NC}" >&2
fi
}

# 初始化系统
init_system() {
log "INFO" "初始化日志分析系统"

# 创建输出目录
mkdir -p "$OUTPUT_DIR"
if [ $? -ne 0 ]; then
    log "ERROR" "无法创建输出目录: $OUTPUT_DIR"
    exit 1
fi

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

log "INFO" "输出目录: $OUTPUT_DIR"
log "INFO" "日志文件: $LOG_FILE"
}

# 提取日志级别统计
analyze_log_levels() {
log "INFO" "分析日志级别分布"

local output_file="$OUTPUT_DIR/log_levels.txt"

# 使用awk统计各级别日志数量
awk '
/\[DEBUG\]/ { debug++ }
/\[INFO\]/  { info++ }
/\[WARN\]/  { warn++ }
/\[ERROR\]/ { error++ }
/\[FATAL\]/ { fatal++ }
END {
    total = debug + info + warn + error + fatal
    print "=== 日志级别统计 ==="
    printf "DEBUG: %6d (%5.1f%%)\n", debug, (debug/total)*100
    printf "INFO:  %6d (%5.1f%%)\n", info, (info/total)*100
    printf "WARN:  %6d (%5.1f%%)\n", warn, (warn/total)*100
    printf "ERROR: %6d (%5.1f%%)\n", error, (error/total)*100
    printf "FATAL: %6d (%5.1f%%)\n", fatal, (fatal/total)*100
    print "-------------------"
    printf "总计:  %6d\n", total
}
' "$LOG_FILE" > "$output_file"

log "INFO" "日志级别分析完成: $output_file"
cat "$output_file"
}

# 分析时间模式
analyze_time_patterns() {
log "INFO" "分析时间模式"

local output_file="$OUTPUT_DIR/time_patterns.txt"

# 提取时间并统计每小时日志数量
awk '
/^[0-9]{4}-[0-9]{2}-[0-9]{2}/ {
    # 提取小时
    split($2, time, ":")
    hour = time[1]
    hours[hour]++
}
END {
    print "=== 时间分布统计 ==="
    for (h = 0; h < 24; h++) {
        hour = sprintf("%02d", h)
        count = hours[hour]
        if (count == "") count = 0
        printf "%2s时: %4d 条日志", hour, count
        # 简单条形图
        for (i = 0; i < count/100; i++) printf "█"
        print ""
    }
}
' "$LOG_FILE" > "$output_file"

log "INFO" "时间模式分析完成: $output_file"
cat "$output_file"
}

# 提取错误信息
extract_errors() {
log "INFO" "提取错误信息"

local output_file="$OUTPUT_DIR/errors.txt"

# 使用grep提取错误行,使用sed清理格式
grep -i "error\|fail\|exception\|critical" "$LOG_FILE" | \
sed 's/^[0-9-]* [0-9:]* //' | \
sort | uniq -c | sort -nr > "$output_file"

log "INFO" "错误信息提取完成: $output_file"
echo "前10个最常见的错误:"
head -10 "$output_file"
}

# IP地址分析
analyze_ips() {
log "INFO" "分析IP地址"

local output_file="$OUTPUT_DIR/ip_analysis.txt"

# 使用正则表达式提取IP并统计
grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' "$LOG_FILE" | \
sort | uniq -c | sort -nr > "$output_file"

log "INFO" "IP地址分析完成: $output_file"
echo "前10个最活跃的IP:"
head -10 "$output_file"
}

# 用户代理分析(针对Web日志)
analyze_user_agents() {
log "INFO" "分析用户代理"

local output_file="$OUTPUT_DIR/user_agents.txt"

# 提取用户代理字符串并统计
if grep -q "User-Agent" "$LOG_FILE"; then
    grep -o 'User-Agent: [^"]*' "$LOG_FILE" | \
    sed 's/User-Agent: //' | \
    sort | uniq -c | sort -nr > "$output_file"

    log "INFO" "用户代理分析完成: $output_file"
    echo "前10个最常见的用户代理:"
    head -10 "$output_file"
else
    log "WARN" "未找到用户代理信息"
fi
}

# 响应时间分析(针对Web日志)
analyze_response_times() {
log "INFO" "分析响应时间"

local output_file="$OUTPUT_DIR/response_times.txt"

# 提取响应时间并生成统计
if grep -q "response_time" "$LOG_FILE"; then
    awk '
    /response_time/ {
        for(i=1; i<=NF; i++) {
            if ($i ~ /response_time=/) {
                split($i, a, "=")
                time = a[2]
                sum += time
                count++
                times[count] = time

                # 分类统计
                if (time < 0.1) fast++
                else if (time < 0.5) medium++
                else if (time < 1.0) slow++
                else very_slow++
            }
        }
    }
    END {
        if (count > 0) {
            avg = sum / count

            # 排序计算中位数
            n = asort(times)
            if (n % 2) median = times[(n+1)/2]
            else median = (times[n/2] + times[n/2+1]) / 2

            print "=== 响应时间分析 ==="
            printf "总请求数: %d\n", count
            printf "平均响应时间: %.3f 秒\n", avg
            printf "中位数响应时间: %.3f 秒\n", median
            printf "快速 (<0.1s): %d (%.1f%%)\n", fast, (fast/count)*100
            printf "中等 (0.1-0.5s): %d (%.1f%%)\n", medium, (medium/count)*100
            printf "慢速 (0.5-1s): %d (%.1f%%)\n", slow, (slow/count)*100
            printf "极慢 (>1s): %d (%.1f%%)\n", very_slow, (very_slow/count)*100
        } else {
            print "未找到响应时间数据"
        }
    }
    ' "$LOG_FILE" > "$output_file"

    log "INFO" "响应时间分析完成: $output_file"
    cat "$output_file"
else
    log "WARN" "未找到响应时间信息"
fi
}

# 生成报告
generate_report() {
log "INFO" "生成分析报告"

local report_file="$OUTPUT_DIR/analysis_report.txt"

cat > "$report_file" << EOF
日志分析报告
生成时间: $(date)
日志文件: $LOG_FILE
输出目录: $OUTPUT_DIR

=== 分析摘要 ===
$(date '+%Y-%m-%d %H:%M:%S') - 分析完成

各分析模块输出:
1. 日志级别统计: log_levels.txt
2. 时间分布模式: time_patterns.txt
3. 错误信息汇总: errors.txt
4. IP地址分析: ip_analysis.txt
5. 用户代理分析: user_agents.txt
6. 响应时间分析: response_times.txt

=== 快速统计 ===
总日志行数: $(wc -l < "$LOG_FILE")
错误数量: $(grep -c -i "error" "$LOG_FILE")
唯一IP数量: $(grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' "$LOG_FILE" | sort -u | wc -l)

报告生成完成。
EOF

log "INFO" "报告生成完成: $report_file"
cat "$report_file"
}

# 主函数
main() {
log "INFO" "开始日志分析"

# 初始化
init_system

# 执行各项分析
analyze_log_levels
echo

analyze_time_patterns
echo

extract_errors
echo

analyze_ips
echo

analyze_user_agents
echo

analyze_response_times
echo

# 生成最终报告
generate_report

log "INFO" "日志分析完成"
echo -e "${GREEN}分析结果保存在: $OUTPUT_DIR${NC}"
}

# 使用示例和帮助
show_usage() {
cat << EOF
用法: $0 [日志文件] [选项]

选项:
-d, --debug     启用调试模式
-h, --help      显示此帮助信息

示例:
$0 /var/log/syslog
$0 /var/log/nginx/access.log -d
$0 --help

说明:
此脚本分析日志文件并生成多种统计报告。
支持分析日志级别、时间模式、错误信息、IP地址等。
EOF
}

# 参数处理
case "${1:-}" in
"-h"|"--help")
    show_usage
    exit 0
    ;;
"-d"|"--debug")
    DEBUG=1
    shift
    ;;
esac

# 运行主函数
main "$@"
使用示例:
./log_analyzer.sh /var/log/syslog - 分析系统日志
./log_analyzer.sh /var/log/nginx/access.log -d - 分析Nginx访问日志(调试模式)
./log_analyzer.sh --help - 显示帮助信息

高级主题最佳实践

❌ 不好的做法
# 复杂的单行命令,难以理解和维护
cat file.txt | grep "error" | sed 's/old/new/g' | awk '{print $1}' | sort | uniq

# 硬编码的正则表达式
if [[ "$email" =~ .*@.*\..* ]]; then
echo "valid"
fi

# 全局变量滥用
process_data() {
result=$1
# ... 修改全局变量
}
✅ 好的做法
# 清晰的多步骤处理
extract_errors() {
local file=$1
grep "error" "$file" | \
    sed 's/old/new/g' | \
    awk '{print $1}' | \
    sort | uniq
}

# 使用有意义的正则常量
readonly EMAIL_REGEX='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if [[ "$email" =~ $EMAIL_REGEX ]]; then
echo "valid"
fi

# 使用局部变量和返回值
process_data() {
local input=$1
local result
# ... 处理逻辑
echo "$result"  # 返回结果
}
高级Shell编程建议:
  • 正则表达式 - 使用有意义的变量名存储复杂模式,添加注释说明
  • sed/awk - 将复杂脚本保存在单独文件中,使用版本控制
  • 函数设计 - 保持函数单一职责,合理使用参数和返回值
  • 错误处理 - 使用set -euo pipefail和trap命令
  • 性能优化 - 避免不必要的管道,使用内置字符串操作
  • 可读性 - 使用清晰的变量名,添加适当的注释和文档
常见陷阱和解决方案:
  • 正则表达式贪婪匹配 - 使用.*?进行非贪婪匹配
  • sed特殊字符 - 正确转义&\等特殊字符
  • awk字段分隔 - 使用FS变量明确设置分隔符
  • 函数返回值 - 记住返回值只能是0-255,使用echo返回字符串
  • 性能问题 - 对大文件使用stream处理,避免加载到内存
  • 编码问题 - 明确设置LANGLC_ALL环境变量