Linux正则表达式完全指南:从基础到高级应用

2025/11/11 Linux 系统管理 共 34584 字,约 99 分钟

Linux正则表达式完全指南:从基础到高级应用

正则表达式(Regular Expression)是一种强大的文本匹配和处理工具,在Linux系统管理、编程开发、数据处理等领域有着广泛的应用。无论是日志分析、配置文件修改还是数据提取,掌握正则表达式都能大幅提高工作效率。本文将全面介绍Linux环境下正则表达式的基本概念、语法规则、常用工具以及实战应用,帮助您从入门到精通正则表达式技术。

1. 正则表达式基础概念

1.1 什么是正则表达式

在开始学习正则表达式之前,我们需要先明确它与Shell通配符的区别。虽然它们都用于模式匹配,但应用场景和语法规则有很大不同:

通配符 vs 正则表达式:

  • 通配符:主要用于匹配文件名,是完全匹配,由Shell直接处理,支持命令如ls、find、cp等
  • 正则表达式:主要用于匹配文件内容,是包含匹配,由具体工具处理,支持命令如grep、sed、awk等

下面是一个通配符使用的实际案例:

# 创建测试目录和文件
mkdir regex
cd regex/
touch user-{1..3}.sh {a..d}.log

# 查看创建的文件
ll
# 总用量 0
# -rw-r--r--. 1 user user 0 11月 11 14:31 a.log
# -rw-r--r--. 1 user user 0 11月 11 14:31 b.log
# -rw-r--r--. 1 user user 0 11月 11 14:31 c.log
# -rw-r--r--. 1 user user 0 11月 11 14:31 d.log
# -rw-r--r--. 1 user user 0 11月 11 14:31 user-1.sh
# -rw-r--r--. 1 user user 0 11月 11 14:31 user-2.sh
# -rw-r--r--. 1 user user 0 11月 11 14:31 user-3.sh

# 使用*.log通配符匹配所有.log文件
ls *.log
# a.log  b.log  c.log  d.log

# 使用u*通配符匹配以u开头的文件
ls u*
# user-1.sh  user-2.sh  user-3.sh

# 使用user?3*通配符匹配特定模式的文件
ls user?3*
# user-3.sh

# 使用user-[13]*通配符匹配包含1或3的用户文件
ls user-[13]*
# user-1.sh  user-3.sh

# 使用user-[13].*通配符匹配指定数字的用户文件
ls user-[13].*
# user-1.sh  user-3.sh

想了解更多通配符与正则表达式的区别,可以参考我的文章:Linux中通配符与正则表达式的区别与应用详解

1.2 什么是正则表达式

正则表达式是一种用于描述字符串模式的表达式,它可以用来匹配、查找、替换和验证符合特定模式的文本。在Linux系统中,正则表达式被广泛应用于grep、sed、awk等文本处理工具中。

1.2 正则表达式的两种主要流派

在Linux环境中,我们主要接触到两种正则表达式流派:

  • 基本正则表达式(BRE - Basic Regular Expression):传统的正则表达式语法,被grep等基础工具使用
  • 扩展正则表达式(ERE - Extended Regular Expression):更现代的正则表达式语法,被grep -E、awk等工具使用

两者的主要区别在于元字符的使用方式,ERE中某些字符不需要转义即可表示特殊含义。

1.3 正则表达式在Linux中的应用场景

  • 文本搜索:在文件中查找特定模式的文本
  • 日志分析:从大量日志中提取有用信息
  • 配置管理:修改配置文件中的特定设置
  • 数据验证:验证输入数据是否符合特定格式
  • 文本转换:批量替换或格式化文本内容
  • 数据提取:从复杂文本中提取所需字段

2. 基本正则表达式语法

2.1 字符匹配

普通字符

普通字符直接匹配自身,如abc匹配字符串”abc”。

特殊字符(需要转义)

以下字符在正则表达式中有特殊含义,如需匹配它们本身,需要使用反斜杠\转义:

. * ? + [ ] ( ) { } ^ $ | \n \t

例如,要匹配点号,需要使用\.

2.2 字符类

字符类用于匹配一组字符中的任意一个:

  • [abc]:匹配字符a、b或c中的任意一个
  • [^abc]:匹配除了a、b、c之外的任意字符
  • [a-z]:匹配任意小写字母
  • [A-Z]:匹配任意大写字母
  • [0-9]:匹配任意数字
  • [a-zA-Z0-9]:匹配任意字母或数字

2.3 预定义字符类

BRE中常用的预定义字符类:

  • .:匹配除换行符之外的任意单个字符
  • \d:匹配任意数字(等同于[0-9])
  • \D:匹配任意非数字字符(等同于[^0-9])
  • \w:匹配任意字母、数字或下划线(等同于[a-zA-Z0-9_])
  • \W:匹配任意非字母、数字或下划线的字符
  • \s:匹配任意空白字符(空格、制表符、换行符等)
  • \S:匹配任意非空白字符

2.4 POSIX字符类的实践应用

POSIX字符类是一种更标准化的字符类表示方式,在Linux正则表达式中广泛使用。它们以[[:class:]]的形式表示,提供了更清晰和跨平台的字符匹配能力。

测试文件内容(file1.txt)

 acd 
 abc 
 a_c 
 aZc 
 aZd 
 a c 
 a3c 

POSIX字符类匹配案例分析

  1. 字母数字字符类 [:alnum:]
    # 匹配以a开头,后跟一个字母或数字,然后是c结尾的行
    egrep "^a[[:alnum:]]c$" file1.txt
    # 输出:
    # abc 
    # aZc 
    # a3c 
    

    说明[:alnum:]匹配任意字母(大小写)或数字,所以匹配到了包含小写字母b、大写字母Z和数字3的行。

  2. 字母字符类 [:alpha:]
    # 匹配以a开头,后跟一个字母,然后是c结尾的行
    egrep "^a[[:alpha:]]c$" file1.txt
    # 输出:
    # abc 
    # aZc 
    

    说明[:alpha:]只匹配字母(大小写),不匹配数字,所以只有包含b和Z的行被匹配到,而包含数字3的行没有匹配。

  3. 数字字符类 [:digit:]
    # 匹配以a开头,后跟一个数字,然后是c结尾的行
    egrep "^a[[:digit:]]c$" file1.txt
    # 输出:
    # a3c 
    

    说明[:digit:]只匹配数字字符,所以只有包含数字3的行被匹配到。

  4. 小写字母字符类 [:lower:]
    # 匹配以a开头,后跟一个小写字母,然后是c结尾的行
    egrep "^a[[:lower:]]c$" file1.txt
    # 输出:
    # abc 
    

    说明[:lower:]只匹配小写字母,所以只有包含小写字母b的行被匹配到,而包含大写字母Z的行没有匹配。

  5. 大写字母字符类 [:upper:]
    # 匹配以a开头,后跟一个大写字母,然后是c结尾的行
    egrep "^a[[:upper:]]c$" file1.txt
    # 输出:
    # aZc 
    

    说明[:upper:]只匹配大写字母,所以只有包含大写字母Z的行被匹配到。

  6. 可打印字符类 [:print:]
    # 匹配以a开头,后跟一个可打印字符,然后是c结尾的行
    egrep "^a[[:print:]]c$" file1.txt
    # 输出:
    # abc 
    # a_c 
    # aZc 
    # a c 
    # a3c 
    

    说明[:print:]匹配所有可打印字符,包括字母、数字、标点符号和空格,所以匹配到了大部分行。

  7. 标点符号字符类 [:punct:]
    # 匹配以a开头,后跟一个标点符号,然后是c结尾的行
    egrep "^a[[:punct:]]c$" file1.txt
    # 输出:
    # a_c 
    

    说明[:punct:]只匹配标点符号字符,所以只有包含下划线(_)的行被匹配到。

  8. 空白字符类 [:blank:][:space:]
    # 匹配以a开头,后跟一个空格或制表符,然后是c结尾的行
    egrep "^a[[:blank:]]c$" file1.txt
    # 输出:
    # a c 
    
    # 匹配以a开头,后跟任意空白字符,然后是c结尾的行
    egrep "^a[[:space:]]c$" file1.txt
    # 输出:
    # a c 
    

    说明[:blank:]匹配空格和制表符,而[:space:]匹配所有空白字符(包括空格、制表符、换行符等)。在这个例子中,两种匹配效果相同,因为只有空格需要匹配。

  9. 十六进制数字字符类 [:xdigit:]
    # 匹配以a开头,后跟一个十六进制数字,然后是c结尾的行
    egrep "^a[[:xdigit:]]c$" file1.txt
    # 输出:
    # abc 
    # a3c 
    

    说明[:xdigit:]匹配十六进制数字(0-9, a-f, A-F),所以匹配到了包含小写字母b(在十六进制中是有效字符)和数字3的行,但没有匹配包含大写字母Z的行(Z不是十六进制数字)。

POSIX字符类的关键特性

  1. 标准化:POSIX字符类提供了标准化的字符集合表示,确保在不同系统上具有一致的行为
  2. 可读性:相比于复杂的字符范围表示,POSIX字符类更直观易读
  3. 全面性:涵盖了各种常见的字符类型需求
  4. 组合使用:可以与其他正则表达式元素组合使用,创建更复杂的匹配模式

常用POSIX字符类总结

字符类 说明 等价的简单字符类
[:alnum:] 字母和数字 [a-zA-Z0-9]
[:alpha:] 字母 [a-zA-Z]
[:digit:] 数字 [0-9]
[:lower:] 小写字母 [a-z]
[:upper:] 大写字母 [A-Z]
[:punct:] 标点符号 各种标点符号
[:space:] 空白字符 空格、制表符、换行符等
[:blank:] 空格和制表符 空格和\t
[:print:] 可打印字符 字母、数字、标点符号、空格等
[:xdigit:] 十六进制数字 [0-9a-fA-F]
[:cntrl:] 控制字符 非打印控制字符
[:graph:] 非空白可打印字符 字母、数字、标点符号

