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!"
课后练习
实践任务
- 编写正则表达式验证邮箱、手机号、IP地址
- 创建包含多个函数的工具库
- 编写支持参数的日志分析脚本
- 实现带超时和信号处理的脚本
- 使用并行处理优化脚本性能
- 编写完整的系统监控脚本
下一篇预告:我们将学习Shell脚本实战,通过真实案例掌握自动化运维技能。