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

描述

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

问题

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

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

 

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

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

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

 

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

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

解决方案

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

$> seq 1012345678910

 

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

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

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

$> cat textLorem ipsumdolor sit amet$> cat text | cut -c 2-5oremolor

 

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

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

$> ls dataset-directory | grep 'dddd_A.csv'0009_A.csv0001_A.csv0002_A.csv0005_A.csv0007_A.csv0003_A.csv0006_A.csv...

 

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

$> ls dataset-directory | grep 'dddd_A.csv' | sort | cut -c 1-40001000200030005000600070009...0499

 

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

# parse.pyimport sysfor line in sys.stdin:    i = int(line)    print(i)

 

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

$> ls dataset-directory | grep 'dddd_A.csv' | cut -c 1-4 | python3 parse.py1235679...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)48...500

 

搞定!

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分