强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Bash 脚本编写教程 / 10 - 正则表达式

10 - 正则表达式

10.1 正则表达式基础

正则表达式(Regular Expression, Regex)是文本匹配的强大工具。在 Bash 生态中,有多种正则引擎:

引擎工具类型说明
BREgrep(默认), sed基本正则\+ \? 需转义
EEREgrep -E, sed -E, awk扩展正则+ ? `
PCREgrep -P, perlPerl 正则支持 \d \w 前瞻后顾
Glob[[ == ]]通配符* ? [abc]
Bash Regex[[ =~ ]]ERE 子集支持 BASH_REMATCH 捕获

BRE vs ERE 语法差异

功能BREERE
任意字符..
量词(0或多次)**
量词(1或多次)\++
量词(0或1次)\??
量词(n次)\{n\}{n}
量词(n-m次)\{n,m\}{n,m}
||
分组\(\)()
转义\\
# BRE(grep 默认)
echo "abc123" | grep 'abc[0-9]\+'

# ERE(grep -E)
echo "abc123" | grep -E 'abc[0-9]+'

# 等价但 ERE 更易读

10.2 字符类与元字符

常用元字符

元字符含义示例
.任意单个字符a.c 匹配 abca1c
^行/字符串开头^Hello
$行/字符串结尾World$
\b单词边界\bcat\b 匹配 cat 但不匹配 catch
\d数字(PCRE)[0-9] 等价
\w单词字符(PCRE)[a-zA-Z0-9_] 等价
\s空白字符(PCRE)[ \t\n\r\f] 等价

字符类

# 方括号字符类
[abc]       # a 或 b 或 c
[^abc]      # 不是 a、b、c
[a-z]       # 小写字母
[A-Z]       # 大写字母
[0-9]       # 数字
[a-zA-Z0-9] # 字母和数字
[[:alpha:]] # 字母
[[:digit:]] # 数字
[[:alnum:]] # 字母和数字
[[:space:]] # 空白字符
[[:upper:]] # 大写字母
[[:lower:]] # 小写字母
[[:punct:]] # 标点符号

量词

量词含义示例
*0 次或多次ab*cac, abc, abbc
+1 次或多次ab+cabc, abbc
?0 次或 1 次ab?cac, abc
{n}恰好 n 次a{3}aaa
{n,m}n 到 m 次a{2,4}aa, aaa, aaaa
{n,}至少 n 次a{2,}aa, aaa, …

10.3 grep 正则匹配

# 基本匹配
echo "Hello World" | grep "World"       # 匹配
echo "Hello World" | grep -i "hello"    # 忽略大小写

# 行号显示
grep -n "error" /var/log/syslog

# 递归搜索
grep -rn "TODO" ./src/

# 只输出匹配部分
echo "Hello World 123" | grep -oE '[0-9]+'  # 输出: 123

# 反向匹配(不包含)
grep -v "debug" logfile.txt

# 仅匹配文件名
grep -rl "pattern" /path/to/dir/

# 统计匹配行数
grep -c "error" logfile.txt

# 上下文显示
grep -B 2 -A 3 "error" logfile.txt  # 前2行后3行

# ERE 模式
grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' <<< "192.168.1.1"

# PCRE 模式
grep -P '\d{3}-\d{4}' <<< "电话: 123-4567"

10.4 sed 正则替换

# 基本替换
echo "Hello World" | sed 's/World/Bash/'        # Hello Bash
echo "hello hello" | sed 's/hello/Hi/g'          # Hi Hi(全局替换)

# 使用不同的分隔符(处理路径时有用)
echo "/usr/local/bin" | sed 's|/usr/local|/opt|'  # /opt/bin
echo "/usr/local/bin" | sed 's#/usr/local#/opt#'  # /opt/bin

# 捕获组与反向引用
echo "2026-05-10" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/'
# 输出: 10/05/2026

# 删除匹配行
sed '/^#/d' config.txt          # 删除注释行
sed '/^$/d' config.txt          # 删除空行

# 插入和追加
sed '2a\新增的一行' file.txt    # 在第2行后追加
sed '2i\新增的一行' file.txt    # 在第2行前插入

# 范围操作
sed '3,5d' file.txt             # 删除第3-5行
sed '/start/,/end/d' file.txt   # 删除 start 到 end 之间的行

# 就地编辑(直接修改文件)
sed -i 's/old/new/g' file.txt        # Linux
sed -i '' 's/old/new/g' file.txt     # macOS
sed -i.bak 's/old/new/g' file.txt    # 带备份

10.5 Bash [[ =~ ]] 正则匹配

# 基本匹配
email="user@example.com"
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "有效的邮箱地址"
fi

# BASH_REMATCH 捕获组
version="v1.2.3"
if [[ "$version" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
    echo "完整匹配: ${BASH_REMATCH[0]}"   # v1.2.3
    echo "主版本:   ${BASH_REMATCH[1]}"   # 1
    echo "次版本:   ${BASH_REMATCH[2]}"   # 2
    echo "补丁:     ${BASH_REMATCH[3]}"   # 3
fi

# 提取 IP 地址
log_line="2026-05-10 192.168.1.100 ERROR Something happened"
if [[ "$log_line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then
    echo "IP: ${BASH_REMATCH[0]}"
fi

# 验证输入格式
validate_date() {
    local input="$1"
    if [[ "$input" =~ ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$ ]]; then
        return 0
    else
        return 1
    fi
}

validate_date "2026-05-10" && echo "有效日期" || echo "无效日期"
validate_date "2026-13-45" && echo "有效日期" || echo "无效日期"

# 正则变量(需要存入变量才能正常工作)
pattern='^[0-9]+$'
if [[ "12345" =~ $pattern ]]; then
    echo "是纯数字"
fi
# ⚠️ 注意:不能将正则放在引号中
# [[ "123" =~ "^[0-9]+$" ]]  # ❌ 这会变成字面匹配

⚠️ 重要:在 [[ =~ ]] 中,正则表达式不能用引号包裹(会变成字面匹配)。但可以存入变量中使用。

10.6 awk 模式匹配

# awk 正则匹配
echo -e "apple\nbanana\napricot\ncherry" | awk '/^a/'

# 条件匹配
awk '$3 ~ /^[0-9]+$/ {print $1, $3}' <<< "item1 price 100"

# 提取并转换
echo "2026-05-10 error: something failed" | \
    awk '{
        if (match($0, /[0-9]{4}-[0-9]{2}-[0-9]{2}/)) {
            print "日期:", substr($0, RSTART, RLENGTH)
        }
        if (match($0, /error: (.+)/, arr)) {
            print "错误:", arr[1]
        }
    }'

# 复杂文本解析
cat << 'EOF' | awk -F'|' 'NR>2 && $3 ~ /active/ {printf "%s (%s)\n", $2, $4}'
ID|Name  |Status |Email
--|------ |-------|-----
1 |张三   |active |zhangsan@test.com
2 |李四   |inactive|lisi@test.com
3 |王五   |active |wangwu@test.com
EOF

10.7 业务场景:日志解析与验证

#!/bin/bash
# log_parser.sh —— 正则表达式综合应用
set -euo pipefail

# 验证函数集合
validators::is_ipv4() {
    local ip="$1"
    [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
    
    # 检查每段 0-255
    IFS='.' read -r a b c d <<< "$ip"
    ((a <= 255 && b <= 255 && c <= 255 && d <= 255))
}

validators::is_email() {
    [[ "$1" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
}

validators::is_url() {
    [[ "$1" =~ ^https?://[a-zA-Z0-9.-]+(:[0-9]+)?(/.*)?$ ]]
}

validators::is_semver() {
    [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$ ]]
}

# 测试
echo "=== 输入验证测试 ==="
tests=("192.168.1.1" "999.999.999.999" "not-an-ip")
for ip in "${tests[@]}"; do
    validators::is_ipv4 "$ip" && echo "✅ $ip 是有效IP" || echo "❌ $ip 无效"
done

# Nginx 日志解析
parse_nginx_log() {
    local line="$1"
    
    if [[ "$line" =~ ^([0-9.]+)\ -\ -\ \[([^\]]+)\]\ \"([A-Z]+)\ ([^\ ]+)\ [^\"]+\"\ ([0-9]+)\ ([0-9]+) ]]; then
        echo "IP:     ${BASH_REMATCH[1]}"
        echo "时间:   ${BASH_REMATCH[2]}"
        echo "方法:   ${BASH_REMATCH[3]}"
        echo "路径:   ${BASH_REMATCH[4]}"
        echo "状态码: ${BASH_REMATCH[5]}"
        echo "大小:   ${BASH_REMATCH[6]} bytes"
    else
        echo "解析失败"
    fi
}

echo ""
echo "=== Nginx 日志解析 ==="
sample_log='192.168.1.100 - - [10/May/2026:13:45:30 +0000] "GET /api/users HTTP/1.1" 200 1234'
parse_nginx_log "$sample_log"

# 从文本中提取所有 URL
extract_urls() {
    grep -oE 'https?://[a-zA-Z0-9./?=&#%_-]+' "$@" 2>/dev/null
}

# 从文本中提取所有邮箱
extract_emails() {
    grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' "$@" 2>/dev/null
}

10.8 正则表达式常用模式速查

模式用途示例
^[0-9]+$纯数字12345
^[a-zA-Z]+$纯字母Hello
^[a-zA-Z0-9_]+$字母数字下划线user_123
^\S+@\S+\.\S+$简易邮箱a@b.c
^https?://URL 开头https://...
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}IPv4192.168.1.1
^#注释行# comment
^\s*$空白行
\b[0-9a-f]{32}\bMD5 哈希d41d8cd98f00...
[0-9]{4}-[0-9]{2}-[0-9]{2}ISO 日期2026-05-10

10.9 注意事项

陷阱说明解决方案
=~ 中使用引号正则变成字面匹配将正则存入变量
BRE 中 + ? 不工作需要转义使用 grep -Esed -E
贪婪匹配.* 匹配尽可能多使用 .*?(PCRE)或更精确的模式
. 匹配换行默认 . 不匹配 \n使用 [\s\S]s 标志
特殊字符未转义. * + 等在文本中使用 \ 转义

10.10 扩展阅读