Linux 过滤器 - 如何像老板一样精简文本
您是否熟悉 Unix 哲学以及如何创建更优秀的脚本?在本指南中,我们将探索 Unix 哲学的一般定义,探究优秀脚本的关键要素,并学习脚本的构建模块,例如管道运算符、stdin 和 stdout 操作。最后,我们将剖析如何在 Ruby/bash 脚本中将这些最佳实践付诸实践!
目录
Unix 哲学是什么?
Unix 哲学最初由大师Ken Thompson定义,它是一套定义极简主义和模块化软件的良好实践,Unix 的所有核心实用程序(例如find
,grep
)都遵循这种良好实践,因此我们可以同意它需要很好,对吗?
Doug Mcllroy记录的原始引文包含以下内容:
- 让每个程序只做好一件事。要完成一项新任务,最好重新构建程序,而不是通过添加新的“功能”来使旧程序变得复杂。
- 每个程序的输出都应该成为另一个未知程序的输入。不要用无关信息扰乱输出。避免使用严格的列式或二进制输入格式。不要坚持交互式输入。
- 设计和构建软件,甚至是操作系统,都应尽早进行测试,最好在几周内完成。不要犹豫,丢弃那些不完善的部分,重新构建它们。
- 使用工具而不是非技术性的帮助来减轻编程任务,即使您必须绕道来构建工具并准备在使用完它们之后将其中一些扔掉。
最后两条定义了一些更“通用”的编程技巧,本文的目标将完全集中在前两条技巧上,其中,好的脚本可以很好地完成一项工作,并且可以放入管道中。在接下来的章节中,我们将尝试更好地理解这句话。
什么是管道?
管道是程序的连续运行,其中一个程序的标准输出作为下一个程序的标准输入,在 bash/zsh/fish shell 上,我们可以使用|
运算符来引用管道,并且我们经常使用它来进一步查询信息,例如:假设您想计算目录中有多少个 markdown 文件,您可以执行以下操作:
find . -iname '*.md' | wc -l
让我们逐一分解一下,以便您能够正确理解,好吗?
首先,我们find
使用命令列出当前目录中的所有 markdown 文件,就我个人而言,这会产生以下内容:
$ find . -iname '*.md'
./posts/20230814T124722/README.md
./posts/20230703T214043/README.md
./posts/20230616T234323/README.md
./posts/20230625T223158/README.md
./posts/20230731T212528/README.md
./posts/20230807T203924/README.md
./posts/20230804T140043/README.md
./REPL Driven Development - For not so smart developers.md
./Como escrever uma CLI CRUD utilizando ScyllaDB + Ruby.md
./Linux filters - How to streamline text like a boss.md
如果您只想列出正确的文件名而不计算它们,您甚至可以将其通过管道传输到sort
命令,这样它就会按字母顺序列出,如下所示:
$ find . -iname '*.md' | sort
./Como escrever uma CLI CRUD utilizando ScyllaDB + Ruby.md
./Linux filters - How to streamline text like a boss.md
./REPL Driven Development - For not so smart developers.md
./posts/20230616T234323/README.md
./posts/20230625T223158/README.md
./posts/20230703T214043/README.md
./posts/20230731T212528/README.md
./posts/20230804T140043/README.md
./posts/20230807T203924/README.md
./posts/20230814T124722/README.md
看到第一个命令的输出find
是如何精简为sort
正确排序后返回结果的命令了吗?这就是编写小工具的全部目的,这样你就可以轻松地将它们组合到不同的管道中,以获得很酷的结果。
在最初的示例中,我们使用了wc
仅统计从标准输入接收的数据的命令,而在本例中,我们使用-l
标志位来统计行数。所有这些组合在一起,瞧!我们得到了结果:
$ find . -iname '*.md' | wc -l
10
什么是 stdin 和 stdout?
Stdin(标准输入)和 stdout(标准输出)是计算机与外界通信的主要方式。下面,我们将深入探讨每个方式的细节:
- Stdin(标准输入)通常指等待用户交互的任何事物(例如在输入框中键入内容、从列表中选择项目等),但这可以以更广义的方式来理解;基本上,我们可以将 Stdin 视为默认信息提供者,无论它来自另一个程序还是来自与其交互的用户。
- Stdout(标准输出)通常指任何将值打印到屏幕上的东西(可以是终端、浏览器或其他任何东西)。与 Stdin 不同,这种说法完全正确,唯一需要注意的是,当插入到管道上下文中时,所有 stdout 都会被抑制为下一个命令的标准输入,而不是打印到屏幕上。
什么定义了糟糕的剧本以及如何将其转变为好的剧本?
由于涉及诸多变量,准确概括什么是好的脚本可能相当棘手。不如让我们反过来问:根据 Linux 哲学,糟糕的脚本是什么样的?我们该如何解决具体问题,从而创建更好的工具?
忽略标准输入和输出通信
您是否曾经使用过一些 CLI,它们以动画输入或炫酷的旋转器来巧妙地显示信息?当您只想单独使用这些工具时,它们确实很酷,但当您尝试将它们放入管道中时,它们会破坏整个流程。相反,编写工具时,请始终使用标准通信作为接收和检索信息的主要方式,并优先使用选项而不是硬编码值(选项的示例是--output=json
或-o json
)。
在 Ruby 中,避免搬起石头砸自己的脚要容易得多,因为我们学习获取数据的方式已经与标准输入配合得很好了,但关注脚本的设计方式很重要。
例如,考虑这个名为printname.rb
example 的简单脚本:
#!/usr/bin/env ruby
puts 'Type your name: '
name = gets.chomp
puts "Your name is: #{name}"
乍一看,该脚本既可以单独运行,也可以在管道上运行,但请观察两者产生的输出:
$ echo "Cherry Ramatis" | ./printname.rb
Type your name:
Your name is: Cherry Ramatis
$ ./printname.rb
Type your name:
Cherry Ramatis
Your name is: Cherry Ramatis
您可以观察到我们一直在打印Type your name
消息,在这种情况下我们有两个选择:
- 通过接收标志参数来接受此名称
--name="Cherry Ramatis"
- 删除
Type your name
消息,将脚本转变为更易于流水线化的脚本(好词吧?)。
在同一个工具上做很多事情,也就是单片脚本
有时,你在编写一个工具时,很快发现它的范围在不断扩大,原本应该很小的工具,却发展成为一个包含各种交互步骤的大型项目。当它发展到这个地步时,通过添加大量子命令来不断迭代同一个工具确实很诱人,但 Unix 的哲学帮助我们理解,编写一个只做正确一件事的工具比编写一个庞然大物更有价值。
因此,不要创建包含一整套命令的大型 CLI,而是尝试编写相互连接、相互补充的小型独立工具,例如:
想象一下,您想要编写一个命令,按特定顺序列出数据库中的所有歌曲并提示用户选择一首,而不是将所有这些功能都写在一个命令中,而是可以使用不同的命令来编写,例如:
$ list_songs | sort | update_song
看看不同的脚本是如何list_songs
工作update_song
的吗?它们都通过 STDIN 和 STDOUT 进行通信,允许用户通过任何命令进行管道传输并接收相同的行为(例如,我们通过管道传输它以进行排序,因为我们希望以排序的方式查看列表。)
加分点:vim 中的 bang 运算符
Vim 用户们,欢呼吧!这是我们大放异彩的时刻。Vim 是 Unix 系统中一个相当标准的编辑器,它带来了一系列好处,比如可以通过 运算符直接在缓冲区中与二进制文件交互!
。
在 vim 中,你可以!!
在正常模式下按下 ,在迷你缓冲区中填充类似这样的命令:.!
,你在 之后输入的命令将!
与当前行一起用作 STDIN,是不是很神奇?请看下面的例子:
考虑以下 Ruby 脚本:
#!/usr/bin/env ruby
STDIN.each do |line|
puts "- #{line}"
end
在这个脚本中,我们循环遍历接收到的 STDIN 并将其打印回 STDOUT,-
开头为,现在我们可以与 vim 交互:
bang 命令的其他变体:
!}
:从当前行到段落末尾填充 bang 命令。!/pattern
:将 bang 命令从当前行填充到pattern
缓冲区中第一个找到的位置。!G
:从当前行填充bang命令到文件末尾。!4j
:将bang命令从当前行填充到下面4行。
结论
这篇文章篇幅不长,我试图更深入地阐述我日常生活中经常做的事情:自动化。希望这篇文章对你有用,如果我能帮上什么忙,尽管联系我!愿原力与你同在🍒
文章来源:https://dev.to/cherryramatis/linux-filters-how-to-streamline-text-like-a-boss-2dp4