实用技巧

  • 在编写跨平台的正则表达式时,优先使用POSIX字符类以确保兼容性
  • 在需要匹配特定类型字符但不确定具体范围时,POSIX字符类提供了便捷的解决方案
  • 对于复杂的字符匹配需求,可以组合使用多个POSIX字符类
  • 在grep和egrep命令中,POSIX字符类需要放在方括号内使用,格式为[[:class:]]

2.4 量词

量词用于指定前面的字符或表达式应该匹配多少次:

  • *:匹配前面的字符或表达式0次或多次
  • \+:匹配前面的字符或表达式1次或多次(在BRE中需要转义)
  • \?:匹配前面的字符或表达式0次或1次(在BRE中需要转义)
  • \{n\}:匹配前面的字符或表达式恰好n次(在BRE中需要转义)
  • \{n,\}:匹配前面的字符或表达式至少n次(在BRE中需要转义)
  • \{n,m\}:匹配前面的字符或表达式n到m次(在BRE中需要转义)

2.5 位置锚定

位置锚定用于匹配字符串的特定位置:

  • ^:匹配字符串的开头
  • $:匹配字符串的结尾
  • \b:匹配单词边界
  • \B:匹配非单词边界

字符边界(\b)的重要性:手机号码匹配案例

字符边界\b在需要精确匹配特定长度的数字或单词时非常重要。下面通过一个手机号码匹配的实际案例来说明:

测试文件内容(phone.txt)

 13412345678 
 135666666667 
 13a12345678 
 198123456 

匹配过程与分析

  1. 不使用字符边界的匹配
    egrep '1[3-9][0-9]{9}' phone.txt
    # 输出:
    #  13412345678 
    #  135666666667 
    

    问题:这个正则表达式匹配了11位的有效手机号13412345678,但同时也错误地匹配了12位的数字串135666666667,因为正则表达式只检查是否包含11位符合模式的数字,而不关心数字串的长度。

  2. 使用字符边界的精确匹配
    egrep '\b1[3-9][0-9]{9}\b' phone.txt
    # 输出:
    #  13412345678 
    

    解决效果:添加\b字符边界后,正则表达式只匹配完整的11位手机号码,正确地排除了过长的数字串135666666667

为什么需要字符边界

  • \b匹配的是单词边界位置,它确保匹配的模式两侧不是字母、数字或下划线
  • 在手机号码匹配中,\b1[3-9][0-9]{9}\b确保:
    1. 手机号码的开头前面要么是空白字符,要么是行首
    2. 手机号码的结尾后面要么是空白字符,要么是行尾
    3. 有效防止部分匹配,确保只匹配完整的11位手机号码

实际应用价值

在处理日志文件、用户数据或文本内容时,使用字符边界可以避免提取到错误的部分匹配结果,特别是在处理具有特定格式要求的数据(如电话号码、身份证号等)时尤为重要。

3. 扩展正则表达式语法

扩展正则表达式(ERE)提供了更简洁的语法,不需要对某些元字符进行转义:

3.1 主要语法差异

  • +:匹配前面的字符或表达式1次或多次(不需要转义)
  • ?:匹配前面的字符或表达式0次或1次(不需要转义)
  • {n}:匹配前面的字符或表达式恰好n次(不需要转义)
  • {n,}:匹配前面的字符或表达式至少n次(不需要转义)
  • {n,m}:匹配前面的字符或表达式n到m次(不需要转义)
  • |:匹配左侧或右侧的表达式(不需要转义)
  • ():分组(不需要转义)

3.2 分组和引用

  • (pattern):将pattern作为一个分组处理
  • \1, \2…:反向引用前面的分组,如(abc)\1匹配”abcabc”

3.3 贪婪与非贪婪匹配

  • 贪婪匹配:默认情况下,量词会尽可能多地匹配字符
  • 非贪婪匹配:在量词后添加?,使其尽可能少地匹配字符(部分工具支持)

4. Linux中常用的正则表达式工具

#定制目标类型变量 
target_type=(登录 注册) 
#定制普通变量 - 修复正则表达式
user_regex='^[a-zA-Z0-9_@.]{6,15}$' 
passwd_regex='^[a-zA-Z0-9.]{6,8}$' 
phone_regex='^\b1[3-9][0-9]{9}\b$' 
email_regex='^[a-zA-Z0-9_]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,5}$' 

#检测用户名规则 
check_func(){
    # 接收函数参数 
    target=$1 
    target_regex=$2 
    # 判断目标格式是否有效 
    echo "$target" | egrep "${target_regex}" >/dev/null && echo "true" || echo "false" 
}

#定制服务的操作提示功能函数 
menu(){
    echo -e "\e[31m---------------管理平台登录界面---------------"
    echo -e " 1: 登录  2: 注册"
    echo -e "-------------------------------------------\033[0m"
}

#定制帮助信息 
Usage(){
    echo "请输入正确的操作类型"
}

#管理平台用户注册过程 
user_register_check(){
    read -p "> 请输入用户名: " login_user 
    user_result=$(check_func "${login_user}" "${user_regex}") 
    if [ "${user_result}" == "true" ];then 
        read -p "> 请输入密码: " login_passwd 
        passwd_result=$(check_func "${login_passwd}" "${passwd_regex}") 
        if [ "${passwd_result}" == "true" ];then 
            read -p "> 请输入手机号: " login_phone 
            phone_result=$(check_func "${login_phone}" "${phone_regex}") 
            if [ "${phone_result}" == "true" ];then 
                read -p "> 请输入邮箱: " login_email
                email_result=$(check_func "${login_email}" "${email_regex}") 
                if [ "${email_result}" == "true" ];then 
                    echo -e "\e[31m----用户注册信息内容----"
                    echo -e " 用户名称: ${login_user}"
                    echo -e " 登录密码: ${login_passwd}"
                    echo -e " 手机号码: ${login_phone}"
                    echo -e " 邮箱地址: ${login_email}"
                    echo -e "------------------------\033[0m"
                    read -p "> 是否确认注册[yes|no]: " login_status 
                    [ "${login_status}" == "yes" ] && echo "用户 ${login_user} 注册成功" && exit || return 
                else 
                    echo "邮箱地址格式不规范"
                fi 
            else 
                echo "手机号码格式不规范"
            fi 
        else 
            echo "登录密码格式不规范"
        fi 
    else 
        echo "用户名称格式不规范"
    fi 
}

#定制业务逻辑 
while true 
do 
    menu 
    read -p "> 请输入要操作的目标类型: " target_id 
    if [ "${target_type[$target_id-1]}" == "登录" ];then 
        echo "开始登录管理平台..."
    elif [ "${target_type[$target_id-1]}" == "注册" ];then 
        user_register_check 
    else 
        Usage 
    fi 
done

4.1 关键正则表达式解析

4.1.1 用户名正则表达式

修复前^[0-Z_@.]{6,15}$ 修复后^[a-zA-Z0-9_@.]{6,15}$

解析

  • 修复前的[0-Z]在ASCII表中包含了数字0-9、大写字母A-Z,但中间还包含了一些特殊字符(如:;<=等)
  • 修复后的[a-zA-Z0-9]明确表示只包含大小写字母和数字,更加精确和安全
  • {6,15}确保用户名长度在6到15个字符之间

4.1.2 密码正则表达式

修复前^[0-Z.]{6,8}$ 修复后^[a-zA-Z0-9.]{6,8}$

解析

  • 类似用户名正则,修复了字符范围表示,确保只接受字母、数字和点号
  • {6,8}限制密码长度在6到8个字符之间

4.1.3 手机号正则表达式

修复前^\<1[3-9][0-9]{9}\>$ 修复后^\b1[3-9][0-9]{9}\b$

解析

  • 修复前使用了<>作为边界标记,这不是标准的正则表达式语法
  • 修复后使用了标准的\b字符边界标记,确保只匹配完整的手机号码
  • 1[3-9][0-9]{9}确保手机号以1开头,第二位是3-9之间的数字,后面跟着9位数字
  • 结合^$,确保整个输入行只包含一个有效的手机号码

4.1.4 邮箱正则表达式

修复前^[0-Z_]+\@[0-Z]+\.[0-Z]{2,5}$ 修复后^[a-zA-Z0-9_]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,5}$

解析

  • 修复了字符范围表示,确保用户名部分只包含字母、数字、下划线
  • 域名部分只包含字母和数字
  • 顶级域名长度限制在2-5个字母之间
  • 移除了不必要的转义字符,@在双引号中不需要转义

4.1.5 脚本执行流程分析

  1. 初始化阶段
    • 定义了目标类型数组和各项数据验证的正则表达式
    • 创建了各种功能函数,如check_funcmenuUsageuser_register_check
  2. 主循环
    • 通过while true创建一个无限循环
    • 在循环中显示菜单并等待用户输入
  3. 用户交互流程
    • 如果用户选择登录(输入1),显示登录提示
    • 如果用户选择注册(输入2),调用user_register_check函数进行注册验证
    • 如果输入其他数字,显示帮助信息
  4. 注册验证流程
    • 用户名验证:要求6-15个字符,只允许字母、数字、下划线、@和点号
    • 密码验证:要求6-8个字符,只允许字母、数字和点号
    • 手机号验证:使用字符边界确保精确匹配11位手机号
    • 邮箱验证:确保符合标准邮箱格式
    • 信息确认:显示收集的所有信息,等待用户确认
    • 注册完成:如果用户确认,显示注册成功信息并退出脚本

