The Missing Semester of Your CS Education(第四课,文本处理和数据处理)笔记

NOTE:这堂课有一句话我很受用:好用的工具通常由别人告诉你,而使用这个工具的技巧则通常可以通过查手册学会

1. journalctl 命令

systemd用来启动系统并管理进程。systemd包含了一个叫做journalctl的辅助组件,其主要作用是管理系统的事件日志记录。

journalctl可以查看所有的系统日志文件,由于日志信息量很大,journalctl还提供了各种参数帮助用户更快速的定位到日志信息。

默认情况下,用户都可以访问自己的日志。对于系统主日志和其他用户的日志,仅限于有权限的用户访问,比如root用户,wheel组和systemd组的用户。

来源:https://blog.csdn.net/qq_36595013/article/details/107318025

2. systemd:暂时理解为linux下一个用于管理系统服务和进程的软件

systemd即为system daemon,是linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证下开源发布,开发目标是提供更优秀的框架以表示 系统服务间的依赖关系,并依此实现系统初始化时 服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替常用的System V与BSD风格init程序。
与多数发行版使用的System V风格init相比,systemd采用了以下新技术: (1) 采用Socket激活式与总线激活式 服务,以提高相互依赖的 各服务的并行运行性能; (2) 用Cgroups代替PID来 追踪进程,因此即使是两次fork之后生成的守护进程也不会脱离systemd的控制。
来源:百度百科
======================================以下内容摘自  The Missing Semester of Your CS Education(第四课)================================
看完第四课的一些补充:
R语言:很适合做数据分析和绘制图表的工具
gnuplot可以绘制一些简单的图表
ffmpeg适用于处理多媒体文件(比如视频、图片、声音)
ssh 可以用于远程传输文件
awk是一种非常善于处理文本的编程语言
3. sed命令中经常会用到正则表达式,正则表达式中常见的特殊字符有
  • . 除换行符之外的”任意单个字符”
  • * 匹配前面字符零次或多次
  • + 匹配前面字符一次或多次
  • [abc] 匹配 ab 和 c 中的任意一个
  • (RX1|RX2) 任何能够匹配RX1 或 RX2的结果
  • ^ 行首
  • $ 行尾
 NOTE: sed 的正则表达式替换语法和vim类似,当末尾不添加'g'时,每行只会匹配一次,当末尾添加'g'时,会匹配全行中符合正则表达式的内容并执行替换
NOTE: 构建正则表达式的一个很好的建议:先构建一个简单的正则表达式来满足你简单的需求,然后一点点修饰这个正则表达式,直到它最后满足所有的需求
sed 的正则表达式有些时候是比较奇怪的,它需要你在这些模式前添加 \才能使其具有特殊含义。或者,您也可以 添加-E选项来支持这些匹配。

回过头我们再看/.*Disconnected from /,我们会发现这个正则表达式可以匹配任何以若干任意字符开头,并接着包含”Disconnected from “的字符串。这也正式我们所希望的。但是请注意,正则表达式并不容易写对。如果有人将 “Disconnected from” 作为自己的用户名会怎样呢?

Jan 17 03:13:00 thesquareplanet.com sshd[2631]: Disconnected from invalid user Disconnected from 46.97.239.16 port 55920 [preauth]

正则表达式会如何匹配?* 和 + 在默认情况下是贪婪模式,也就是说,它们会尽可能多的匹配文本。因此对上述字符串的匹配结果如下:

46.97.239.16 port 55920 [preauth]

这可不是我们想要的结果。对于某些正则表达式的实现来说,您可以给 * 或 + 增加一个? 后缀使其变成非贪婪模式,但是很可惜 sed 并不支持该后缀。不过,我们可以切换到 perl 的命令行模式,该模式支持编写这样的正则表达式:

perl-pe 's/.*?Disconnected from //'

让我们回到 sed 命令并使用它完成后续的任务,毕竟对于这一类任务,sed是最常见的工具。sed 还可以非常方便的做一些事情,例如打印匹配后的内容,一次调用中进行多次替换搜索等。但是这些内容我们并不会在此进行介绍。sed 本身是一个非常全能的工具,但是在具体功能上往往能找到更好的工具作为替代品。

正则表达式在线调试工具 regex debugger      https://regex101.com/r/qqbZqh/2

好的,我们还需要去掉用户名后面的后缀,应该如何操作呢?

想要匹配用户名后面的文本,尤其是当这里的用户名可以包含空格时,这个问题变得非常棘手!这里我们需要做的是匹配一整行

|sed -E 's/.*Disconnected from (invalid |authenticating )?user .* [^ ]+ port [0-9]+( \[preauth\])?$//'

