Shell脚本编程不仅仅是简单的命令组合,它还包含许多强大的高级功能。掌握这些高级主题可以让您编写更高效、更强大的脚本。
强大的模式匹配工具,用于文本搜索、替换和验证。
流编辑器,用于对文本进行非交互式的编辑和处理。
强大的文本处理语言,特别适合处理结构化数据。
创建可重用的代码块,提高脚本的模块化和可维护性。
处理系统信号,实现优雅的脚本终止和资源清理。
控制子进程,实现并行处理和作业控制。
这些高级主题是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+ |
空格, 制表符 |
#!/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
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 |
#!/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 's/old/new/' file - 替换第一个old为newsed 's/old/new/g' file - 替换所有old为newsed 's/old/new/2' file - 替换第二个old为newsed '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是一种强大的文本处理语言,特别适合处理结构化数据。
| 内置变量 | 说明 | 示例 |
|---|---|---|
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 |
#!/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 '/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脚本中实现代码重用和模块化的关键工具。
#!/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和函数创建一个完整的日志分析系统。
#!/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" # 返回结果
}
set -euo pipefail和trap命令.*?进行非贪婪匹配&、\等特殊字符FS变量明确设置分隔符LANG和LC_ALL环境变量