4.1.6 正则表达式在用户注册中的重要性

  1. 数据有效性验证:确保用户输入的数据符合预期格式,防止非法数据进入系统
  2. 安全性提升:通过限制可接受的字符集,减少潜在的注入攻击风险
  3. 用户体验优化:即时反馈输入错误,帮助用户正确填写表单
  4. 数据一致性:确保数据库中存储的信息格式统一,便于后续处理和查询
  5. 业务规则实施:通过正则表达式可以轻松实现各种业务规则,如长度限制、字符类型限制等

这个案例展示了正则表达式在实际应用中的重要性,特别是在需要精确数据验证的场景下。通过合理使用正则表达式,我们可以构建更加健壮和安全的用户注册系统。

4.2 grep - 文本搜索工具

grep是最常用的文本搜索工具,可以使用正则表达式来匹配文本。

grep与egrep的区别

在Linux系统中,grepegrep都是文本搜索工具,但它们在处理正则表达式时有重要区别:

  • grep:默认使用基本正则表达式(BRE),某些特殊字符(如+, ?, |, ()等)需要转义才能使用
  • egrep:等同于grep -E,使用扩展正则表达式(ERE),特殊字符不需要转义即可使用

实际应用案例分析

下面是一个使用grep和egrep进行文本匹配的实际案例:

测试文件内容(keepalived.conf)

! Configuration File for keepalived 
global_defs { 
  router_id kpmaster 
}
vrrp_instance VI_1 { 
   state MASTER 
   interface ens33 
   virtual_router_id 50 
   nopreempt 
   priority 100 
   advert_int 1 
   virtual_ipaddress { 
       192.168.8.100 
   } 
}

案例分析

  1. 点号(.)匹配单个字符
    # 匹配"st"开头,"e"结尾,中间有2个任意字符的字符串
    grep 'st..e' keepalived.conf  # 匹配到"state MASTER"
       
    # 匹配"ens"开头,后面有2个任意字符的字符串
    grep 'ens..' keepalived.conf  # 匹配到"interface ens33"
       
    # 匹配"ens"开头,后面有1个任意字符的字符串
    grep 'ens.' keepalived.conf   # 匹配到"interface ens33"
    
  2. 字符类匹配
    # 匹配"i"开头,"t"结尾,中间是任意小写字母的字符串
    grep 'i[a-z]t' keepalived.conf  # 匹配到interface, virtual_router_id, advert_int, virtual_ipaddress
       
    # 匹配"i"开头,"t"结尾,中间是a-n范围内小写字母的字符串
    grep 'i[a-n]t' keepalived.conf  # 匹配到interface, advert_int
       
    # 匹配包含字符b或c的行
    grep '[b-c]' keepalived.conf    # 匹配到包含global_defs, vrrp_instance, interface的行
    
  3. egrep使用扩展正则表达式
    # 使用egrep匹配包含x、y或z字符的行
    egrep '[x-z]' keepalived.conf   # 匹配到"priority 100"(包含字母z)
    

关键发现

  • 在这个配置文件中,grep 'st..e'成功匹配到”state MASTER”,验证了点号(.)可以匹配任意单个字符
  • grep 'ens.'grep 'ens..'都能匹配”ens33”,说明点号匹配是精确的字符数量
  • 字符类[a-z]和范围限制[a-n]的区别在于匹配范围的大小
  • egrep '[x-z]'成功匹配到”priority 100”中的字母”z”,展示了egrep处理字符范围的能力

更多实际应用案例

继续通过keepalived.conf文件展示更多正则表达式的应用:

  1. 反向字符类匹配
    # 匹配除小写字母a-Z、下划线、空格、大括号、数字0-5之外的任意字符的行
    grep '[^a-Z_ }{0-5]' keepalived.conf  # 匹配到所有包含其他字符(如大写字母、数字6-9等)的行
    

    说明[^...]是反向字符类,表示匹配除指定字符之外的任意字符。在这个例子中,它匹配除了小写字母a-Z、下划线、空格、大括号{}和数字0-5以外的所有字符,因此几乎匹配了文件中的所有行。

  2. 使用egrep的或操作符
    # 使用|操作符匹配包含"state"或"priority"的行
    egrep 'state|priority' keepalived.conf  # 匹配到"state MASTER"和"priority 100"
    

    说明|是扩展正则表达式中的或操作符,在egrep中可以直接使用。它允许我们在一个模式中指定多个选项,只要满足其中一个即可匹配。

  3. 子串匹配
    # 匹配包含子串"st"或"pri"的行
    egrep 'st|pri' keepalived.conf  # 匹配到包含"router_id"、"state"、"priority"等的行
    

    说明:这个例子展示了如何使用或操作符匹配多个子串。'st|pri'会匹配包含”st”或”pri”的任何行,所以会匹配到”router_id”(包含”st”)、”state”和”priority”等内容。

位置锚点的实际应用

下面通过nginx.conf文件展示行首(^)和行尾($)锚点的使用:

nginx.conf文件中的相关内容

worker_processes  1;

http {
    server_name  localhost;
    # 其他配置...
}

行首和行尾锚点匹配案例

  1. 行首匹配
    # 匹配以"wor"开头的行
    grep '^wor' nginx.conf  # 匹配到"worker_processes  1;"
       
    # 精确匹配以"http {"开头的行
    grep '^http {$' nginx.conf  # 匹配到"http {"
    

    说明^符号用于匹配行首位置,确保模式必须从一行的开头开始匹配。这在配置文件中查找特定章节或配置项时特别有用。

  2. 行尾匹配
    # 匹配以"st;"结尾的行
    grep 'st;$' nginx.conf  # 匹配到"    server_name  localhost;"
    

    说明$符号用于匹配行尾位置,确保模式必须在行的末尾结束。这对于查找特定结尾的配置行很有帮助。

  3. 行首和行尾组合匹配
    # 匹配以"w"开头且以";"结尾的行
    grep '^w.*;$' nginx.conf  # 匹配到"worker_processes  1;"
    

    说明:结合使用^$可以匹配完整的行内容。^w.*;$表示匹配以字母”w”开头,以分号”;”结尾,中间可以有任意字符的完整行。这种模式对于精确匹配配置文件中的特定设置非常有用。

实用技巧

  • 位置锚点特别适合在配置文件中精确定位特定的配置行
  • 结合通配符和锚点可以创建更精确的匹配模式
  • 在处理结构化文本(如配置文件、日志文件)时,位置锚点可以显著提高匹配的准确性

空行匹配与反向过滤

在处理配置文件时,经常需要处理空行和包含空白字符的行。下面通过nginx.conf文件展示相关操作:

nginx.conf文件的部分内容

#user  nobody;

worker_processes  1;

