程序员,你真的会用 Unix 命令?

(给Linux爱好者加星标,提升Linux技能

本文转自公众号:CSDN,ID:CSDNnews

英文:Vegard Stikbakke  ,译者 | 弯月,责编 | 屠敏

那些常用的 Unix 命令,你不知道的功能!

以下为译文:

我逐渐意识到,Unix 的命令行工具可以解决一切与文字处理有关的问题。我来讲一个我遇到过的问题,以及怎样用 Unix 命令行工具解决的。


问题

我在做我的硕士论文研究。像许多统计学家一样,我需要进行许多模拟。首先我利用一些数字的种子(以保证可复现性)来模拟了一些数据,然后在这些数据上用一个算法来估算一些东西。每次运行模拟时我都会创建一些文件,大概像下面这样:

dataset-directory/0001_data.csv
dataset-directory/0001_A.csv

但有时候运行会失败。这并不是太严重,模拟失败了我只需要运行另一个模拟就好了。比如,0001 这个数据我成功地运行了算法 A。因此我想在 0001 数据上使用算法 B。但我真正需要的是跟踪哪些命令失败了。

在许多数据上运行算法 A 之后,我得到了一大堆文件,像这样:

dataset-directory/0001_data.csv
dataset-directory/0001_A.csv
dataset-directory/0002_data.csv
dataset-directory/0002_A.csv
dataset-directory/0003_data.csv
dataset-directory/0003_A.csv
dataset-directory/0004_data.csv
dataset-directory/0005_data.csv
dataset-directory/0005_A.csv
dataset-directory/0006_data.csv
dataset-directory/0006_A.csv
dataset-directory/0007_data.csv
dataset-directory/0007_A.csv
dataset-directory/0008_data.csv
dataset-directory/0009_data.csv
dataset-directory/0009_A.csv
...
dataset-directory/0499_A.csv

通过观察得知算法 A 在数据 0004 和 0008 上的结果不存在。那么,怎样才能列出所有 A 没有成功的数据呢?

我当然可以手工来做,但那样很容易出错,而且很麻烦。要是能写程序完成就最好了!


解决方案

简单地来说,那些没有成功的就是从0001到0500的数字中去掉那些成功了的数字。而得到数字列表的最好命令就是 seq:

$> seq 10
1
2
3
4
5
6
7
8
9
10

(如果只给出一个数字,那么默认序列从1开始。当然,seq 2 10也会按照你期待的那样工作。)

现在,如果我能获得成功运行的列表,就可以通过 seq 命令生成所有可能的数字,再交叉检查两个列表来获得想要的结果了!

许多命令行工具只完成一项工作。例如,cut 可以获得每行指定位置上的字符:

$> cat text
Lorem ipsum
dolor sit amet
$> cat text | cut -c 2-5
orem
olor

注意这里使用了所谓的“管道运算符” | 。我说过,许多工具只做一件事,而且把那件事做到最好。很方便的是这些工具可以组合使用。使用管道,左边命令的结果就会变成右边命令的输入。注意这些命令会将输入当做一系列行来处理,通常来说这非常方便。

我们可以把成功的文件名单列表利用管道输入至 grep 命令,该命令可以使用正则表达式来查找。由于所有文件都以同样长度的四个数字开始,我们可以利用正则表达式 dddd 来匹配这四个连续的数字,然后将文件末尾的 A 算法加到正则表达式中。要获得文件列表,每行一个文件,只需执行 ls。(尽管直接调用 ls 时并不会显示成每行一个文件,但放在管道中时输出就会变成这样。)

$> ls dataset-directory | grep 'dddd_A.csv'
0009_A.csv
0001_A.csv
0002_A.csv
0005_A.csv
0007_A.csv
0003_A.csv
0006_A.csv
...

出于某些原因,在使用 grep 之后这里的顺序混乱了。我们可以使用 sort 命令来改正。而且由于我们只关心数字,可以使用 cut -c 1-4 来取出数字部分。

$> ls dataset-directory | grep 'dddd_A.csv' | sort | cut -c 1-4
0001
0002
0003
0005
0006
0007
0009
...
0499

这些数字跟 seq 命令的结果不太一样,因为它们前面补了零。我们可以使用一段 Python 脚本将它们转成整数。

# parse.py
import sys

for line in sys.stdin:
    i = int(line)
    print(i)

现在将结果用管道导入这个脚本就能得到我们需要的数字:

$> ls dataset-directory | grep 'dddd_A.csv' | cut -c 1-4 | python3 parse.py
1
2
3
5
6
7
9
...
499

接近目标了!现在需要找出怎样才能交叉检查这两个数字的列表。幸运的是,有个命令叫做comm,它能检查两个输入流中的公共字符。为了将上面这个命令的输出作为输入使用,我们可以将其求值后重定向,只需使用<(...)即可实现:

$> comm <(ls dataset-directory | grep 'dddd_A.csv' | cut -c 1-4 | python3 parse.py) <(seq 500)
        1
        2
        3
    4
        5
        6
        7
    8
        9
    10
...
    500

这个输出不是太好理解。阅读 comm 的手册(运行 man comm)可以发现,comm 会“产生三列的输出,分别是:只存在于 file1 的行,只存在于 file2 的行,以及存在于两个文件的行”。要去掉第一列(其实它是空的,因为没有任何数字只存在于文件列表中),可以在调用 comm 时加入参数-1。我们也不关心同时存在于两个输入中的数字,因此还要传递标志-3。

$> comm -1 -3 <(ls dataset-directory | grep 'dddd_A.csv' | cut -c 1-4 | python3 parse.py) <(seq 500)
4
8
...
500

搞定!


网友说

这个方法发布之后,在 Hacker News 上引发无数网友参与讨论:

https://news.ycombinator.com/item?id=19160659

评论 1:

Unix CLI 工具最有意思的地方在于有数百万种有效的方法来解决问题。我还想到了我自己的“更好”的解决方案,跟这里说过的大多数方法完全不同的方法。但这不是重点。

这篇文章的优点在于它遵循逐步解决问题的过程。我发现很多与我一起工作的程序员都在努力解决 CLI 问题,我觉得这有点令人惊讶。但我认为这完全取决于你如何看待这样的问题。

如果你从“我如何构建一个函数来操作这个原始数据?”或“哪种数据结构最能表达这些文件名之间的关系?”那么肯定会遇到困难。但是,如果你考虑“如何改变这些数据以消除无关的细节?”和“我有什么工具可以解决数据上的问题,如果有一些麻烦,我怎么能解决这些麻烦?”并且如果能接受采用逐步的方式,而不是构建和操纵整个抽象逻辑结构,那么你就可以有效地利用这些工具在几分钟之内解决这些问题,无需花费几小时。

评论 2:

这篇文章的问题是:“cut”命令后的参数并没有什么标准格式。

更糟糕的是,看似有标准格式,其实被许多其他格式破坏了。通常,这些格式取决于最初的作者,以及当时人们流行的想法。所以,有时候标志会“聚集”在一起,同时假设多字符的标志会使用两个横线作为开始。后来一些程序开始用一个横线来表示多字符的标志。还有许多其他的不一致。如果我学习了cut的基本范围语法,我能知道 imagemagick 的基本语法吗?

这些不一致在技术上并不冲突,因为每个只存在于特定的背景下。但是,如果看到那些不一致的东西被放在管道两侧,特别是当其中一个是非标准用法时,理智上真的很痛苦。(或者,即使它只是你需要的一个命令,但是你使用了错误的标志语法。)这一切都增加了认知负荷。

哦,从 Bash 转到 Python 语言是一个巨大的风险。如果你每天使用 Python 编写脚本,那可能还问题不大。但对于读者来说,语言边界是巨大的。因为用户不仅需要处理运行时错误和格式错误,而且还需要处理语言错误。如果命令行跳出一个语法错误或异常,我相信大多数读者就不会再读下去了。

英文:http://vegardstikbakke.com/unix/

本文转自公众号“CSDN”,ID:CSDNnews


推荐阅读

(点击标题可跳转阅读)

一文看懂 Unix 的版权史

Unix vs. Linux:有什么不同?

那些被岁月遗忘的 UNIX 经典著作


看完本文有收获?请分享给更多人

关注「Linux 爱好者」加星标,提升Linux技能

好文章,我在看❤️