让我们借助正则表达式在线调试工具regex debugger 来理解这段表达式。OK,开始的部分和以前是一样的,随后,我们匹配两种类型的“user”(在日志中基于两种前缀区分)。再然后我们匹配属于用户名的所有字符。接着,再匹配任意一个单词([^ ]+ 会匹配任意非空且不包含空格的序列)。紧接着后面匹配单“port”和它后面的一串数字,以及可能存在的后缀[preauth],最后再匹配行尾。

注意,这样做的话,即使用户名是“Disconnected from”,对匹配结果也不会有任何影响,您知道这是为什么吗?(TODO: so...为什么呢?) (TODO:其实我想知道IP地址是在哪里匹配的)(回答:我大概明白了,IP地址是在[^ ]+匹配的,而至于带空格的用户名则由(.*)匹配,至于其中原因,我们可以回忆一下编译原理里学到的东西:用于匹配正则表达式的状态机可不会只扫描一次语句,当它发现用之前的逻辑无法匹配时,它就会退回,换另一种逻辑来匹配)

问题还没有完全解决,日志的内容全部被替换成了空字符串,整个日志的内容因此都被删除了。我们实际上希望能够将用户名保留下来。对此,我们可以使用“捕获组(capture groups)”来完成。被圆括号内的正则表达式匹配到的文本,都会被存入一系列以编号区分的捕获组中。捕获组的内容可以在替换字符串时使用(有些正则表达式的引擎甚至支持替换表达式本身),例如\1、 \2\3等等,因此可以使用如下命令:

|sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/'

想必您已经意识到了,为了完成某种匹配,我们最终可能会写出非常复杂的正则表达式。例如,这里有一篇关于如何匹配电子邮箱地址的文章e-mail address,匹配电子邮箱可一点也不简单。网络上还有很多关于如何匹配电子邮箱地址的讨论。人们还为其编写了测试用例及 测试矩阵。您甚至可以编写一个用于判断一个数是否为质数的正则表达式。

正则表达式是出了名的难以写对,但是它仍然会是您强大的常备工具之一。

===========总结一下sed能干的事情================

1. 替换

2. 文本注入

3. 打印特定行(比如基于索引选择特定行)

========================================

==========一些关于sed使用技巧的小补充=============

1. 可以在正则表达式中使用小括号“()”来表示匹配字符串,比如(ab)表示匹配字符串“ab”,但是由于sed是个old tool,需要加上-E前缀来使用这些新特性。

2. 如果要匹配特殊字符,比如"()"或者".",则在特殊字符前面加上反斜杠"\"

========================================

回到数据整理

OK,现在我们有如下表达式:

ssh myserver journalctl
 |grep sshd
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/' 

sed 还可以做很多各种各样有趣的事情,例如文本注入:(使用 i 命令),打印特定的行 (使用 p命令),基于索引选择特定行等等。详情请见man sed!

现在,我们已经得到了一个包含用户名的列表,列表中的用户都曾经尝试过登陆我们的系统。但这还不够,让我们过滤出那些最常出现的用户:

ssh myserver journalctl
 |grep sshd
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/' | sort | uniq -c 

sort 会对其输入数据进行排序。uniq -c 会把连续出现的行折叠为一行并使用出现次数作为前缀。我们希望按照出现次数排序,过滤出最常出现的用户名:

ssh myserver journalctl
 |grep sshd
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/' | sort | uniq -c | sort -nk1,1 | tail -n10 