http {
    sendfile        on;

    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

空行与空白行匹配案例

  1. 匹配空白行
    # 匹配只包含空白字符的行(空格或制表符)
    grep '^[[:space:]]$' nginx.conf
    

    说明^[[:space:]]$模式匹配以空白字符开头并以空白字符结尾的行,即只包含空白字符的行。在上面的示例中,没有完全匹配的行。

  2. 匹配空行
    # 匹配完全空的行(不包含任何字符)
    grep '^$' nginx.conf
    

    说明^$模式匹配完全空的行,即行首紧跟着行尾,中间没有任何字符。在上面的示例中,nginx.conf文件中有多个空行,会输出这些空行。

  3. 反向过滤空行
    # 显示所有非空行
    grep -v '^$' nginx.conf
    

    说明-v选项用于反向匹配,即不匹配指定模式的行。^$匹配空行,所以grep -v '^$'会显示所有非空行,这在处理配置文件时非常有用,可以过滤掉空行以获得更干净的输出。

  4. 过滤空行和空白行
    # 过滤掉空行和只包含空白字符的行
    grep -v '^[[:space:]]*$' nginx.conf
    

    说明^[[:space:]]*$匹配空行或只包含空白字符的行,结合-v选项可以过滤掉所有空白行和空行,只显示包含实际内容的行。

实用技巧

  • 在处理日志文件或配置文件时,过滤空行可以使输出更清晰
  • grep -v '^$' file | grep -v '^#' 可以同时过滤空行和注释行
  • 正则表达式^[[:space:]]*可以匹配行首的任意空白字符,常用于定位实际内容的开始

结合sed进行信息提取

在实际工作中,我们经常需要结合使用grep和sed工具,先用grep过滤出包含目标信息的行,再用sed通过正则表达式提取或转换特定部分。下面通过zoo.cfg配置文件展示sed信息提取的实际应用:

zoo.cfg文件中的相关内容

server.1=10.0.0.12:2182:2183 

sed信息提取案例分析

  1. 提取配置项名称(方法一)
    # 使用多个分组捕获并只输出配置项名称
    grep server.1 zoo.cfg | sed -r 's/(.*)=(.*):(.*):(.*)/\1/'
    # 输出:
    # server.1
    

    说明:这个命令使用了-r选项启用扩展正则表达式,将一行分为四个分组:配置项名称、IP地址、第一个端口和第二个端口,然后通过\1引用第一个分组(配置项名称)。

  2. 提取配置项名称(方法二)
    # 使用更简洁的正则表达式提取配置项名称
    grep server.1 zoo.cfg | sed -r 's/(.*)=.*/\1/'
    # 输出:
    # server.1
    

    说明:这个方法更简洁,它将一行分为两个分组:等号前的部分和等号后的部分,然后只保留等号前的部分。.*/匹配等号后面的所有内容,这样可以更灵活地处理不同格式的配置行。

  3. 提取并格式化配置项和IP地址
    # 提取配置项名称和IP地址并添加引号
    grep server.1 zoo.cfg | sed -r 's/(.*)=(.*):(.*):(.*)/\1"="\2/'
    # 输出:
    # server.1"="10.0.0.12
    

    说明:这个例子展示了如何在提取信息的同时添加额外的格式化字符。通过在替换模式中添加引号,我们可以将提取的内容格式化为带有引号的键值对形式。

  4. 提取并保留特定格式
    # 提取配置项名称和IP地址,保留等号格式
    grep server.1 zoo.cfg | sed -r 's/(.*)=(.*):(.*):(.*)/\1=\2/'
    # 输出:
    # server.1=10.0.0.12
    

    说明:这个例子提取了配置项名称和IP地址,并保留了原始的等号格式,丢弃了端口信息。这种方法在只需要部分配置信息时非常有用。

sed信息提取的关键特性

  1. 分组捕获与引用:sed支持使用括号进行分组捕获,并通过\1, \2等引用捕获的内容
  2. 正则表达式灵活性:可以根据需要创建不同复杂度的正则表达式来匹配和提取信息
  3. 替换与格式化:不仅可以提取信息,还可以在提取过程中添加额外的格式化字符
  4. 管道组合:与grep等工具结合使用,可以构建强大的文本处理管道

实用技巧

  • 使用-r选项启用扩展正则表达式,使语法更简洁
  • 在复杂的配置文件中,先使用grep过滤出相关行,再用sed精确提取所需信息
  • 对于结构化的配置数据,可以使用多个分组来精确提取每个部分
  • 当格式不确定时,可以使用更通用的模式如(.*)=(.*)来适应不同的配置格式

单词边界与行首锚点的区别

在正则表达式中,单词边界和行首锚点是两种不同的位置匹配方式,它们有着本质的区别:

nginx.conf文件中的相关内容

server_name  localhost;
location / {

单词边界与行首锚点对比案例

  1. 使用单词边界
    # 使用\b单词边界匹配以"loca"开头的单词
    grep '\bloca' nginx.conf  # 匹配到"server_name  localhost;"和"location / {"
       
    # 使用\<单词边界匹配以"loca"开头的单词
    grep '\<loca' nginx.conf  # 匹配到"server_name  localhost;"和"location / {"
    

    说明\b\<都是单词边界锚点,用于匹配单词的开始位置。\bloca\<loca都会匹配以”loca”开头的单词,无论这个单词出现在行中的什么位置。在上面的示例中,它们同时匹配了”localhost”和”location”,因为这两个单词都以”loca”开头。

  2. 使用行首锚点
    # 匹配以"loca"开头的行
    grep '^loca' nginx.conf  # 未匹配到任何内容
    

    说明^是行首锚点,用于匹配行的开始位置。^loca只会匹配以”loca”开头的整行,而不会匹配行中间出现的”loca”字符串。在上面的示例中,”localhost”和”location”都不是在行首出现的,所以没有匹配结果。

单词边界与行首锚点的本质区别

  1. 匹配位置不同
    • 单词边界(\b\<)匹配的是单词的开始或结束位置,单词边界由字母、数字、下划线与其他字符的边界决定
    • 行首锚点(^)匹配的是整个行的开始位置,与单词无关
  2. 匹配范围不同
    • 单词边界可以在一行内的任何位置匹配单词的开始或结束
    • 行首锚点只能在行的最开始位置进行匹配
  3. 应用场景不同
    • 单词边界适合于查找特定单词,无论它在行中的什么位置
    • 行首锚点适合于查找特定格式的行或行首的特定内容

实用技巧

  • 单词边界匹配通常用于精确查找特定单词,避免匹配到包含该单词作为子串的其他单词
  • 在grep命令中,\b\</\>在基本正则表达式(BRE)中都可以使用表示单词边界
  • 行首锚点与单词边界结合使用,可以创建更精确的匹配模式,如^[[:space:]]*\bword匹配行首(可能有前导空白)开始的特定单词

单词尾部边界与完整单词匹配

在正则表达式中,单词边界不仅可以匹配单词的开始,还可以匹配单词的结束,并且可以组合使用实现完整单词的精确匹配:

nginx.conf文件中的相关内容

location / {
    root   html;
    index  index.html index.htm;

单词尾部边界与完整单词匹配案例

  1. 单词尾部边界匹配
    # 使用\>单词尾部边界匹配以"ion"结尾的单词
    grep 'ion\>' nginx.conf  # 匹配到"location / {"
       
    # 使用\b单词边界匹配以"ion"结尾的单词
    grep 'ion\b' nginx.conf  # 匹配到"location / {"
    

    说明\>是单词尾部边界锚点,用于匹配单词的结束位置。ion\>ion\b都会匹配以”ion”结尾的单词。在上面的示例中,它们匹配了”location”,因为该单词以”ion”结尾。

  2. 完整单词精确匹配
    # 使用\<和\>精确匹配完整的"index"单词
    grep '\<index\>' nginx.conf  # 匹配到"index  index.html index.htm;"
       
    # 使用\b精确匹配完整的"index"单词
    grep '\bindex\b' nginx.conf  # 匹配到"index  index.html index.htm;"
    

    说明\<word\>\bword\b都可以用于精确匹配完整的单词。在上面的示例中,它们匹配了”index”指令,但不会匹配”index.html”或”index.htm”中的”index”子串,因为这些不是独立的单词。

单词边界匹配的关键特性

  1. 单词尾部边界
    • \>专门用于匹配单词的结束位置
    • \b在单词结尾时也可以匹配单词的结束位置
    • 单词边界由字母、数字、下划线与其他字符的边界决定
  2. 完整单词匹配
    • 组合使用单词开始和结束边界可以精确匹配完整单词
    • \<word\>\bword\b效果相同,都能确保匹配的是完整单词而非子串
    • 这在配置文件中查找特定指令或关键字时特别有用
  3. 匹配优先级
    • 在匹配时,正则表达式引擎会优先寻找完整的单词匹配
    • 即使字符串中包含多个可能的匹配位置,也会选择最精确的匹配

实用技巧

  • 完整单词匹配在处理配置文件时非常重要,可以避免匹配到包含目标单词作为子串的其他内容
  • 在脚本中进行文本处理时,精确的单词匹配可以提高处理的准确性
  • 结合使用不同的单词边界符号,可以创建更复杂和精确的匹配模式

分组匹配的实际应用

分组匹配是正则表达式中的强大功能,它使用括号()将模式的一部分标记为一个组,便于进行更精确的匹配和后续引用。下面通过zoo.cfg配置文件展示分组匹配的实际应用:

zoo.cfg文件内容

tickTime=2000 
initLimit=10 
syncLimit=5 
dataDir=/data/server/zookeeper/data 
dataLogDir=/data/server/zookeeper/logs 
clientPort=2181 
server.1=10.0.0.12:2182:2183 
server.2=10.0.0.13:2182:2183 
server.3=10.0.0.14:2182:2183:observer 
4lw.commands.whitelist=stat, ruok, conf, isro 

分组匹配案例分析

  1. 使用分组匹配服务器配置
    # 使用分组匹配所有服务器配置行
    egrep '(server.[0-9])' zoo.cfg
    # 输出:
    # server.1=10.0.0.12:2182:2183 
    # server.2=10.0.0.13:2182:2183 
    # server.3=10.0.0.14:2182:2183:observer
    

    说明:这个正则表达式(server.[0-9])使用了分组()来捕获模式server.[0-9],匹配任何以”server.”开头,后面跟着一个数字的行。在egrep中,我们使用扩展正则表达式,括号不需要转义。

  2. 使用分组和或操作符
    # 尝试匹配包含initLimit或syncLimit的行(注意大小写)
    egrep '(init|sync)limit' zoo.cfg  # 无匹配结果
       
    # 使用正确的大小写匹配
    egrep '(init|sync)Limit' zoo.cfg
    # 输出:
    # initLimit=10 
    # syncLimit=5
    

    说明:这个例子展示了分组与或操作符|的结合使用。(init|sync)Limit匹配以”init”或”sync”开头,后跟”Limit”的字符串。同时也强调了正则表达式默认是大小写敏感的,”limit”和”Limit”被视为不同的模式。

分组匹配的关键特性

  1. 分组捕获:括号()内的模式被视为一个整体,可以被捕获并在后续引用
  2. 与或操作符结合:分组常与|操作符结合使用,用于匹配多个可能的模式
  3. 优先级控制:分组可以改变操作符的优先级,确保模式按预期工作
  4. 在扩展正则表达式中:在egrep(或grep -E)中,分组括号不需要转义

非捕获分组的工作原理

非捕获分组是一种特殊类型的分组,它使用(?:pattern)语法,与普通捕获组(pattern)的主要区别在于:非捕获组只进行匹配,不将匹配内容存储在内存中供后续引用。

非捕获分组的工作机制

  1. 基本语法(?:pattern) - 在左括号后加上?:前缀,创建一个非捕获组
  2. 匹配行为:与普通捕获组相同,将括号内的模式作为一个整体进行匹配
  3. 内存使用:不会创建反向引用编号,也不会将匹配内容存储在内存缓冲区
  4. 性能影响:由于不需要存储匹配结果,非捕获组通常比普通捕获组更高效

非捕获组与普通捕获组的对比

特性 非捕获组 (?:pattern) 普通捕获组 (pattern)
匹配功能 ✓ 能将模式作为整体匹配 ✓ 能将模式作为整体匹配
内容存储 ✗ 不存储匹配内容 ✓ 存储匹配内容供引用
反向引用 ✗ 不能通过\1等引用 ✓ 可以通过\1等引用
性能表现 ✓ 性能更好,内存占用更少 ✗ 性能略低,内存占用更多
兼容性 ✗ 某些工具不支持(如grep) ✓ 广泛支持

非捕获分组的实际应用场景

  1. 优化性能:当只需要分组功能而不需要引用匹配内容时,使用非捕获组可以提高性能
  2. 复杂模式组织:在复杂的正则表达式中,使用非捕获组来组织子模式,避免创建不必要的捕获
  3. 与量词结合:将非捕获组与量词结合,如(?:abc){3}表示”abc”重复3次

示例代码演示

# 使用普通捕获组匹配IP地址格式
# 这里创建了4个捕获组,每个数字段
# 注意:grep不支持非捕获组,但许多编程语言和工具如sed -r, awk等支持

# 在支持非捕获组的环境中(如sed -r)示例:
# 匹配IP地址但不捕获各个段
# sed -r 's/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/<IP地址>/g' file.txt

# 在JavaScript中使用非捕获组的示例:
# const pattern = /(?:https?:\/\/)?(?:www\.)?(\w+\.\w+)/;
# const result = url.match(pattern);
# console.log(result[1]); // 只捕获域名部分,忽略协议和www前缀

非捕获组的重要注意事项

  1. Linux环境兼容性:在Linux的grep/egrep命令中,非捕获组语法(?:...)不受支持,会导致错误
  2. 工具支持差异:非捕获组主要在扩展正则表达式中支持,但不同工具对其支持程度不同
  3. 适用场景选择
    • 当需要引用匹配内容时,必须使用普通捕获组
    • 当只需要分组功能且追求性能时,优先考虑非捕获组
  4. 内存优化:在处理大量数据时,使用非捕获组可以减少内存占用,特别是当正则表达式包含多个分组时

实用技巧

  • 在编写复杂的正则表达式时,先确定哪些分组需要被引用,将不需要引用的分组设为非捕获组
  • 在性能敏感的应用中,优先使用非捕获组来优化正则表达式的执行效率
  • 在Linux环境中使用grep/egrep时,避免使用非捕获组,改用普通捕获组或重构正则表达式

实用技巧

  • 在处理配置文件时,分组匹配特别适合提取结构化数据
  • 结合分组和或操作符可以创建更灵活的匹配模式
  • 注意正则表达式的大小写敏感性,可以使用-i选项忽略大小写
  • 对于复杂的配置文件,分组匹配可以帮助快速定位和提取相关配置项

正则表达式限定符的实践应用

正则表达式中的量词(限定符)用于指定前面的字符或表达式应该匹配多少次。下面通过实际案例展示不同量词的使用效果和差异:

测试文件内容(file.txt)

 ac 
 abbcd 
 abbbce 
 abbbbbc 
 abcf 

量词实践案例分析

  1. 星号(*)量词:匹配前面的字符0次或多次
    # 尝试匹配以ab开头,c结尾,中间有任意数量的b且后面跟着s的行
    egrep "^ab*cs$" file.txt  # 无匹配结果
       
    # 匹配以ab开头,c结尾,中间有任意数量的b(包括0个)的行
    egrep "^ab*c$" file.txt
    # 输出:
    # ac 
    # abbbbbc 
    

    说明*量词匹配前面的字符0次或多次,所以ab*c可以匹配”ac”(b出现0次)和”abbbbbc”(b出现多次)。注意最后一个匹配行末尾的空格也被包含在内。

  2. 问号(?)量词:匹配前面的字符0次或1次
    # 匹配以ab开头,c结尾,中间有0个或1个b的行
    egrep '^ab?c$' file.txt
    # 输出:
    # ac 
    

    说明?量词匹配前面的字符0次或1次,所以ab?c只能匹配”ac”(b出现0次),而不能匹配包含多个b的行。

  3. 加号(+)量词:匹配前面的字符1次或多次
    # 匹配以ab开头(至少1个b)的行
    egrep "^ab+" file.txt
    # 输出:
    # abbcd 
    # abbbce 
    # abbbbbc 
    # abcf 
    

    说明+量词匹配前面的字符1次或多次,所以ab+可以匹配所有包含至少一个b的以ab开头的行。

  4. 花括号({n,m})量词:指定匹配的精确次数范围
    # 匹配以ab开头,且包含2到4个b的行
    egrep "^ab{2,4}" file.txt
    # 输出:
    # abbcd 
    # abbbce 
    # abbbbbc 
    

    说明{2,4}指定前面的字符应该匹配2到4次,但由于后面的字符串可能包含更多的b,所以abbbbbc也被匹配到了。这是因为正则表达式默认是贪婪匹配,并且我们只限定了开头部分。

  5. 精确匹配n次
    # 尝试匹配以ab开头,且恰好包含3个b的行
    egrep "^ab{3}" file.txt
    # 输出:
    # abbbce 
    # abbbbbc 
    

    说明{3}指定前面的字符应该恰好匹配3次,但由于没有限制后面的字符,所以包含3个或更多b的行都被匹配到了。

    # 使用相同模式的另一种写法
    egrep "^ab{3,3}" file.txt
    # 输出:
    # abbbce 
    # abbbbbc 
    

    说明{3,3}{3}效果相同,都指定恰好匹配3次,但同样没有限制后面的字符。

  6. 匹配至少n次
    # 匹配以ab开头,且至少包含2个b的行
    egrep "^ab{2,}" file.txt
    # 输出:
    # abbcd 
    # abbbce 
    # abbbbbc 
    

    说明{2,}指定前面的字符应该至少匹配2次。

如何精确匹配只有3个b的字符串

要精确匹配恰好包含3个b的字符串,我们需要使用更精确的模式来限制字符串的开始和结束,以及b的数量。以下是几种可能的解决方案:

  1. 精确匹配整行
    # 匹配以a开头,然后恰好3个b,然后是c结尾的行
    egrep "^ab{3}c$" file.txt  # 无匹配结果
    

    说明:这个模式要求严格的格式,但在我们的测试文件中没有完全符合这个格式的行。

  2. 匹配特定位置的3个b
    # 匹配包含3个连续b的行,但排除包含4个或更多连续b的行
    egrep "ab{3}c" file.txt | grep -v "ab{4}"
    

    说明:这种方法结合了两个正则表达式,先找出包含至少3个b的行,然后过滤掉包含4个或更多b的行。

  3. 使用单词边界
    # 如果我们要匹配的是单词中的3个b
    egrep "\b[ab]{3}\b" file.txt
    
  4. 使用更精确的位置匹配: 对于我们的测试文件,要找到恰好包含3个b的字符串,我们可以使用以下方法:
    # 匹配以a开头,后跟恰好3个b,然后是其他字符的行
    egrep "^a(?!b*b{4})b*c" file.txt
    

    说明:这个模式使用了否定前瞻断言(?!b*b{4}),确保不会有4个或更多的b。

量词使用的关键要点

  1. 贪婪匹配:默认情况下,量词会尽可能多地匹配字符
  2. 精确限制:使用{n}可以精确指定匹配次数
  3. 范围限制:使用{n,m}可以指定匹配次数的范围
  4. 起始/结束锚点:结合^$可以精确控制整个字符串的匹配
  5. 排除匹配:结合grep -v可以排除某些模式

实用技巧

  • 在使用量词时,要考虑是否需要精确匹配整个字符串或仅匹配部分内容
  • 对于复杂的匹配需求,可以结合多个正则表达式或使用高级特性如前瞻断言
  • 对于精确数量的匹配,确保使用适当的锚点来限制匹配范围
  • 在实际应用中,可以先使用宽松的匹配找到候选行,再逐步细化模式以获得精确结果

基本用法

# 基本搜索(使用BRE)
grep "pattern" file.txt

# 使用扩展正则表达式
grep -E "pattern" file.txt
# 或
egrep "pattern" file.txt

# 忽略大小写
grep -i "pattern" file.txt

# 显示行号
grep -n "pattern" file.txt

# 显示不匹配的行
grep -v "pattern" file.txt

# 递归搜索目录
grep -r "pattern" directory/

# 只显示匹配的部分
grep -o "pattern" file.txt

示例

# 搜索包含数字的行
grep "[0-9]" file.txt

# 搜索以abc开头的行
grep "^abc" file.txt

# 搜索以xyz结尾的行
grep "xyz$" file.txt

# 使用扩展正则表达式搜索邮箱格式
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" file.txt

4.2 sed - 流编辑器

sed是一种流编辑器,主要用于对文本进行替换操作,支持正则表达式。

基本用法

# 替换文本
sed 's/pattern/replacement/' file.txt

# 替换所有匹配项
sed 's/pattern/replacement/g' file.txt

# 替换并保存修改
sed -i 's/pattern/replacement/g' file.txt

# 只替换第n个匹配项
sed 's/pattern/replacement/n' file.txt

# 使用扩展正则表达式
sed -E 's/pattern/replacement/g' file.txt

# 删除包含特定模式的行
sed '/pattern/d' file.txt

# 在匹配行前添加内容
sed '/pattern/i\text to add' file.txt

# 在匹配行后添加内容
sed '/pattern/a\text to add' file.txt

示例

# 将所有的"error"替换为"ERROR"
sed 's/error/ERROR/g' log.txt

# 删除所有空行
sed '/^$/d' file.txt

# 将IP地址中的点替换为中划线
sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/\1-\2-\3-\4/g' file.txt

# 在每行开头添加行号
sed = file.txt | sed 'N;s/\n/:/' file.txt

4.3 awk - 文本处理工具

awk是一种强大的文本处理工具,支持复杂的模式匹配和数据处理。

基本用法

# 打印包含模式的行
awk '/pattern/' file.txt

# 根据条件处理\ nawk '/pattern/ { action }' file.txt

# 使用扩展正则表达式\ nawk --re-interval '/pattern/ { action }' file.txt

# 匹配并处理多个模式\ nawk '/pattern1/ { action1 } /pattern2/ { action2 }' file.txt

示例

# 打印包含数字的行的第一个字段\ nawk '/[0-9]/ { print $1 }' file.txt

# 统计包含特定模式的行数\ nawk '/error/ { count++ } END { print count }' log.txt

# 使用正则表达式作为字段分隔符\ nawk -F '[ :]+' '{ print $1, $3 }' file.txt

# 提取IP地址\ nawk -E '/([0-9]{1,3}\.){3}[0-9]{1,3}/ { print $0 }' log.txt

4.4 其他支持正则表达式的工具

  • find:在文件系统中搜索文件
  • locate:快速查找文件
  • vim/vi:文本编辑器中的搜索和替换
  • less/more:分页查看文件时的搜索
  • ssh:远程连接时的主机匹配
  • bash:shell中的变量匹配和条件判断

5. 正则表达式实战应用

5.1 日志分析

场景:分析Web服务器日志,提取访问IP和请求URL。

示例

# 从Nginx日志中提取IP和URL
cat /var/log/nginx/access.log | grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}.*"[A-Z]+ [^ ]+' | sed -E 's/"[A-Z]+ / /'

# 统计访问次数最多的前10个IP地址
grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -10

# 查找返回404错误的请求
grep '404' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -nr

5.2 配置文件管理

场景:修改配置文件中的特定设置。

示例

# 修改SSH配置,禁用密码登录
sudo sed -i 's/^#*PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

# 修改最大文件打开数限制
sudo sed -i 's/^\* soft nofile [0-9]*/\* soft nofile 65536/' /etc/security/limits.conf
sudo sed -i 's/^\* hard nofile [0-9]*/\* hard nofile 65536/' /etc/security/limits.conf

# 修改PHP配置中的内存限制
sudo sed -i 's/memory_limit = [0-9]*M/memory_limit = 256M/' /etc/php/7.4/fpm/php.ini

5.3 数据验证

场景:验证用户输入的数据格式。

示例

# 验证IP地址格式
validate_ip() {
  if [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
    echo "Valid IP"
  else
    echo "Invalid IP"
  fi
}

# 验证邮箱地址格式
validate_email() {
  if [[ $1 =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "Valid email"
  else
    echo "Invalid email"
  fi
}

# 验证手机号码格式(中国大陆)
validate_phone() {
  if [[ $1 =~ ^1[3-9][0-9]{9}$ ]]; then
    echo "Valid phone"
  else
    echo "Invalid phone"
  fi
}

5.4 文本转换与格式化

场景:对文本进行批量转换和格式化。

示例

# 将驼峰命名法转换为下划线命名法
echo "userName" | sed -E 's/([a-z0-9])([A-Z])/\1_\2/g' | tr '[:upper:]' '[:lower:]'

# 格式化日期(YYYY-MM-DD转换为DD/MM/YYYY)
echo "2024-05-15" | sed -E 's/(....)-(..)-(..)/\3\/\2\/\1/'

# 提取JSON中的特定字段(简单示例)
echo '{"name":"John","age":30}' | sed -E 's/.*"name":"([^"]+)".*/\1/'

# 替换多个空格为单个空格
sed 's/[[:space:]]\+/ /g' file.txt

5.5 系统管理任务

场景:使用正则表达式执行系统管理任务。

示例

# 查找特定时间段内修改的文件
find /var/log -type f -name "*.log" -mtime -7 | xargs ls -la

# 查找大于100MB的文件
find /home -type f -size +100M -exec ls -lh {} \;

# 提取系统进程中的内存使用情况
ps aux | grep -E '^[a-zA-Z0-9_-]+[[:space:]]+[0-9]+[[:space:]]+[0-9]+\.[0-9]+%' | sort -k3 -nr | head -10

# 清理旧的日志文件
find /var/log -name "*.log.[0-9]" -o -name "*.log.[0-9].gz" -mtime +30 -delete

5.6 正则表达式在用户注册系统中的实战应用

下面我们通过一个实际的登录注册管理脚本案例,来展示正则表达式在数据验证中的重要应用。

5.6.1 登录注册脚本分析与修复

原始脚本存在的问题

  1. 正则表达式错误:使用了[0-Z]这样的范围,这在ASCII表中包含了很多特殊字符,不是正确的字母数字范围表示
  2. 字符串边界表示错误:手机号正则使用了<>而不是标准的\b字符边界
  3. 变量引用缺少引号:在条件判断中没有给变量加上引号,可能导致特殊字符处理错误
  4. 邮箱输入和验证逻辑错误:邮箱输入和验证在同一行,可能导致解析错误
  5. 函数定义格式不规范:部分函数定义使用了非标准的缩进和格式

5.6.2 关键正则表达式解析

  • 用户名正则:使用^[a-zA-Z0-9_]{3,16}$确保用户名由字母、数字或下划线组成,长度为3-16个字符
  • 密码正则:使用^[a-zA-Z0-9@#$%^&*()_+]{8,20}$确保密码包含字母、数字和特殊字符,长度为8-20个字符
  • 手机号正则:使用^1[3-9]\d{9}$验证中国大陆手机号格式
  • 邮箱正则:使用^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$验证标准邮箱格式

6. 正则表达式高级技巧

6.1 零宽断言

零宽断言用于匹配位置而不是字符,在某些高级正则表达式引擎中支持:

  • 正向先行断言(?=pattern) - 匹配后面跟着pattern的位置
  • 负向先行断言(?!pattern) - 匹配后面不跟着pattern的位置
  • 正向后行断言(?<=pattern) - 匹配前面是pattern的位置
  • 负向后行断言(?<!pattern) - 匹配前面不是pattern的位置

示例

# 匹配后面跟着"@example.com"的用户名(在支持的工具中)
grep -P '\w+(?=@example\.com)' emails.txt

# 匹配前面不是"admin"的用户(在支持的工具中)
grep -P '(?<!admin)user' file.txt

6.2 回溯引用

回溯引用用于重复前面匹配的内容:

示例

# 匹配重复的单词(如"the the")
grep -E '(\b\w+\b)\s+\1' text.txt

# 替换重复的单词,只保留一个
sed -E 's/(\b\w+\b)\s+\1/\1/g' text.txt

# 匹配HTML标签对
grep -E '<([a-z]+)[^>]*>.*</\1>' html.txt

6.3 性能优化

正则表达式的效率对于处理大量数据至关重要:

  1. 避免过度使用通配符.*可能会导致回溯过多
  2. 使用锚点^$可以限制匹配范围
  3. 使用非捕获组(?:pattern)(pattern)更高效
  4. 避免嵌套量词:如(a+)+会导致严重的性能问题
  5. 使用具体的字符类:如[a-z].更精确
  6. 优先匹配常见情况:将常见的匹配模式放在前面

示例

# 低效的正则表达式
grep '.*error.*' log.txt

# 更高效的正则表达式
grep 'error' log.txt

# 低效的嵌套量词
grep -E '(a+)+b' file.txt

# 避免嵌套量词
grep -E 'a+b' file.txt

6.4 正则表达式调试技巧

调试复杂的正则表达式可能很困难,以下是一些有用的技巧:

  1. 逐步构建:从简单的模式开始,逐步添加复杂度
  2. 分段测试:单独测试正则表达式的各个部分
  3. 使用在线工具:如regex101、regexr等网站可以可视化调试正则表达式
  4. 添加注释:在复杂的正则表达式中添加注释(使用#x标志)
  5. 使用grep -o:只显示匹配的部分,便于调试

示例

# 分段测试IP地址正则表达式
# 测试第一部分
grep -o '[0-9]\{1,3\}' file.txt
# 测试完整的IP地址
grep -o '\([0-9]\{1,3\}\.[0-9]\{1,3\}\)\{2\}\.[0-9]\{1,3\}' file.txt

# 使用grep -o调试复杂模式
grep -o '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]\{2,\}\b' file.txt

7. 常用正则表达式模式库

7.1 常用数据格式验证

电子邮箱

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

IP地址(IPv4)

([0-9]{1,3}\.){3}[0-9]{1,3}

IP地址(精确匹配,考虑0-255范围)

((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)

IPv4地址匹配正则表达式深度解析

以下是一个更详细的IPv4地址匹配正则表达式及其执行过程分析:

egrep '(^([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]{1,2}|1[1-9]{2}|25[0-5][0-9]|25[0-4])$' testip.txt

测试文件内容(testip.txt)

 112.456.44.55 
 256.18.56.1 
 10.0.0.12 

正则表达式详细解析

这个正则表达式用于精确匹配有效的IPv4地址(0-255范围内的每个数字段),让我们分解每一部分:

  1. ^ - 行首锚点,确保匹配从行的开始处进行

  2. 第一数字段匹配 - ([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])
    • [1-9] - 匹配1-9的单个数字(排除以0开头但不是单个0的情况)
    • | - 或操作符
    • 1[0-9] - 匹配10-19的数字
    • 1[1-9]{2} - 匹配110-199的数字
    • 2[0-4][0-9] - 匹配200-249的数字
    • 25[0-5] - 匹配250-255的数字
  3. . - 匹配点号分隔符(已转义为\.

  4. 第二和第三数字段匹配 - (([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}
    • 这部分重复两次,匹配第二和第三个数字段
    • [0-9]{1,2} - 匹配0-99的数字
    • 其余部分与第一数字段类似,但允许以0开头
  5. 第四数字段匹配 - ([0-9]{1,2}|1[1-9]{2}|25[0-5][0-9]|25[0-4])
    • 匹配最后一个数字段
    • [0-9]{1,2} - 匹配0-99的数字
    • 1[1-9]{2} - 匹配110-199的数字
    • 25[0-5][0-9] - 匹配2500-2559的数字(这看起来是一个错误,因为IPv4地址每个段最多是255)
    • 25[0-4] - 匹配250-254的数字
  6. $ - 行尾锚点,确保匹配到行的结束处

执行过程分析

  1. 对于”112.456.44.55”
    • 第一个数字段”112”成功匹配 1[1-9]{2}
    • 但第二个数字段”456”超过了255的限制,无法匹配任何模式,所以整行不匹配
  2. 对于”256.18.56.1”
    • 第一个数字段”256”超过了255的限制(最大只能到255),无法匹配 25[0-5],所以整行不匹配
  3. 对于”10.0.0.12”
    • 第一个数字段”10”成功匹配 1[0-9]
    • 第二个数字段”0”成功匹配 [0-9]{1,2}
    • 第三个数字段”0”成功匹配 [0-9]{1,2}
    • 第四个数字段”12”成功匹配 [0-9]{1,2}
    • 所有部分都成功匹配,所以这行被返回

注意事项

  • 正则表达式中有一个潜在的问题:25[0-5][0-9] 会匹配2500-2559的数字,这超出了IPv4地址单个段的合法范围(0-255)
  • 修正后的正确模式应该是:25[0-5] 来匹配250-255的数字
  • 更精确的IPv4地址验证正则表达式应该是:
    ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
    

Linux环境兼容的优化版IPv4地址匹配

在Linux的grep/egrep命令中,非捕获组语法(?:...)可能不受支持,会导致”不匹配的 [、[^、[:、[.、或 [=”错误。以下是修复后的兼容版本:

# 修复版:移除非捕获组语法,使用普通捕获组
# 在testip.txt文件中匹配有效的IPv4地址
egrep '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' testip.txt

# 或者使用grep -E(等同于egrep)确保扩展正则表达式支持
grep -E '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' testip.txt

修复说明

  • 移除了非捕获组语法(?:...),替换为普通的捕获组(...)
  • 确保正则表达式中所有的特殊字符都正确转义
  • 使用grep -E明确启用扩展正则表达式支持,提高跨平台兼容性

实际应用建议

  • 在实际使用中,建议使用更标准和经过验证的IPv4地址匹配正则表达式
  • 对于严格的IP地址验证,除了正则表达式外,还可以考虑使用专门的网络工具或编程语言库来确保准确性
  • 在Linux环境中使用grep/egrep时,避免使用非捕获组等高级正则表达式特性,以确保兼容性

URL

https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)

网址检测正则表达式详细解析

以下是一个实际使用的网址检测正则表达式及其执行过程分析:

egrep '((https?|ftp):\/\/)?(www\.)?([0-z]+\.)([a-Z]{2,5})$' testsite.txt

测试文件内容(testsite.txt)

`http://www.baidu.com` 
`https://www.baidu.com` 
www.126.com
163.com
http.example.comcom

执行结果

`http://www.baidu.com` 
`https://www.baidu.com` 
www.126.com
163.com

正则表达式详细解析

  1. ((https?|ftp):\/\/)? - 可选的协议部分
    • (https?|ftp) - 匹配”http”、”https”或”ftp”协议
    • :\/\/ - 匹配协议后的冒号和两个斜杠
    • ? - 表示整个协议部分是可选的
  2. (www\.)? - 可选的”www.”前缀
    • www\. - 匹配”www.”字符串
    • ? - 表示这个前缀是可选的
  3. ([0-z]+\.) - 匹配域名的中间部分
    • [0-z]+ - 匹配一个或多个字母、数字字符
    • \. - 匹配域名中的点
  4. ([a-Z]{2,5})$ - 匹配域名的顶级部分
    • [a-Z]{2,5} - 匹配2到5个字母(不区分大小写)
    • $ - 行尾锚点,确保匹配到行的末尾

执行过程分析

  1. 对于http://www.baidu.com
    • 协议部分http://成功匹配
    • “www.”前缀成功匹配
    • 中间部分baidu.成功匹配
    • 顶级域名com成功匹配(3个字母,在2-5范围内)
    • 所有部分都成功匹配,所以这行被返回
  2. 对于https://www.baidu.com
    • 协议部分https://成功匹配
    • 后续部分匹配过程与第一个例子相同
    • 所有部分都成功匹配,所以这行被返回
  3. 对于www.126.com
    • 没有协议部分,但协议部分是可选的,所以继续匹配
    • “www.”前缀成功匹配
    • 中间部分126.成功匹配
    • 顶级域名com成功匹配
    • 所有部分都成功匹配,所以这行被返回
  4. 对于163.com
    • 没有协议部分和”www.”前缀,但它们都是可选的
    • 中间部分163.成功匹配
    • 顶级域名com成功匹配
    • 所有部分都成功匹配,所以这行被返回
  5. 对于http.example.comcom
    • 顶级域名部分comcom有6个字母,超出了{2,5}的范围
    • 整体匹配失败,所以这行不被返回

注意事项

  • 正则表达式中的[0-z]可能会匹配到一些非预期的字符,因为在ASCII编码中,数字0和小写字母z之间包含了一些特殊字符
  • 更精确的域名匹配应该使用[a-zA-Z0-9]来确保只匹配字母和数字
  • 顶级域名长度限制为2-5个字符,这对于大多数常见的顶级域名足够,但可能会排除一些新的或特殊的顶级域名

日期(YYYY-MM-DD)

\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])

时间(HH:MM:SS)

([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]

7.2 编程相关模式

变量名(大多数语言)

[a-zA-Z_][a-zA-Z0-9_]*

函数定义(简单C风格)

\w+\s+\w+\s*\(.*\)\s*\{

注释(C/C++/Java)

//.*|/\*.*?\*/

字符串字面量

".*?"|'.*?'

7.3 系统管理相关模式

Linux用户名

^[a-z_][a-z0-9_-]{0,31}$

文件路径

^(/[^/ ]*)+/?$

MAC地址

([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})

UUID

[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}

8. 正则表达式实战案例

8.1 案例一:分析Nginx访问日志

目标:从Nginx访问日志中提取访问量最高的IP地址和URL。

日志格式

192.168.1.1 - - [15/May/2024:10:30:45 +0800] "GET /index.html HTTP/1.1" 200 1234 "https://example.com" "Mozilla/5.0..."

实现脚本

#!/bin/bash

LOG_FILE="/var/log/nginx/access.log"

# 统计访问量最高的前10个IP地址
echo "=== 访问量最高的IP地址 ==="
grep -E -o '^([0-9]{1,3}\.){3}[0-9]{1,3}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10

# 统计访问量最高的前10个URL
echo -e "\n=== 访问量最高的URL ==="
grep -E -o '"[A-Z]+ ([^ "]+)' "$LOG_FILE" | sed 's/"[A-Z]\+ //' | sort | uniq -c | sort -nr | head -10

# 统计404错误最多的URL
echo -e "\n=== 404错误最多的URL ==="
grep ' 404 ' "$LOG_FILE" | grep -E -o '"[A-Z]+ ([^ "]+)' | sed 's/"[A-Z]\+ //' | sort | uniq -c | sort -nr | head -10

# 统计每个小时的访问量
echo -e "\n=== 每小时访问量统计 ==="
grep -E -o '\[(.*?)\]' "$LOG_FILE" | sed 's/\[//; s/:[0-9][0-9]:[0-9][0-9].*//' | sort | uniq -c | sort -k2

8.2 案例二:批量重命名文件

目标:将目录中的文件名从”YYYYMMDD_filename.txt”格式批量重命名为”YYYY-MM-DD_filename.txt”格式。

实现脚本

#!/bin/bash

# 批量重命名文件,将YYYYMMDD格式转换为YYYY-MM-DD格式
for file in 20??[0-1][0-9][0-3][0-9]_*.txt; do
  # 检查文件是否存在
  if [ -f "$file" ]; then
    # 使用正则表达式提取日期部分
    year=${file:0:4}
    month=${file:4:2}
    day=${file:6:2}
    name=${file:8}
    
    # 构建新文件名
    new_name="${year}-${month}-${day}${name}"
    
    # 重命名文件
    echo "重命名: $file -> $new_name"
    mv "$file" "$new_name"
  fi
done

echo "批量重命名完成!"

8.3 案例三:配置文件差异比较

目标:比较两个配置文件,找出除了注释和空行外的实际差异。

实现脚本

#!/bin/bash

# 比较两个配置文件,忽略注释和空行

if [ $# -ne 2 ]; then
  echo "用法: $0 文件1 文件2"
  exit 1
fi

FILE1=$1
FILE2=$2

# 创建临时文件存储过滤后的内容

### 8.4 案例四:目标地址检测工具 (target_check.sh)

**目标**:创建一个交互式工具,能够验证IP地址和网站地址的格式,并检测其连通性状态。

**实现脚本**```bash
#!/bin/bash 
# ************************************** 
# *  shell功能脚本模板 
# *  作者:钟翼翔 
# *  联系:clockwingsoar@outlook.com 
# *  版本:2025-11-11 
# ************************************** 

# 定制目标类型变量 
target_type=(主机 网站) 

# 定制检测ip地址格式的函数 
check_ip() { 
    # 接收函数参数 
    IP=$1 
    ip_regex='(^([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]{1,2}|1[1-9]{2}|25[0-4])$' 
    # 判断ip地址是否有效 
    echo $IP | egrep "${ip_regex}" >/dev/null && echo "true" || echo "false" 
} 

# 定制网址的格式检测函数 
check_url() { 
    # 接收函数参数 
    site=$1 
    site_regex='((http|https|ftp):\/\/)?(www\.)?([a-zA-Z0-9]+\.)([a-zA-Z]{2,5})$' 
    # 判断网址地址是否有效 
    echo "$site" | egrep "${site_regex}" >/dev/null && echo "true" || echo "false" 
} 

# 定制服务的操作提示功能函数 
menu() { 
    echo -e "\e[31m---------------确定检测目标类型---------------" 
    echo -e " 1: 主机  2: 网站" 
    echo -e "-------------------------------------------\033[0m" 
} 

# 目标主机检测过程 
host_ip_check() { 
    read -p "> 请输入要检测的主机ip: " ip_addr 
    result=$(check_ip ${ip_addr}) 
    if [ "${result}" == "true" ]; then 
        ping -c1 -W1 ${ip_addr} &> /dev/null && echo "${ip_addr} 状态正常" || echo "${ip_addr} 状态不可达" 
    else 
        echo "目标ip格式异常" 
    fi 
} 

# 目标站点检测过程 
net_site_check() { 
    read -p "> 请输入要检测的网站地址: " site_addr 
    result=$(check_url "${site_addr}") 
    if [ "${result}" == "true" ]; then 
        curl -s -o /dev/null "${site_addr}" && echo "${site_addr} 状态正常" || echo "${site_addr} 状态异常" 
    else 
        echo "目标网址格式异常" 
    fi 
} 

# 定制帮助信息 
Usage() { 
    echo "请输入正确的检测目标类型" 
} 

# 定制业务逻辑 
while true 
do 
    menu 
    read -p "> 请输入要检测的目标类型: " target_id 
    if [ "${target_type[$target_id-1]}" == "主机" ]; then 
        host_ip_check 
    elif [ "${target_type[$target_id-1]}" == "网站" ]; then 
        net_site_check 
    else 
        Usage 
    fi 
done

执行结果分析

---------------确定检测目标类型--------------- 
 1: 主机  2: 网站 
------------------------------------------- 
> 请输入要检测的目标类型: 1 
> 请输入要检测的主机ip: 10.0.0.13 
10.0.0.13 状态正常 
---------------确定检测目标类型--------------- 
 1: 主机  2: 网站 
------------------------------------------- 
> 请输入要检测的目标类型: 1 
> 请输入要检测的主机ip: 10.0.0.14 
10.0.0.14 状态不可达 
---------------确定检测目标类型--------------- 
 1: 主机  2: 网站 
------------------------------------------- 
> 请输入要检测的目标类型: 2 
> 请输入要检测的网站地址: www.baidu.com 
www.baidu.com 状态正常 
---------------确定检测目标类型--------------- 
 1: 主机  2: 网站 
------------------------------------------- 
> 请输入要检测的目标类型: 2 
> 请输入要检测的网站地址: www.clockwingsoar.cyou 
www.clockwingsoar.cyou 状态异常 
---------------确定检测目标类型--------------- 
 1: 主机  2: 网站 
------------------------------------------- 
> 请输入要检测的目标类型: www.bucunzai.comm 
target_check.sh:行69: www.bucunzai.comm-1:语法错误: 无效的算术运算符 (错误符号是 ".bucunzai.comm-1")

执行过程详解

  1. 场景1:检测可达的主机IP
    • 用户选择目标类型1(主机)
    • 输入IP地址10.0.0.13
    • check_ip函数通过正则表达式验证IP格式正确
    • ping命令检测到IP可达,输出”状态正常”
  2. 场景2:检测不可达的主机IP
    • 用户选择目标类型1(主机)
    • 输入IP地址10.0.0.14
    • check_ip函数验证IP格式正确
    • ping命令无法连接到IP,输出”状态不可达”
  3. 场景3:检测正常的网站
    • 用户选择目标类型2(网站)
    • 输入网站地址www.baidu.com
    • check_url函数通过正则表达式验证网址格式正确
    • curl命令成功访问网站,输出”状态正常”
  4. 场景4:检测异常的网站
    • 用户选择目标类型2(网站)
    • 输入网站地址www.clockwingsoar.cyou
    • check_url函数验证网址格式正确
    • curl命令无法正常访问网站,输出”状态异常”
  5. 场景5:输入错误的目标类型
    • 用户输入www.bucunzai.comm作为目标类型,而不是1或2
    • 系统尝试执行${target_type[$target_id-1]},其中$target_id是”www.bucunzai.comm”
    • 当计算”www.bucunzai.comm-1”时,Shell将其解析为算术表达式,但遇到无效字符,导致语法错误

正则表达式详细解析

  1. IP地址正则表达式(^([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]{1,2}|1[1-9]{2}|25[0-4])$
    • ^$:确保匹配整个字符串
    • 第一个八位组:([1-9]|1[0-9]|1[1-9]{2}|2[0-4][0-9]|25[0-5])
      • 匹配1-9、10-19、110-199、200-249或250-255的数字
    • 中间两个八位组:(([0-9]{1,2}|1[1-9]{2}|2[0-4][0-9]|25[0-5])\.){2}
      • 使用{2}重复匹配两次,每个八位组格式与第一个类似,后面跟一个点
    • 最后一个八位组:([0-9]{1,2}|1[1-9]{2}|25[0-4])
      • 注意这里的范围是25[0-4],只匹配到254而不是255,这是一个小问题
  2. 网站地址正则表达式((http|https|ftp):\/\/)?(www\.)?([a-zA-Z0-9]+\.)([a-zA-Z]{2,5})$
    • ((http|https|ftp):\/\/)?:可选的协议部分,匹配http://、https://或ftp://
    • (www\.)?:可选的www.前缀
    • ([a-zA-Z0-9]+\.):域名的主体部分,后跟一个点
    • ([a-zA-Z]{2,5})$:顶级域名,2-5个字母

脚本优化建议

  1. 修复IP地址正则表达式
    • 最后一个八位组应该是25[0-5]而不是25[0-4],以正确匹配0-255的范围
    • 优化后的正则:(^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){3}([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$
  2. 增强错误处理
    • 添加对非数字目标类型输入的检查,避免算术语法错误
    • 例如:if ! [[ "$target_id" =~ ^[1-2]$ ]]; then Usage; continue; fi
  3. 改进网址正则表达式
    • 当前正则表达式不支持更复杂的URL格式(如包含路径、查询参数等)
    • 更全面的正则:((https?|ftp):\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)
  4. 代码健壮性
    • 所有变量引用都应该用双引号括起来,避免空白字符和特殊字符导致的问题
    • 例如:result=$(check_ip "${ip_addr}")而不是result=$(check_ip ${ip_addr})
  5. 添加退出机制
    • 当前脚本是一个无限循环,用户无法正常退出
    • 可以添加退出选项,如输入0或q退出脚本

这个案例展示了正则表达式在实际应用中的重要作用,尤其是在数据验证方面。同时也提醒我们在编写Shell脚本时需要注意输入验证和错误处理,以提高脚本的健壮性。

# 过滤掉注释行和空行
sed -E '/^\s*#/d; /^\s*$/d' "$FILE1" | sort > "$TEMP1"
sed -E '/^\s*#/d; /^\s*$/d' "$FILE2" | sort > "$TEMP2"

# 比较过滤后的文件
diff -u "$TEMP1" "$TEMP2"

# 清理临时文件
rm -f "$TEMP1" "$TEMP2"

8.4 案例四:提取HTML页面中的链接

目标:从HTML文件中提取所有的链接。

实现脚本

#!/bin/bash

if [ $# -ne 1 ]; then
  echo "用法: $0 HTML文件"
  exit 1
fi

HTML_FILE=$1

# 使用多种工具提取链接
echo "=== 提取的链接 ==="
# 方法1: 使用grep和sed
grep -E -o '<a[^>]+href="[^>^"]+"[^>]*>' "$HTML_FILE" | sed -E 's/.*href="([^"]+)".*/\1/' | sort | uniq

# 方法2: 如果安装了lynx,可以使用lynx提取
if command -v lynx > /dev/null; then
  echo -e "\n=== 使用lynx提取的链接 ==="
  lynx -dump -listonly "$HTML_FILE" | grep '^  *[0-9]\+' | awk '{print $2}'
fi

9. 学习资源与工具推荐

9.1 在线学习资源

9.2 在线调试工具

  • Regex101 - 功能强大的正则表达式测试和调试工具
  • Regexr - 在线正则表达式可视化工具
  • RegEx Tester - 简单易用的正则表达式测试工具
  • Debuggex - 正则表达式可视化调试工具

9.3 Linux命令行工具加强版

  • ripgrep (rg) - grep的替代品,更快更强大
  • fd - find的替代品,更智能的文件搜索工具
  • sd - sed的替代品,更易用的字符串替换工具
  • xsv - CSV文件处理工具,支持正则表达式

10. 总结

正则表达式是Linux系统管理员和开发者的强大工具,掌握它可以大大提高文本处理和系统管理的效率。本文从基础概念到高级应用,全面介绍了Linux环境下正则表达式的使用方法和实战技巧。

通过本文中的实战案例,我们展示了正则表达式在不同场景下的应用:从Nginx日志分析、文件批量重命名,到配置文件差异比较、目标地址检测工具,再到HTML链接提取,正则表达式都发挥了关键作用。特别是在数据验证方面,如target_check.sh脚本中对IP地址和网站地址的格式验证,充分展示了正则表达式的精确匹配能力。

这些案例也提醒我们,在编写正则表达式时需要注意以下几点:

  1. 确保正则表达式的准确性,避免边界条件处理不当
  2. 考虑性能优化,避免使用过于复杂或低效的模式
  3. 加强错误处理和输入验证,提高脚本的健壮性
  4. 根据不同的应用场景选择合适的正则表达式语法(BRE或ERE)
  5. 对于复杂的匹配需求,合理使用分组和引用功能

正则表达式的学习是一个持续的过程,需要不断的实践和积累。建议读者从简单的模式开始,逐步尝试更复杂的应用场景,通过实际操作来加深理解。同时,要注意正则表达式的性能优化,避免编写低效的模式。

最后,希望本文的内容对您有所帮助,让您在Linux系统管理和文本处理的道路上更进一步!记住,正则表达式的力量在于它的灵活性和表达能力,掌握了它,您将拥有处理各种复杂文本任务的强大能力。

文档信息

Search

    Table of Contents