MIT工具课第四课任务 Linux在shell中数据流处理 正则表达式

  1. 学习一下这篇简短的 交互式正则表达式教程.
    感觉基本字符的匹配语法与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+)?
  2. 统计 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. 正向前瞻断言((?=...))
    仅当某个模式后面紧接着另一个模式时才进行匹配,并且前瞻断言中的内容不会包含在最终的匹配结果内。
    echo "abc123xyz" | grep -P "abc(?=\d{3})"
    # 仅当“abc”后面紧接着是3个数字(“123”)时才匹配“abc”,输出结果为“abc”。
    2. 反向后顾断言((?<=...))
    仅当某个模式前面紧挨着另一个模式时才进行匹配。
    echo "xyzabc123" | grep -P "(?<=abc)\d+"
    # 仅当数字前面紧挨着“abc”时才匹配这些数字,输出结果为“123”。
    3. 负向前瞻断言((?!...))
    确保匹配的内容后面不会紧接着出现指定的模式。
    echo "abc123xyz" | grep -P "abc(?!\d{3})"
    # 仅当“abc”后面不是紧接着3个数字时才匹配“abc”,在此示例中无输出内容。
    4. 负向后顾断言((?<!...))
    确保匹配的内容前面不会紧挨着指定的模式。
    echo "xyz123abc" | grep -P "(?<!xyz)\d+"
    # 仅当数字前面不是紧挨着“xyz”时才匹配这些数字,在此示例中无输出内容。
    6. 惰性量词(*?、+?、??)
    在仍能使正则表达式匹配成功的前提下,尽可能少地匹配字符。
    echo "aaaa" | grep -P "a+?"
    # 匹配第一个“a”(而不是像使用“a+”那样贪婪地匹配所有的“a”)。
    7.  命名组((?<name>...))
    捕获一个匹配的组,并为其指定一个名称,该名称可在后续操作中被引用。
    echo "year=2024" | grep -P "(?<year>\d{4})"
    # 匹配四位数字表示的年份,并将其存储为名为“year”的命名组。

    综合示例:

    此示例在一个正则表达式中展示了多个 PCRE 的功能:

    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
    后顾断言((?<=email:\s)):确保匹配的内容前面是 “email:”。
    非捕获组((?:[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
    

    继续统计出现频率前三的末尾两个字母是什么,命令如下:
    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 |sed -n "1,3p"
    # output:    
         101 an
         63 ns
         54 as
    
    (1)读取单词文件cd
    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结尾,我之前没有对正则匹配结果进行大小写转换,导致找文件不同之处行数始终不匹配一行)

  3. 进行原地替换听上去很有诱惑力,例如: 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(就地)” 的意思。需要注意的是,如果使用的是 GNU sed,还可以选择添加一个备份扩展名,例如 -i.bak,这样在对原文件进行就地编辑、覆盖原文件内容之前,会先创建一个原文件的备份文件。
          在 man sed 手册中也对 -i 选项有所说明,其格式为 -i[SUFFIX], --in-place[=SUFFIX],含义是就地编辑文件,并且如果提供了后缀(SUFFIX),则会创建备份;要是没提供后缀,默认就是直接编辑文件,不会创建备份。​​​​


  4. 找出您最近十次开机的开机时间平均数、中位数和最长时间。在 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
    
  5. 查看之前三次重启启动信息中不同的部分(参见 journalctl 的 -b 选项)。将这一任务分为几个步骤,首先获取之前三次启动的启动日志,也许获取启动日志的命令就有合适的选项可以帮助您提取前三次启动的日志,亦或者您可以使用 sed '0,/STRING/d' 来删除 STRING 匹配到的字符串前面的全部内容。然后,过滤掉每次都不相同的部分,例如时间戳。下一步,重复记录输入行并对其计数(可以使用 uniq )。最后,删除所有出现过 3 次的内容(因为这些内容是三次启动日志中的重复部分)。
    首先储存最近三次启动的信息:
    journalctl --boot=-0 > last3start.txt
    journalctl --boot=-1 >> last3start.txt
    journalctl --boot=-2 >> last3start.txt
    删除所有出现过 大于3 次的内容:
     
    cat last3start.txt | cut -d ' ' --complement -f 1,2,3 | sort | uniq -c | sort -k 1nr | awk '$1<3{print}' | awk '{$1=" ";print}' 
    
  6. 在网上找一个类似 这个 或者 这个 的数据集。或者从 这里 找一些。使用 curl 获取数据集并提取其中两列数据,如果您想要获取的是 HTML 数据,那么 pup 可能会更有帮助。对于 JSON 类型的数据,可以试试 jq。请使用一条指令来找出其中一列的最大值和最小值,用另外一条指令计算两列之间差的总和。
     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值