sort -n 会按照数字顺序对输入进行排序(默认情况下是按照字典序排序 -k1,1 则表示“仅基于以空格分割的第一列进行排序”。,n 部分表示“仅排序到第n个部分”,默认情况是到行尾。就本例来说,针对整个行进行排序也没有任何问题,我们这里主要是为了学习这一用法!

如果我们希望得到登陆次数最少的用户,我们可以使用 head 来代替tail。或者使用sort -r来进行倒序排序。

相当不错。但我们只想获取用户名,而且不要一行一个地显示。

ssh myserver journalctl
 |grep sshd
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/' | sort | uniq -c | sort -nk1,1 | tail -n10 | awk '{print $2}' | paste -sd, 

如果您使用的是 MacOS:注意这个命令并不能配合 MacOS 系统默认的 BSD paste使用。参考课程概览与 shell的习题内容获取更多相关信息。

我们可以利用 paste命令来合并行(-s),并指定一个分隔符进行分割 (-d),那awk的作用又是什么呢?

awk – 另外一种编辑器

awk 其实是一种编程语言,只不过它碰巧非常善于处理文本。关于 awk 可以介绍的内容太多了,限于篇幅,这里我们仅介绍一些基础知识。

首先, {print $2} 的作用是什么? awk 程序接受一个模式串(可选),以及一个代码块,指定当模式匹配时应该做何种操作。默认当模式串即匹配所有行(上面命令中当用法)。 在代码块中,$0 表示整行的内容,$1 到 $n 为一行中的 n 个区域,区域的分割基于 awk 的域分隔符(默认是空格,可以通过-F来修改)。在这个例子中,我们的代码意思是:对于每一行文本,打印其第二个部分,也就是用户名。

让我们康康,还有什么炫酷的操作可以做。让我们统计一下所有以c 开头,以 e 结尾,并且仅尝试过一次登陆的用户。

|awk '$1 == 1 && $2 ~ /^c[^ ]*e$/ { print $2 }' | wc -l

让我们好好分析一下。首先,注意这次我们为 awk指定了一个匹配模式串(也就是{...}前面的那部分内容)。该匹配要求文本的第一部分需要等于1(这部分刚好是uniq -c得到的计数值),然后其第二部分必须满足给定的一个正则表达式。代码块中的内容则表示打印用户名。然后我们使用 wc -l 统计输出结果的行数。

不过,既然 awk 是一种编程语言,那么则可以这样:

BEGIN { rows = 0 } $1 == 1 && $2 ~ /^c[^ ]*e$/ { rows += $1 } END { print rows } 

BEGIN 也是一种模式,它会匹配输入的开头( END 则匹配结尾)。然后,对每一行第一个部分进行累加,最后将结果输出。事实上,我们完全可以抛弃 grep 和 sed ,因为 awk 就可以解决所有问题。至于怎么做,就留给读者们做课后练习吧。

分析数据

bc - An arbitrary precision calculator language

想做数学计算也是可以的!例如这样,您可以将每行的数字加起来:

|paste -sd+ | bc -l

下面这种更加复杂的表达式也可以:

echo "2*($(data | paste -sd+))" | bc -l 

您可以通过多种方式获取统计数据。如果已经安装了R语言st是个不错的选择:

ssh myserver journalctl
 |grep sshd
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/' | sort | uniq -c | awk '{print $1}' | R --slave -e 'x <- scan(file="stdin", quiet=TRUE); summary(x)' 

R 也是一种编程语言,它非常适合被用来进行数据分析和绘制图表这里我们不会讲的特别详细, 您只需要知道summary 可以打印某个向量的统计结果。我们将输入的一系列数据存放在一个向量后,利用R语言就可以得到我们想要的统计数据。

如果您希望绘制一些简单的图表, gnuplot 可以帮助到您:

ssh myserver journalctl
 |grep sshd
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/' | sort | uniq -c | sort -nk1,1 | tail -n10 | gnuplot -p -e 'set boxwidth 0.5; plot "-" using 1:xtic(2) with boxes' 

 

利用数据整理来确定参数

有时候您要利用数据整理技术从一长串列表里找出你所需要安装或移除的东西。我们之前讨论的相关技术配合 xargs 即可实现:

rustup toolchain list |grep nightly | grep -vE "nightly-x86" | sed 's/-x86.*//' | xargs rustup toolchain uninstall 

 

整理二进制数据

虽然到目前为止我们的讨论都是基于文本数据,但对于二进制文件其实同样有用。例如我们可以用 ffmpeg 从相机中捕获一张图片,将其转换成灰度图后通过SSH将压缩后的文件发送到远端服务器(NOTE:可以使用ssh传输文件),并在那里解压、存档并显示。

ffmpeg-loglevel panic -i /dev/video0 -frames 1 -f image2 -
 | convert - -colorspace gray - | gzip | ssh mymachine 'gzip -d | tee copy.jpg | env DISPLAY=:0 feh -'

 除了ssh外,还可以使用scp来传输文件,但目标服务器要开启写入权限。

=========================第四课习题===============================

1. 学习正则表达式tutorial

在正则表达式中,\w 等同于 [A-Za-z0-9_] (注意,还有个下划线)  

2. 可以使用花括号{}来表明前面的字符应该被匹配多少次(注意,这种语法在某些正则表达式实现中不被支持)

3. \d 代表数字  \s代表空白字符(包括\t\n\r还有空格等等)

4. 左右括号() 除了用来表示字符串外,还可以作为“子正则表达式”。此外捕捉组还有个用途,设想你要匹配一个文件夹下的所有图像,你可以使用^(IMG\d+\.png)$ 来匹配图像,但这样的字符串会带有.png后缀,如果你只想捕捉图像名称而不希望带后缀,则可以使用^(IMG\d+)\.png$ ,这样一来后缀名.png就不会被捕捉了。

来源: https://regexone.com/lesson/capturing_groups?

5. 捕捉组也可以嵌套使用,例如 ^(IMG(\d+))\.png$,\1会包含整个文件名(包括数字,但不包括.png),而\2则只包含文件名中的数字,这个顺序是根据左括号( 被定义的顺序来决定的。

来源:https://regexone.com/lesson/nested_groups

6. W 表示不匹配 [A-Za-z0-9_], \S表示不匹配空白字符, \D表示不匹配数字 (注意,如果使用[\W\S\D] 并不是不匹配字母数字空白字符,相反,是所有字符都匹配,因为[]内的不同字符是暗含了“或”('|')的)

7. \b 用于匹配一个单词和一个非单词字符之间的边界 (注意!\b只匹配边界,不匹配字符!)

8. 一些正则表达式的实现允许你使用back referencing,也就是把被捕捉的字符串重新用到正则表达式上(使用\0,\1,\2等)。\0表示整个被匹配的文本,而不是被捕捉的字符串。

===

9. tr命令可以把一个文件中的字符全部转成大写,也可以把一个文件中的空白字符都去掉

10.

sed中,使用\u表示大写,\l表示小写
1. 把每个单词的第一个小写字母变大写:
sed 's/\b[a-z]/\u&/g' filename
2. 把所有小写变大写:
sed 's/[a-z]/\u&/g' filename
3. 大写变小写:
sed 's/[A-Z]/\l&/g' filename
来源:https://www.cnblogs.com/zzlinux/p/5861693.html
11. 
diff --unchanged-group-format='' <(cat occurance.txt) <(cat all.txt) | wc -l

--unchanged-group-format=''用于将两个文件中相同的内容设置为空字符串,剩下的内容就是差异的部分。

12. 

sed s/REGEX/SUBSTITUTION/ input.txt > input.txt 表达式中后一个 input.txt会首先被清空,而且是发生在前的。所以前面一个input.txt在还没有被 sed 处理时已经为空了。在使用正则处理文件前最好是首先备份文件。

sed -i.bak s/REGEX/SUBSTITUTION/ input.txt > input.txt

可以自动创建一个后缀为 .bak 的备份文件。

13. 打印一个文件中指定行内容(也可以范围打印)

# sed 打印第 6 行内容
sed -n '6p' file.txt

# awk 打印第 6 行内容
awk 'NR==6' file.txt

# tail 配合 head,打印指定行内容
tail -n +6 file.txt | head -1

来源:https://www.cnblogs.com/sharesdk/p/14133293.html

====

1. 第二题,哪个组合从未出现过?

cat /usr/share/dict/words | grep -E -v -f <(echo ".*($(cat /usr/share/dict/words | grep ".*a.*a.*a.*[^'s]$" | sed -E "s/.*(.{2})$/\1/" | sort | uniq -c | sort -nk1,1 | awk '{print $2}' | paste -sd'|'))$") | sed -E "s/.*(.{2})$/\1/" | sort | uniq -c | sort -nk1,1 | awk '{print $2}'

2. 第四题(如果会R语言,这题很简单,或者使用shell script也很简单)

获取最长时间   journalctl | grep -E "\[1\]:\sStartup finished in" | sed -E "s/.*= (([0-9]*)min)?(.*)s\./\2\3/" | awk '{if ($0 ~ /^[0-9]+\s[0-9\.]+/) print $1*60+$2; else print $0}' | sort -n | tail -n1

获取最短时间  journalctl | grep -E "\[1\]:\sStartup finished in" | sed -E "s/.*= (([0-9]*)min)?(.*)s\./\2\3/" | awk '{if ($0 ~ /^[0-9]+\s[0-9\.]+/) print $1*60+$2; else print $0}' | sort -nr | tail -n1

获取平均数  journalctl | grep -E "\[1\]:\sStartup finished in" | sed -E "s/.*= (([0-9]*)min)?(.*)s\./\2\3/" | awk '{if ($0 ~ /^[0-9]+\s[0-9\.]+/) print $1*60+$2; else print $0}' > time.txt; echo "($(paste -sd+ time.txt))/$(wc -l time.txt | awk '{print $1}')" | bc -l

获取中位数(建议还是使用正确的工具做正确的事情。。。)  

journalctl | grep -E "\[1\]:\sStartup finished in" | sed -E "s/.*= (([0-9]*)min)?(.*)s\./\2\3/" | awk '{if ($0 ~ /^[0-9]+\s[0-9\.]+/) print $1*60+$2; else print $0}' | sort -n > time.txt; cat time.txt | wc -l | awk '{print int($0/2)+1}' | xargs -I {} tail -n {} time.txt | tail -n1

3. 第五题

cat <(journalctl -b 1) <(journalctl -b 2) <(journalctl -b 3) | sed 's/.*chenyinhua-VirtualBox //' | sort | uniq -c | sort -nk1,1 | awk '$1!=3 {print $0}'

4. 第六题

TODO:这里skip吧,但是记住,工具jq适合用于处理json数据,工具pup适合用于处理html数据

来源: https://missing-semester-cn.github.io/missing-notes-and-solutions/2020/solutions/data-wrangling-solution/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值