文章目录
基本概念
正规式:也叫正则表达式,是表示正规集的数学工具,是说明单词的模式的一种表示法,常用于在一个文件或字符里进行格式匹配。正则工作时以行为单位,一次处理一行。
元字符:正则表达式由普通字符和元字符组成。其中元字符不代表他们本身的字面意思,而是用于表达控制或通配符的功能,如下表所示:
元字符 | 描述 |
---|---|
^ | 从开始行开始匹配. |
$ | 从末端开始匹配. |
. | 句号匹配除了换行符以外的任意单个字符。 |
[ ] | 字符集。匹配方括号内的任意字符。 |
* | 匹配>=0个重复的在*号之前的字符。 |
\ | 转义字符,用于匹配一些保留的字符 ,如`[ ] ( ) { } . * + ? ^ $ \ |
[^ ] | 否定字符集。匹配除了方括号里的任意字符 |
+ | 匹配>=1 个重复的 + 号前的字符。 |
? | 标记?之前的字符为可选. |
{n,m} | 匹配 num 个大括号之前的字符或字符集 (n <= num <= m). |
(xyz) | 特征标群,匹配与 xyz 完全相等的字符串. |
| | 或运算符,匹配符号前或后的字符. |
Linux 三剑客
Linux 下普通命令无法使用正则表达式,仅三剑客(grep、sed、awk)支持。
通配符是大部分普通命令都支持的,用于查找文件或目录,而正则表达式用于通过文本处理工具三剑客命令在文件中过滤内容。
在 Linux 中,正则表达式分为两种:
①.基本正则表达式:BRE,对应的元字符有 ^$.[]*
②.扩展的正则表达式:ERE,即是在 BRE 基础上,增加上 (){}?+|
等字符
(1) grep
grep 是一种文本过滤工具,用于根据用户指定的 " 模式 (过滤条件)" 对目标文本逐行进行匹配检查,打印匹配到的行
语法:grep [options] [pattern] file
扩展正则必须使用 grep -E
才能生效,如若不加,则需要在特殊字符前加反斜杠 “”,标识为正则
参数选项 | 含义 |
---|---|
-v | 排除匹配结果 |
-n | 显示匹配行与行号 |
-i | 不区分大小写 |
-c | 只统计匹配的行数 |
-E | 使用扩展的正则表达式 |
–color=auto | 为 grep 的结果添加颜色 |
-w | 只匹配过滤的单词 |
-o | 只输出匹配的内容 |
(2) sed
sed 是一种字符流编辑工具,适合编辑匹配到的文本,默认不会直接修改源文件数据,而是会将数据复制到缓冲区中,修改也仅限于缓冲区中的数据
语法:sed [选项] [sed 内置命令字符] [输入文件]
如果需要使用扩展正则需要加上选项 -E
参数选项 | 解释 |
---|---|
-n | 取消默认 sed 的输出,常与 sed 内置命令 p 一起使用从而只输出匹配行 |
-i | 直接将修改结果写入文件,若不加 -i,sed 修改的是内存缓冲区数据 |
-e | 多次编辑,不需要管道符了 |
-E | 支持正则扩展 |
sed 的内置命令字符 | 解释 |
---|---|
a | append,对文本追加,在指定行后面添加一行/多行文本 |
d | Delete,删除匹配行 |
i | insert,表示插入文本,在指定行前添加一行/多行文本 |
p | Print,打印匹配行的内容,通常 p 与 -n 一起用 |
s/正则/替代内容/g | 匹配正则内容,然后替换内容 (支持正则),结尾 g 代表全局匹配 |
(3) awk
awk 是格式化文本的利器,用于对文本进行较复杂的格式处理,默认以空格为分隔符,支持条件判断、数组、循环等功能,其默认使用 ERE
语法:awk [option] 'pattern [{action}]' file
内置变量 | 解释 |
---|---|
$n | 指定分隔符后,当前记录的第n个字段 |
$0 | 完整的输入记录 |
FS | 字段分隔符,默认就是空格 |
NF | 分割后,当前行一共有多少个字段(列数) |
NR | 当前记录数(行数) |
参数 | 解释 |
---|---|
-F | 指定分隔字段符 |
-v | 定义或修改一个 awk 内部的变量 |
-f | 从脚本文件中读取 awk 命令 |
(4) 命令行通配符
命令行通配符与正规式中存在些许差异:
元符号 | 作为通配符 | 作为正规式 |
---|---|---|
* | 匹配任意长度字符 | 匹配>=0 个重复的在 * 号之前的字符 |
? | 匹配任意单个字符 | 标记?之前的字符为可选 |
也就是说,作为 Linux 通配符来讲,*
等价于正规式中的 .*
,?
等价于正规式中的 .
不同语言对 RE 的支持
C/C++
标准的 C 和 C++ 都不支持正则表达式,但有正则表达式的函数库提供这功能
C 语言中使用正则表达式一般分为三步:编译、匹配、释放
// 正则表达式头文件
#include<regex.h>
// 其中包含结构体
typedef struct {
regoff_t rm_so; // 存放匹配文本串在目标串中的开始位置
regoff_t rm_eo; // 存放匹配文本串在目标串中的结束位置
} regmatch_t;
// 编译正则表达式:把指定的正则表达式pattern编译成一种特定的数据格式(regex_t *compiled)
int regcomp (regex_t *compiled, const char *pattern, int cflags);
// 匹配正则表达式
int regexec (regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr[], int eflags);
// 释放正则表达式
void regfree (regex_t *compiled);
// 当执行regcomp 或者regexec 产生错误时,可以调用以下函数返回一个包含错误信息的字符串。
size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length);
例如,匹配一个 Email 邮箱:
#include <stdio.h>
#include <sys/types.h>
#include <regex.h>
int main(int argc,char** argv)
{
if (argc != 2) {
printf("Usage: %s Text\n", argv[0]);
return 1;
}
char * buf = argv[1];
const char * pattern = "^\\w+@(\\w+\\.)+[a-zA-Z]{2,}$";
regmatch_t pmatch[1];
regex_t reg;
regcomp(®,pattern,REG_EXTENDED);
if(regexec(®,buf,1,pmatch,0) == REG_NOMATCH)
printf("No match\n");
else{
printf("匹配成功:");
for(int i = pmatch[0].rm_so;i<pmatch[0].rm_eo;++i)putchar(buf[i]);
printf("是一个合法的邮箱地址\n");
}
regfree(®);
return 0;
}
C++ 中也有类似的函数,不过更为简洁
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int main(int argc,char** argv)
{
if (argc != 2) {
printf("Usage: %s Text\n", argv[0]);
return 1;
}
string line = argv[1];
regex pattern(R"(^\w+@(\w+\.)+[a-zA-Z]{2,}$)");
smatch result;
if(regex_match(line,result,pattern)){
cout<<" 匹配成功:"<<result[0]<<" 是一个合法的邮箱地址\n";
}else{
cout<<"No match\n";
}
return 0;
}
Java
Java 的正则表达式是由 java.util.regex 的 Pattern 和 Matcher 类实现的。Pattern 对象表示经编译的正则表达式。静态的 compile( )方法负责将表示正则表达式的字符串编译成 Pattern 对象。常用方法如下表所示
方法名 | 描述 |
---|---|
boolean matches(String regex) | 当前字符串是否匹配给定的正则表达式 |
String replaceAll(String regex,String replacement) | 使用 replacement 替换当前字符串中所有符号正则表 达式的内容 |
String[] split(String regex) | 根据给定的正则表达式拆分当前字符串 |
// 判断输入的手机号是否为13或者18开头
import java.util.Scanner;
public class EXjava {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入手机号:");
String str = input.nextLine();
String regex = "1[38]\\d{9}";//用正则表达式定义手机号规则
boolean flag = str.matches(regex);
System.out.println("手机号是:"+flag);
input.close();
}
}
// 分割年龄段
import java.util.Scanner;
public class EXjava {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String age = "18-30";//定义年龄范围
String regex = "-";
String[] strArr = age.split(regex);//分割成字符串数组
int startage = Integer.parseInt(strArr[0]);
int endage = Integer.parseInt(strArr[1]);
System.out.print("请输入年龄:");
int a = input.nextInt();
if(a >= startage && a <= endage) {
System.out.println("Yes");
}
else {
System.out.println("No");
}
input.close();
}
}
Python
在使用正则表达式之前,需要导入 re 模块。
方法名 | 描述 |
---|---|
findall() | 找到re匹配的所有字符串,返回一个列表 |
search() | 扫描字符串,找到这个re匹配的位置(仅仅是第一个查到的) |
match() | 判断re是否在字符串刚开始的位置(匹配行首) |
sub() | 通过正则定位,替换字符串 |
#从初始位置开始匹配
import re
line = 'i can speak good english'
matchObj = re.match(r'(i)\s(\w*)\s(\w*).*',line)
if matchObj:
print('matchObj.group() :',matchObj.group())
print('matchObj.group() :',matchObj.group(1))
print('matchObj.group() :',matchObj.group(2))
print('matchObj.group() :',matchObj.group(3))
else:
print('no match!')
import re
line = 'i can speak good english'
matchObj = re.search('(.*) (.*?) (.*)',line)
if matchObj:
print('matchObj.group() :',matchObj.group())
print('matchObj.group() :',matchObj.group(1))
print('matchObj.group() :',matchObj.group(2))
print('matchObj.group() :',matchObj.group(3))
else:
print('no match!')
import re
line = 'i can speak good english'
speak = re.sub(r'can','not',line)
print(speak)
speak1 = re.sub(r'\s','',line) #替换所有空格
print(speak1)
提取图片地址
import re
s = """<img data-original="https://img02.sogoucdn.com/app/a/100520024/36189693dc8db6bd7c0be389f8aaddbd.jpg" src="https://img02.sogoucdn.com/app/a/100520024/36189693dc8db6bd7c0be389f8aaddbd.jpg" width="250" height="375" .jpg>"""
result1 = re.search(r"src=\"https.*.jpg\"",s)
print(result1.group())
result2 = re.search(r"src=\"(https.*.jpg)\"",s)
print(result2.group(1))
各种IDE对正规式的支持
vscode
捕获组合和替换模式
要创建带编号的捕获组,可以在正则表达式模式中用圆括号将子表达式括起来。 捕获按正则表达式中左括号的位置从左到右自动编号。 若要访问捕获的组,请考虑以下示例:
- 在正则表达式中:请使用
\number
。 例如,正则表达式(\w+)\s\1
中的\1
引用第一个捕获组(\w+)
。 - 在替换模式中:请使用
$number
。 例如,已分组的正则表达式(\d)([a-z])
定义了两个组:第一个组包含一个十进制数字,第二个组包含一个 a 到 z 之间的字符。 该表达式在以下字符串中查找四个匹配项:1a 2b 3c 4d。 替换字符串z$1
仅引用第一个组($1
),并将该字符串转换为 z1 z2 z3 z4。
也可以为捕获组命名,而不依赖于捕获组的自动编号。 命名的捕获组的语法为 (?<name>subexpression)
。
若要访问命名的捕获组,请考虑以下示例:
- 在正则表达式中:请使用
\k<name>
。 例如,正则表达式(?<repeated>\w+)\s\k<repeated>
中的\k<repeated>
引用名为repeated
且其子表达式为\w+
的捕获组。 - 在替换模式中:请使用
${name}
。 例如,${repeated}
。
word
word 中并不使用正则表达式,而是使用通配符来查找和替换文字,具体如下所示:
描述 | 类型 | 示例 |
---|---|---|
任一字符 | ? | s?t 可找到 “sat” 和 “set” |
任何字符串 | * | s*d 可找到 “sad” 和 “started” |
单词开头 | < | <(inter) 可找到 “interesting” 和 “intercept”,但找不到 “splintered” |
单词结尾 | > | (in)> 可找到 “in” 和 “within”,但找不到 “interesting” |
指定字符之一 | [ ] | w[io]n 可找到 “win” 和 “won” |
此范围内的任一字符 | [-] | [r-t]ight 可找到 “right” 和 “sight”,范围必须是升序 |
除了括号内范围中的字符之外的任一字符 | [!x-z] | t[!a-m]ck 可找到 “tock” 和 “tuck”,但找不到 “tack” 或 “tick” |
前一个字符或表达式的 n 个匹配项 | {n} | fe{2}d 可找到 “feed”,但找不到 “fed” |
前一个字符或表达式的至少 n 个匹配项 | {n,} | fe{1,}d 可找到 “fed” 和 “feed” |
前一个字符或表达式的 n 到 m 个匹配项 | {n,m} | 10{1,3} 可找到 “10”、“100” 和 “1000” |
前一个字符或表达式的一个或多个匹配项 | @ | lo@t 可找到 “lot” 和 “loot” |
可以看到,其中?
*
<
>
!
@
均与正则表达式有较大的不同,他们之间的关系如下:
*
等价于正规式中的 .*
,?
等价于正规式中的 .
,
<
等价于正规式中的 ^
,>
等价于正规式中的 $
,
[!]
等价于正规式中的 [^]
,@
等价于正规式中的 +
。
Nodepad++
nodepad++同样支持正规式查找与替换,大部分规则与前面相同
但notepad++的换行符不太一样,是\r\n
,值得注意。
同样,nodepad++支持捕获组合和替换模式
flex工具对正规式的支持
大多数 flex 程序都具有二义性,相同的输入可能被多种不同的模式匹配。 flex 通过两个简单的规则来解决它:
- 词法分析器匹配输入时匹配尽可能多的字符串。
- 如果两个模式都可以匹配的话,匹配在程序中更早出现的模式。
以下是一个字符统计程序:
%{
int chars = 0;
int words = 0;
int lines = 0;
%}
%%
[a-zA-Z]+ {words++;chars += strlen(yytext);}
\n {chars ++;lines++;}
. {chars++;}
%%
int main(int argc,char** argv){
yylex();
printf("lines:%8d, words:%8d, chars:%8d\n",lines,words,chars);
return 0;
}
Everything 工具对正规式的支持
Everything 是速度最快的文件名搜索软件,其支持通配符和正则表达式搜索
首先需要开启 everything 工具在(字符串)查找时,对正则表达式功能的支持:
【菜单栏】⇒ 【Search】⇒ 勾选【Enable Regex】
- ctrl + i:字符大小写敏感/不敏感
通配符搜索:
正则式搜索: