MIT2020补习班——(四)数据整理

把一种格式的数据转换成另一种格式

具体来说,不管是文本格式还是二进制格式,都要对数据进行处理,直到得到所需的数据为止。

在过去的讲座中,我们已经看到一些基础数据的争论。几乎每次使用|运算符时,都会执行某种数据争用。考虑像journalctl | grep-i intel这样的命令。它查找所有提到Intel的系统日志条目(不区分大小写)。您可能不认为它是一个盘绕的数据,但它正在从一种格式(整个系统日志)转变为一种对您更有用的格式(只是英特尔日志条目)。大多数的数据争论都是关于知道你可以使用什么工具,以及如何组合它们。

让我们从头开始。要整理数据,我们需要两样东西:**要整理的数据,以及与之相关的东西。**日志通常是一个很好的用例,因为您经常想调查关于它们的事情,而阅读整个事情是不可行的。让我们通过查看我的服务器日志来确定谁试图登录到我的服务器:

ssh myserver journalctl

我们把它限制在ssh上:

ssh myserver journalctl | grep sshd

注意,我们正在使用管道在本地计算机上通过grep传输远程文件!ssh很神奇,我们将在下一节关于命令行环境的讲座中进一步讨论它。但这仍然比我们想要的要多得多。而且很难读。让我们做得更好:

ssh myserver 'journalctl | grep sshd | grep "Disconnected from"' | less

为什么要额外引用?好吧,我们的日志可能很大,把它全部流到我们的计算机上然后进行过滤是浪费。相反,我们可以在远程服务器上进行过滤,然后在本地使用数据。less给了我们一个“寻呼机”,允许我们在长输出中上下滚动。为了在调试命令行时节省一些额外的通信量,我们甚至可以将当前筛选的日志粘贴到一个文件中,以便在开发时不必访问网络:

$ ssh myserver 'journalctl | grep sshd | grep "Disconnected from"' > ssh.log
$ less ssh.log

这里仍然有很多噪音。有很多方法可以解决这个问题,但是让我们看看工具箱中最强大的工具之一:sed

sed是一个“流编辑器”,它构建在旧的ed编辑器之上。在它中,您基本上给出了如何修改文件的简短命令,而不是直接操作其内容(尽管您也可以这样做)。有很多命令,但最常见的命令之一是s:substitution。例如,我们可以写:

ssh myserver journalctl
 | grep sshd
 | grep "Disconnected from"
 | sed 's/.*Disconnected from //'

我们刚刚编写的是一个简单的正则表达式;一个强大的构造,它允许您将文本与模式匹配。s命令的格式为:s/REGEX/SUBSTITUTION/,其中REGEX是要搜索的正则表达式,SUBSTITUTION是要用匹配文本替换的文本。

正则表达式

正则表达式是非常常见和有用的,因此值得花一些时间来了解它们是如何工作的。让我们先看看上面使用的那个:/.*Disconnected from /。正则表达式通常(尽管并不总是)被/包围。大多数ASCII字符只携带其正常含义,但有些字符具有“特殊”的匹配行为。究竟哪些字符在正则表达式的不同实现之间做了一些不同的事情,这是一个非常令人沮丧的问题。非常常见的模式有:

. 表示除换行符外的“任何单个字符”

* 前面匹配的零个或多个

+ 前面的一个或多个匹配项

[abc] abc的任何一个字符

(RX1 | RX2)RX1RX2匹配的东西

^ 行首

$ 行尾

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]

这可能不是我们想要的。在某些正则表达式实现中,您可以在*+后面加上使他们不贪婪,但遗憾的是塞德不支持这一点。不过,我们可以切换到perl的命令行模式,它确实支持以下结构:

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

接下来我们将继续使用sed,因为它是这些工作中更常见的工具。sed还可以做其他一些方便的事情,比如在给定的匹配之后打印行、每次调用执行多个替换、搜索内容等等,但是我们在这里不会过多介绍这些内容。sed本身基本上是一个完整的主题,但通常有更好的工具。

好的,我们还有一个后缀要去掉。我们该怎么做?只匹配用户名后面的文本有点棘手,特别是如果用户名可以有空格之类的!我们需要做的是将整个系列匹配起来:

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

让我们看看regex调试器是怎么回事。好吧,开始还是和以前一样。然后,我们匹配任何“用户”变量(日志中有两个前缀)。然后我们在用户名所在的任何字符串上进行匹配。然后我们匹配任何单个单词([^]+;任何非空的非空格字符序列)。然后是“端口”一词,后跟一系列数字。然后可能是后缀[preauth],然后是行的结尾。

注意,使用这种技术,因为“Disconnected from”的用户名不会再让我们感到困惑。你知道为什么吗?

但是有一个问题,那就是整个日志变空了。毕竟我们想保留用户名。为此,我们可以使用“捕获组”。由括号包围的正则表达式匹配的任何文本都存储在编号的捕获组中。这些在替换中是可用的(在某些引擎中,甚至在模式本身中!)如“\1”、“\2”、“\3”等:

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

回到数据争论

好吧,我们现在有了

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,

让我们从粘贴开始:它允许您使用给定的单字符分隔符(-d组合行-s)。但awk是怎么回事?

awk–另一位编辑

awk是一种非常擅长处理文本流的编程语言。

首先,{print$2}做什么?好吧,awk程序采用一个可选模式加上一个块的形式,说明如果模式匹配给定的行该怎么做。默认模式(我们在上面使用)匹配所有行。在块中,$0被设置为整行的内容$1$n被设置为该行的第n个字段,用字段分隔符分隔(默认情况下,空格用-F改变)。在本例中,我们是说,对于每一行,打印第二个字段的内容,这恰好是用户名!

让我们看看能不能做些更新奇的事。让我们计算以c开头以e结尾的一次性用户名的数量:

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

这里有很多东西要拆。首先,注意我们现在有一个模式(在{…}之前的内容)。该模式表示行的第一个字段应该等于1(这是uniq -c中的计数),第二个字段应该与给定的正则表达式匹配。块只是说要打印用户名。然后我们用wc -l计算输出中的行数。

然而,awk是一种编程语言,记得吗?

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

BEGIN是一个模式,它匹配输入的开头(END匹配结尾)。现在,每行块只添加第一个字段的计数(尽管在本例中始终是1),然后我们在最后打印出来。事实上,我们可以完全摆脱grepsed,因为awk可以做到这一切。

分析数据

你可以进行计算!例如,将每行上的数字相加:

| paste -sd+ | bc -l

或产生更详细的表达:

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

你可以通过多种方式获得数据。st很整洁,但是如果你已经有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
 | 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将其发送到远程计算机,在那里解压缩,制作副本,然后显示它。

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 -'

你可能感兴趣的:(linux)