
文档教程【免费下载链接】missing-semesterThe Missing Semester of Your CS Education 项目地址https://gitcode.com/gh_mirrors/mi/missing-semester点击查看免费下载导读本文基于《The Missing Semester of Your CS Education》2019 年数据整理Data Wrangling讲义展开系统讲解如何用一条条管道命令把原始文本以系统日志为典型场景逐步加工成你想要的精确结果。你将掌握grep过滤、sed流编辑与正则表达式、sort/uniq统计分析、awk字段处理、bc/R/gnuplot数值分析以及用xargs把整理结果变成可执行命令的完整方法论。数据整理data wrangling的本质是把数据从一种格式改造成另一种格式直到最终得到你真正想要的东西。很多人已经无意中做过最基础的数据整理例如journalctl | grep -i intel——找出所有提到 Intel不区分大小写的系统日志条目。可以说数据整理的关键不在于掌握某一个工具而在于知道你有哪些工具以及如何把它们组合起来。实战起点从服务器日志里找出入侵尝试者日志是数据整理最典型的应用场景你总是想从里面查点什么但通读全文完全不现实。下面这个场景贯穿全文通过查看服务器日志弄清楚谁在尝试登录我的服务器。最朴素的思路是把远程日志整个拉下来ssh myserver journalctl输出量太大先用管道过滤出 ssh 相关条目ssh myserver journalctl | grep sshd注意这里已经发生了一件很酷的事我们用管道把远程文件流经本地的grep处理。ssh会在后续命令行环境课程中详细讲解可参考 command-line.md 中关于 SSH 与远程机器的章节。输出仍然太多继续缩小范围ssh myserver journalctl | grep sshd | grep Disconnected from仍有很多噪音。清除噪音的手段很多本文重点介绍工具箱中最强大的工具之一sed。进阶在远程端先过滤本地只做整理2020 年版讲义data-wrangling.md在同样场景下给出了更省流量的做法日志可能很大把全部数据流回本地再过滤是浪费的所以把过滤操作放到远程服务器上本地只负责后续整理ssh myserver journalctl | grep sshd | grep Disconnected from | less这里多了一层引号过滤在远程执行less作为分页器让我们能上下滚动查看长输出。调试命令期间还可以先把过滤后的日志存成本地文件省去反复访问网络的成本$ ssh myserver journalctl | grep sshd | grep Disconnected from ssh.log $ less ssh.logsed流编辑器与替换命令sed是一个流编辑器stream editor构建在古老的ed编辑器之上。它的工作方式是指定短小的命令来描述如何修改文件而不是直接操作文件内容虽然它也能做到。sed有大量子命令最常用的是s替换substitutionssh myserver journalctl | grep sshd | grep Disconnected from | sed s/.*Disconnected from //这里用到的.*Disconnected from就是一个简单的正则表达式——一种用模式匹配文本的强大结构。s命令的完整形态是s/REGEX/SUBSTITUTION/其中REGEX是要查找的正则表达式SUBSTITUTION是用于替换匹配文本的内容。顺带一提这个s/.../.../语法和 Vim 的搜索替换十分相似——事实上 Vim 就采用了与sed替换命令相近的语法学会一个工具往往能帮助你更快掌握另一个参见 _2020/editors.md 中 Vim 搜索替换的进阶内容。正则表达式基础正则表达式太常见、太有用了值得花时间弄清它的工作原理。以上面的/.*Disconnected from /为例正则表达式通常但不总是用/包围。绝大多数 ASCII 字符保持字面含义但一部分字符具有特殊匹配行为。不同实现之间这些特殊字符的行为略有差异这也是正则表达式经常令人抓狂的原因。最常用的模式如下模式含义.匹配任意单个字符换行符除外*匹配前一项的零次或多次匹配前一项的一次或多次[abc]匹配a、b、c中任意一个字符(RX1\|RX2)匹配RX1或RX2中的任意一个^匹配行首$匹配行尾sed的正则实现比较特殊要让这些元字符获得特殊含义通常需要在前面加\或者干脆给sed传-E参数启用扩展正则extended regex这正是后续所有例子里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]结果会是什么*和默认是贪婪greedy的它们会尽量匹配尽可能多的文本。所以上面这行最终只会剩下46.97.239.16 port 55920 [preauth]这可能不是我们想要的。某些正则实现允许给*/后缀?使其变成非贪婪non-greedy可惜sed不支持。不过可以切换到 perl 的命令行模式它支持这种写法perl -pe s/.*?Disconnected from //不过本文其余部分仍以sed为主因为它是这类工作里更常见的工具。sed还能做很多其他事在某个匹配之后打印若干行、单次调用执行多次替换、查找内容等但这里不展开——sed本身就是一个大话题而且往往有更合适的工具。详见man sed。用正则匹配整行处理带空格的用户名现在还想去掉尾部后缀。直接匹配用户名后面的文本有点棘手尤其当用户名本身可能包含空格时。正确的做法是匹配整行| sed -E s/.*Disconnected from (invalid |authenticating )?user .* [^ ] port [0-9]( \[preauth\])?$//逐段拆解这个模式开头部分和之前一样然后匹配两种user前缀变体日志中确实存在invalid user和authenticating user两种形式接着匹配任意一串字符充当用户名再匹配一个单词[^ ]即任意非空、不含空格的字符序列然后是字面量port加一串数字之后是可选的[preauth]后缀最后$锚定行尾。注意用这种整行匹配技巧后用户名恰为Disconnected from的情况再也不会迷惑我们了——因为user前缀和.会先把用户名字段匹配完整行匹配从右向左的边界也被$与[^ ]固定住了。捕获组把用户名保留下来但这里有个新问题整行匹配意味着整行都被替换掉输出全空了。我们其实想保留用户名。为此要使用捕获组capture groups任何被括号包围的匹配文本都会存入带编号的捕获组在替换文本里某些引擎甚至可以在模式本身里用\1、\2、\3等引用。于是| sed -E s/.*Disconnected from (invalid |authenticating )?user (.*) [^ ] port [0-9]( \[preauth\])?$/\2/\2引用的正是第二个括号捕获的内容——用户名。正则表达式可以变得极其复杂有人专门写文章讨论如何匹配电子邮件地址这件事并不容易有大量讨论、专门的测试集和测试矩阵甚至可以用正则判断一个给定的数字是否为素数。正则表达式以难以写对著称但一旦掌握就是工具箱里非常趁手的利器。回到数据整理从一行 sed 到完整流水线现在流水线已经长这样ssh myserver journalctl | grep sshd | grep Disconnected from | sed -E s/.*Disconnected from (invalid |authenticating )?user (.*) [^ ] port [0-9]( \[preauth\])?$/\2/其实只用sed也能完成全部工作——为了好玩可以试试ssh myserver journalctl | sed -E -e /Disconnected from/!d -e s/.*Disconnected from (invalid |authenticating )?user (.*) [^ ] port [0-9]( \[preauth\])?$/\2/这里展示了sed的部分能力-e可以串联多条编辑命令第一条/Disconnected from/!d的含义是删除所有不匹配Disconnected from的行!表示取反d表示删除。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 -csort对输入排序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只按第一个以空白分隔的列排序-k1,1中的,1表示排序截止到第 1 个字段默认是行尾。这个例子中按整行排序结果一样但这里是学习时刻想要最不常见的把tail换成head即可sort -r可以反向排序。把用户名拼成一行paste 登场现在我们还想只输出用户名而且不要每行一个而是拼成一行比如方便写进配置文件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,paste用-sserial按顺序把行合并-d指定单字符分隔符这里是逗号。那么中间那个awk {print $2}是怎么回事macOS 提示如果你的系统是 macOS上面这条命令里的 BSD 版paste并不支持这种用法。BSD 与 GNU coreutils 的差异、以及在 macOS 上安装 GNU coreutils 的方法参见 _2020/shell-tools.md 的练习部分。awk另一种编辑器awk是一套编程语言恰好非常擅长处理文本流。完整学习awk内容极多这里只过基础。先看{print $2}做了什么。awk程序的形态是一个可选模式pattern加上一个块block块描述当模式匹配某行时执行什么动作。我们上面用的默认模式匹配所有行。在块内部$0保存整行的内容$1到$n保存该行按字段分隔符切分后的第 n 个字段字段分隔符默认是空白可用-F修改。所以{print $2}的含义是对每一行打印第二个字段——恰好就是用户名带模式的条件统计做点更有意思的统计只出现过一次、以c开头、以e结尾的用户名数量| awk $1 1 $2 ~ /^c[^ ]*e$/ { print $2 } | wc -l这里面信息量很大。首先模式部分{...}之前的内容要求行的第一个字段等于1这正是uniq -c输出的计数并且第二个字段匹配正则/^c[^ ]*e$/——以c开头、中间是任意非空格字符、以e结尾。块部分打印用户名最后用wc -l统计输出行数。awk 的 BEGIN / END 块别忘了awk是一门编程语言BEGIN { rows 0 } $1 1 $2 ~ /^c[^ ]*e$/ { rows $1 } END { print rows }BEGIN是匹配输入开始以及END匹配输入结束的特殊模式。逐行块把第一个字段出现次数累加到rows最后在END块打印结果。事实上grep和sed能做的事awk全都能做——完全可以用awk一手包办整条流水线这留给读者作为练习。数据分析bc、R 与 gnuplot用 bc 直接在 shell 里做数学bc是一个可以从 STDIN 读入表达式的计算器。把每行的数字用连接起来再交给它就能求和| paste -sd | bc -l-l参数加载标准数学库支持浮点与三角函数等。还能构造更复杂的表达式echo 2*($(data | paste -sd)) | bc -l$(...)命令替换会把data | paste -sd的输出一串连接的表达式嵌入外层算式再交给bc -l计算。用 R 做统计摘要统计工具的选择很多如果你已经装了 R可以把用户名计数喂给 R 做 summaryssh 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(filestdin, quietTRUE); summary(x)R --slave或 2020 版讲义中的R --no-echo以非交互方式执行脚本scan(filestdin, quietTRUE)从标准输入读入一个数值向量summary(x)输出该向量的最小值、四分位数、中位数、均值、最大值等摘要统计。R 是另一种奇怪的编程语言擅长数据分析与绘图这里不做深入。用 gnuplot 画柱状图只想快速画个图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-p让绘图窗口在脚本结束后保持打开-e执行一段 gnuplot 脚本set boxwidth 0.5设置柱宽plot -表示从标准输入读取数据using 1:xtic(2)表示用第 1 列作为数值、第 2 列作为 x 轴刻度标签with boxes指定画柱状图。用数据整理生成命令参数xargs有时你想根据一份较长的清单来安装或卸载某些东西——前面所有数据整理技巧加上xargs就是一套强力组合。经典例子卸载 Rust 旧的 nightly 工具链讲义中的演示命令rustup toolchain list | grep nightly | grep -vE nightly-x86|01-17 | sed s/-x86.*// | xargs rustup toolchain uninstall逐步拆解rustup toolchain list列出已安装的工具链grep nightly只保留 nightly 相关条目grep -vE nightly-x86|01-17-v反向匹配排除、-E启用扩展正则把包含nightly-x86或01-17的行剔除sed s/-x86.*//把从-x86开始的后缀删掉xargs rustup toolchain uninstall把每一行输出作为参数逐个传给卸载命令。xargs把标准输入中的每一行或按空白切分的每段转成命令参数这是数据整理 → 批量操作的标准接缝。仓库内延伸二进制数据整理与 XML→JSON管道同样适用于二进制数据以上讨论的都是文本但管道对二进制数据同样适用。2020 版讲义给出了一个完整的端到端示例用 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 -ffmpeg -loglevel panic -i /dev/video0 -frames 1 -f image2 -从/dev/video0抓取一帧输出到标准输出-convert - -colorspace gray -ImageMagick 的convert读标准输入、转灰度、再写回标准输出gzip压缩ssh mymachine ...远程执行解压tee copy.jpg同时把数据落盘一份env DISPLAY:0 feh -在远程显示器的:0上展示这张图。整个链条里数据从始至终只是字节流经一个个程序——这正是数据整理理念对二进制世界的延伸。仓库中的 XML 示例数据仓库在 _2019/files/example-data.xml2020 版为 _2020/files/example-data.xml提供了一个结构规整的示例数据一个people根元素下包含大量person每个person有name与email。在 _2020/editors.md 的宏Macros一节中这门课展示了如何用 Vim 宏把这个 XML 转成 JSON——其中也明确给出了用sed/正则的中间路线例如g/people/d删除people行%s/person/{/g把person替换成{%s/name\(.*\)\/name/name: \1,/g用捕获组\1提取姓名并重排成 JSON 键值对。这与本文的sed捕获组技巧一脉相承同一套正则思维在sed、Vim 宏、awk等不同工具间是相通的。练习如果你还不熟悉正则表达式可以先完成一个短小的交互式正则教程如 regexone它覆盖了大部分基础语法。sed s/REGEX/SUBSTITUTION/g与普通sed有什么区别/I或/m后缀又分别做什么提示g表示全局替换I表示忽略大小写m表示多行模式。做就地替换时很多人会忍不住写sed s/REGEX/SUBSTITUTION/ input.txt input.txt。但这是坏主意为什么这是sed特有的问题吗提示重定向会先截断输入文件。用man sed查查正确的就地编辑方法。用你熟悉的语言和正则库实现一个简化版grep。如果想让输出像grep一样带颜色高亮去研究一下 ANSI 颜色转义序列。有些操作比如重命名文件用裸mv很别扭。rename是个很好用的工具语法类似sed。创建一批名字里带空格的测试文件用rename把空格替换成下划线。找出过去三次重启之间不共享的启动消息参考journalctl的-b参数。可以把多次启动的日志拼到一个文件里会方便很多。统计最近十次启动的开机耗时利用日志时间戳Logs begin at ...与systemd[577]: Startup finished in ...附近计算均值、中位数与最大值。在 macOS 上则用log show查找 system boot:与Previous shutdown cause: 5附近的时间戳。统计/usr/share/dict/words中至少包含三个a且不以s结尾的单词数量这些单词最常见的三个结尾双字母组合分别是什么sed的y命令或tr程序有助于处理大小写不敏感这样的双字母组合一共有多少种挑战题哪些组合没有出现从网上找一份公开数据集例如维基百科统计数据、FBI 犯罪率表等用curl抓取抽出两列数值数据。抓 HTML 可以用pup这类工具抓 JSON 可以用jq。用一条命令求出某一列的最小值与最大值再用另一条命令求出两列之和的差值。关键要点回顾数据整理 知道工具 组合工具管道|是这一切的黏合剂grep做粗过滤sed用正则做行级变换awk用字段与模式做结构化处理sort/uniq做排序与统计paste/bc/R/gnuplot做格式化与数值分析xargs把整理结果变成批量命令正则的贪婪匹配、捕获组、-E扩展语法、-v反向匹配是高频必会点同一套正则思维贯穿sed、Vim、awk等工具学会一个有助于掌握另一个相关课程资料见 _2019/data-wrangling.md 与 _2020/data-wrangling.md构建本仓库站点的完整方法见 README.md。赞分享文档教程【免费下载链接】missing-semesterThe Missing Semester of Your CS Education 项目地址https://gitcode.com/gh_mirrors/mi/missing-semester点击查看免费下载相关推荐Missing Semester 数据整理实战用 sed、正则与管道把日志加工成可用数据Missing Semester 数据整理实战用 sed、正则与管道把日志加工成可用数据 本篇指南源自《计算机教育中缺失的一课》Missing SemestUI组件前端The Missing Semester 中文版 · 数据整理Data Wrangling用命令行管道把日志变成答案The Missing Semester 中文版 · 数据整理Data Wrangling用命令行管道把日志变成答案 数据整理Data Wranglin文档教程教育Obsidian Style Settings高级用法从零开始编写自定义样式配置Obsidian Style Settings高级用法从零开始编写自定义样式配置 Obsidian Style Settings是一款强大的Obsidian插前端上一篇5分钟快速上手pay-java-parent支付集成完整指南下一篇CANN/ops-nn调制梯度算子创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考