- 学习一下这篇简短的 交互式正则表达式教程.
感觉基本字符的匹配语法与python中的re正则匹配模块类似,这里放出一写基本语法:
( )用于创建分组,分组可用于后续提取其中内容
以下内容为上述网站的练习内容参考:
基础部分:
ex1: .{3,7}
ex1.1: .+
ex2: .*[^\w]$
ex3: [cmf]an$
ex4: [^b]og
ex5: [A-Z].*
ex6: .{6,}
ex7: .{2,}
ex8: .*[?]$
ex9: [\s]
ex10: ^Mission.*successful$
ex11: (^file_.*?)[.]
ex12: (\w*[\s](\d*))
ex13: (\d+)[x]{1}(\d+)
ex14: I love (cats|dogs)
ex15: .+
实践部分:
ex1: [\d]$
ex2: [\D]*(\d{3})
ex3: ([^@+]+)
ex4: <(\w+)
ex5: ([^.]+)[.]{1}(gif|png|jpg)$
ex6: [\s]*((\S+[\s]*)+)
ex7: at.*?[.].*?[.](\w+)\(([\w.]+)\:(\d+)\)
ex8: (\w+)://([\w.-]+)[^\d]+(\d+)? - 统计 words 文件 (
/usr/share/dict/words
) 中包含至少三个a
且不以's
结尾的单词个数。这些单词中,出现频率前三的末尾两个字母是什么?sed
的y
命令,或者tr
程序也许可以帮你解决大小写的问题。共存在多少种词尾两字母组合?还有一个很 有挑战性的问题:哪个组合从未出现过?
在Linux中通常存在三种正则匹配模式,例如在像grep这类工具中,基本正则表达式(BRE)、扩展正则表达式(ERE)和 Perl 兼容正则表达式(PCRE)主要在语法与表达能力方面存在差异,具体如下:
1. 基本正则表达式(BRE)
grep中的默认模式:若不使用grep -E或grep -P,grep就采用 BRE 语法。
特殊字符需转义:分组用的()要写成\(`和`\);“或” 操作符|要转写成\|;表示 “一个或多个” 的+以及 “零个或一个” 的?,需转义为\+和\?才能使用。
特点及用法:模式较简单,高级功能少,适用于简单的匹配需求。
示例:echo "abc abc" | grep "a\(b\|c\)"可匹配 “ab” 或 “ac”。
2. 扩展正则表达式(ERE)
启用方式:使用grep -E(egrep也行,但已不推荐使用)来启用。
特殊字符多数无需转义:分组的()、“或” 操作符|以及 “一个或多个” 的+等,不用转义就能正常使用,相比 BRE,书写复杂模式时更易读。
示例:echo "abc abc" | grep -E "a(b|c)"能匹配 “ab” 或 “ac”。
3. Perl 兼容正则表达式(PCRE)
启用方式:通过grep -P启用。
功能特点:功能最丰富,遵循 Perl 的正则表达式引擎,支持前瞻、后顾断言(如(?=...)、(?<=...))、非捕获组((?:...))、惰性量词(*?、+?、??)以及命名组((?<name>...))等高级特性。
以下是一些示例,用于阐释Perl兼容正则表达式(PCRE)特有的高级功能,包括前瞻/后顾断言,非捕获组,惰性量词以及命名组:
1. 正向前瞻断言((?=...))
仅当某个模式后面紧接着另一个模式时才进行匹配,并且前瞻断言中的内容不会包含在最终的匹配结果内。
2. 反向后顾断言((?<=...))echo "abc123xyz" | grep -P "abc(?=\d{3})" # 仅当“abc”后面紧接着是3个数字(“123”)时才匹配“abc”,输出结果为“abc”。
仅当某个模式前面紧挨着另一个模式时才进行匹配。
3. 负向前瞻断言((?!...))echo "xyzabc123" | grep -P "(?<=abc)\d+" # 仅当数字前面紧挨着“abc”时才匹配这些数字,输出结果为“123”。
确保匹配的内容后面不会紧接着出现指定的模式。
4. 负向后顾断言((?<!...))echo "abc123xyz" | grep -P "abc(?!\d{3})" # 仅当“abc”后面不是紧接着3个数字时才匹配“abc”,在此示例中无输出内容。
确保匹配的内容前面不会紧挨着指定的模式。
6. 惰性量词(*?、+?、??)echo "xyz123abc" | grep -P "(?<!xyz)\d+" # 仅当数字前面不是紧挨着“xyz”时才匹配这些数字,在此示例中无输出内容。
在仍能使正则表达式匹配成功的前提下,尽可能少地匹配字符。
7. 命名组((?<name>...))echo "aaaa" | grep -P "a+?" # 匹配第一个“a”(而不是像使用“a+”那样贪婪地匹配所有的“a”)。
捕获一个匹配的组,并为其指定一个名称,该名称可在后续操作中被引用。echo "year=2024" | grep -P "(?<year>\d{4})" # 匹配四位数字表示的年份,并将其存储为名为“year”的命名组。
综合示例:
此示例在一个正则表达式中展示了多个 PCRE 的功能:
后顾断言((?<=email:\s)):确保匹配的内容前面是 “email:”。echo "email: user@example.com" | grep -P "(?<=email:\s)(?:[a-zA-Z0-9._%+-]+)@(?<domain>[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})" # 输出结果:user@example.com
非捕获组((?:[a-zA-Z0-9._%+-]+)):匹配电子邮件地址的本地部分,但不进行捕获。
命名组((?<domain>...)):将电子邮件地址的域名部分捕获为 “domain”。
我的统计 words 文中包含至少三个a
且不以's
结尾的单词个数为853个,命令如下cat /usr/share/dict/words | grep -E "([Aa]{1}\w*){3,}" | sed -E "/\w+'s/d" | wc -l
继续统计出现频率前三的末尾两个字母是什么,命令如下:
(1)读取单词文件cdcat /usr/share/dict/words | grep -E "([Aa]{1}\w*){3,}" | sed -E "/\w+'s/d" | sed -E "s/\w+(\w{2})$/\1/" | sed 's/[A-Z]/\L&/g' | sort | uniq -c | sort -k 1nr |sed -n "1,3p" # output: 101 an 63 ns 54 as
cat /usr/share/dict/words 读取系统自带的单词文件,作为后续处理的原始数据。
(2)筛选单词:
grep -E "([Aa]{1}\w*){3,}" 用扩展正则表达式选出包含至少三个以 A 或 a 开头 “单词片段” 的单词。
sed -E "/\w+'s/d" 删除以 's 结尾的单词,进一步筛选。
sed -E "s/\w+(\w{2})$/\1/" 将单词替换为其末尾两个字符。
(3)处理与统计:
sed 's/[A-Z]/\L&/g' \L 是 sed 中的一个转换标记,它后面跟着的 & 表示匹配到的整个内容(也就是匹配到的那个大写字母),\L 会将 & 所代表的大写字母转换为小写字母。
sort 对单词按字母顺序排序,方便后续统计重复情况。
uniq -c 统计连续重复单词的出现次数。
sort -k 1nr 按出现次数倒序排序,高频次在前。
(4)输出结果:
sed -n "1,3p" 只输出排序后频次最高的前三行内容,展示出现频次靠前的相关单词情况
还有一个很 有挑战性的问题:哪个组合从未出现过? 为了得到没出现的组合,首先我们要生成一个包含全部组合的列表,然后再使用上面得到的出现的组合,比较二者不同即可。创建text1.txt文件存储word中特定单词的俩字母结尾组合:
cat /usr/share/dict/words | grep -E "([Aa]{1}\w*){3,}" | sed -E "/\w+'s/d" | sed -E "s/\w+(\w{2})$/\1/" |sed 's/[A-Z]/\L&/g' | sort | uniq -c | sort -k 1nr | awk '{print $2}' > text1.txt
写一个脚本,我命名为generator来生成26^2种所有可能的俩字母组合:
#!/bin/bash for i in {a..z};do for j in {a..z};do echo "$i$j" done done
记得chmod +x generator使该文件变成可执行文件。
./generator > text2.txt
接下来就是比较俩个文件的不同之处:
grep -F -v -f text1.txt text2.txt
用wc -l 查看有564种组合没有包括,而text1.txt有112行,text2.txt有26^2=676行,故结果正确。(这里面有个很坑的点导致我debug很久,就是在words文件中存在单词以AA结尾,我之前没有对正则匹配结果进行大小写转换,导致找文件不同之处行数始终不匹配一行)
-
进行原地替换听上去很有诱惑力,例如:
sed s/REGEX/SUBSTITUTION/ input.txt > input.txt
。但是这并不是一个明智的做法,为什么呢?还是说只有sed
是这样的? 查看man sed
来完成这个问题将
sed
的输出重定向到与其输入相同的文件(例如sed s/REGEX/SUBSTITUTION/ input.txt > input.txt
)这种操作方式存在问题。原因在于这是大多数 shell 中文件重定向工作方式导致的普遍行为,具体表现为两方面:一方面,当执行> input.txt
这样的重定向操作时,shell 会立刻以写入模式打开文件,在sed
还没开始读取文件内容前就截断文件原有内容;另一方面,由于文件已被清空,sed
没办法获取原始内容来按设定的正则表达式(REGEX)和替换内容(SUBSTITUTION)执行相应的替换操作,如此一来便极有可能造成数据丢失或者使文件处于损坏状态。为了避免上述问题,文中介绍了正确的操作方式。一种可行的做法是借助临时文件来完成替换操作,示例代码如下:
sed 's/REGEX/SUBSTITUTION/' input.txt > temp.txt && mv temp.txt input.txt
其原理是先将
sed
对input.txt
文件执行替换操作后的结果输出到临时文件temp.txt
中,然后再通过mv
命令把临时文件temp.txt
覆盖原文件input.txt
,以此保证整个替换过程安全且不会出现因直接覆盖原文件而引发的数据丢失、文件损坏等情况。此外,
sed
本身提供了用于就地编辑的-i
选项,使用该选项能够直接对文件进行修改,无需额外创建临时文件。示例代码如下:sed -i.bak 's/REGEX/SUBSTITUTION/' input.txt
这里的
-i
代表 “in-place(就地)” 的意思。需要注意的是,如果使用的是 GNUsed
,还可以选择添加一个备份扩展名,例如-i.bak
,这样在对原文件进行就地编辑、覆盖原文件内容之前,会先创建一个原文件的备份文件。
在 man sed 手册中也对 -i 选项有所说明,其格式为 -i[SUFFIX], --in-place[=SUFFIX],含义是就地编辑文件,并且如果提供了后缀(SUFFIX),则会创建备份;要是没提供后缀,默认就是直接编辑文件,不会创建备份。
- 找出您最近十次开机的开机时间平均数、中位数和最长时间。在 Linux 上需要用到
journalctl
,而在 macOS 上使用log show
。找到每次起到开始和结束时的时间戳。在 Linux 上类似这样操作:Logs begin at ...
和
systemd[577]: Startup finished in ...
查看最近的开机时间:
journalctl --list-boots -r | sed -n "1,11p" | awk '{print $4,$5}' # 输出: # FIRST ENTRY # 2024-11-26 19:34:06 # 2024-11-26 19:28:54 # 2024-11-26 19:23:12 # 2024-11-26 00:16:35 # 2024-11-25 19:58:04 # 2024-11-25 15:03:02 # 2024-11-25 15:00:29 # 2024-11-24 23:23:37 # 2024-11-24 23:19:22 # 2024-11-24 16:20:47
- 查看之前三次重启启动信息中不同的部分(参见
journalctl
的-b
选项)。将这一任务分为几个步骤,首先获取之前三次启动的启动日志,也许获取启动日志的命令就有合适的选项可以帮助您提取前三次启动的日志,亦或者您可以使用sed '0,/STRING/d'
来删除STRING
匹配到的字符串前面的全部内容。然后,过滤掉每次都不相同的部分,例如时间戳。下一步,重复记录输入行并对其计数(可以使用uniq
)。最后,删除所有出现过 3 次的内容(因为这些内容是三次启动日志中的重复部分)。
首先储存最近三次启动的信息:
删除所有出现过 大于3 次的内容:journalctl --boot=-0 > last3start.txt journalctl --boot=-1 >> last3start.txt journalctl --boot=-2 >> last3start.txt
cat last3start.txt | cut -d ' ' --complement -f 1,2,3 | sort | uniq -c | sort -k 1nr | awk '$1<3{print}' | awk '{$1=" ";print}'
- 在网上找一个类似 这个 或者 这个 的数据集。或者从 这里 找一些。使用
curl
获取数据集并提取其中两列数据,如果您想要获取的是 HTML 数据,那么 pup 可能会更有帮助。对于 JSON 类型的数据,可以试试 jq。请使用一条指令来找出其中一列的最大值和最小值,用另外一条指令计算两列之间差的总和。
MIT工具课第四课任务 Linux在shell中数据流处理 正则表达式
于 2024-11-27 16:31:19 首次发布