美文网首页
Linux中的文件内查找小技巧

Linux中的文件内查找小技巧

作者: 鹿无为 | 来源:发表于2020-02-16 14:21 被阅读0次

    写在前面的废话

    这一周我为自己的马虎付出了严重的代价!!!
    每天十几个小时一直在debug……以后写代码之前一定要拜拜雍正,专治八阿哥(bug)


    image.png

    虽然debug让我无心其他事情,但是咱说好的周更不能断(其实是我找到了bug,想写篇文章压压惊)

    太长不看系列

    • 最粗鲁的办法:grep -f (慢,内存需求太大)
    • 比较省事的办法:R的merge函数 (较快,但是内存需求贼大)
    • 比较机智的办法:字典/哈希(很快,Python里面叫作字典,Perl里面似乎叫作哈希,对于不会这两门语言的同学友好性太差)
    • 稳如老狗的办法:awk命令 (也是依赖字典方法,速度快,操作简单,深得吾心)


      image.png

    废话超多系列

    我相信你们和我一样,有时需要知道文件A中有哪些行出现在文件B中。说起来有点绕,直接举个例子可能会更通俗易懂一些:

    > cat A.txt
    Hello
    Thank you
    Thank you very much
    > cat B.txt
    Hello
    Welcome to
    Beijing
    

    文件A中有哪些行在文件B中也出现过,这个例子比较简单,我们直接就能看出A.txt中的Hello在B.txt文件中出现。

    但是当文件较大时怎么办呢?一百行的话,你也许还可以试试 肉眼去观察,一旦行数上万,就必须借助计算机帮助我们完成这些事情了

    不瞒你说,我之前查找的两个文件,一个几百万行,另一个上千万行,为了找一个合适的方法可是苦恼了很久

    最粗鲁的办法:grep

    grep方法比较粗鲁,但针对比较小的文件,我还是很喜欢用它的。因为它只需要敲六个字符,真的节省体力。
    cat -f A.txt B.txt : 可以把B文件中存在于A文件中的行输出,具体操作如下

    image.png

    如果你使用了-v参数,则可以进行另一个骚操作,输出只存在与B文件而不存在与A文件中的行。但是这个方法有一些缺点,比如:

    • 不能分字段查找(比如A文件的第二列信息,是否在B文件中的第三列信息中出现)
    • 针对大文件,耗时长且内存消耗大(时间我没有专门统计过,但是在运行这个命令的时候内存倒是溢出过,如下图)


      image.png

    比较省事的办法:R的merge函数

    做生信的同学,你可以不会Python,你可以不会Perl,但是R你总要会一点吧……

    当我们在R中合并文件时,我们经常会用到merge()函数,它可以根据两文件中指定的列进行合并,对文件取交集。换个角度想想,这个取交集的操作不就是找把B文件中存在于A文件中的行么。

    这里,因为篇幅问题,我就不介绍merge函数是如何使用的了。这个函数的原理我自己也不是很懂,只是知道是它用空间换时间,速度很快,但是内存消耗是真的大

    下面是我对两个几百兆的文件merge时的报错信息,可以看到内存溢出的那是相当严重


    image.png

    比较机智的办法:字典/哈希

    如果你学过Python,一定知道dict()字典。这个方法是真的快,就和查字典一样,不存在遍历的问题。这里推荐阅读廖雪峰老师关于字典的介绍,我自己的语言功底很难三两句话把这个名词解释清楚。

    Python里面叫作字典,Perl里面似乎叫作哈希

    但是该方法对于不会这两门语言的同学友好型太差,我只是想比对个文件,你却让我从头开始学一门编程语言???(内心:你这是想要我死????)


    image.png

    稳如老狗的办法:awk命令

    铺垫了这么多,终于可以扯到重点上了。awk/sed/grep可以说是shell中处理数据的三剑客,我们大部分的数据清洗问题,都可以使用这三个方法解决。

    听起来这么牛,但是我们大部分人只会其基本操作,稍微复杂一点就会触碰到我们的知识盲区……


    image.png

    没关系,饭要一口一口吃,毕竟肥肉不是一天长成的,头发也不是一天就掉光的……

    这里说个实话,希望不会挨打

    这里简单介绍一下awk的一些参数,只介绍稍后用到的参数,想了解更多,可以自行搜索学习

    • FNR:各文件分别计数的行号。当awk命令后面跟了不止一个文件时,每读入一个新文件,行号就要从头开始计算。
    • NR:已经读出的记录数,就是行号,从1开始,不断累加,读入新文件也不会从头开始计算

      比如 awk命令之后跟了两个文件,分别有3行和5行,那么FNR的值依次时1,2,3,1,2,3,4,5;而NR的值则是1,2,3,4,5,6,7,8

    • $0表示读取的文件某一行的所有内容,$1表示某一行的第一列,$2表示某一行的第二列,以此类推
    • '[]':数组,比如a[$0]

    介绍了这么多,我应该如何使用这些参数呢?

    awk 'NR==FNR {a[$0]} NR>FNR&&!($0 in a){print $0}' A.txt B.txt
    

    这里的NR==FNR,表示当前读取的是第一个文件A.txt的内容,这个时候创建一个数组,将第一个文件的每一行内容都作为一个key值输入,如果不存在这个a[$0]变量,则创建一个。通过这个方法,我们可以把文件A.txt的所有内容逐行放入数组变量a中。

    接着NR>FNR表示,当前读取的不是第一个文件(即开始读取B.txt文件)。!($0 in a)表示当前读入的行,不存在之前的a数组中(即:B.txt文件中不存在于A.txt文件中的行)。中间的&&表示前后两个条件都要满足,若满足则执行print $0命令。

    最终输出的结果将是只存在于B文件中,而不存在于A文件中的行。除此之外,我们可以指定比较两个文件中的特定列,比如:

    awk 'NR==FNR {a[$3]} NR>FNR&&!($2 in a){print $0}' A.txt B.txt
    

    以上命令表示,我想找出只存在于B文件中第二列,而不存在于A文件中第三列的字段,若存在这样的字段,则将B文件中该字段所在的行打印输出。

    这个方法,速度快,容易上手,基本上你会用Linux就可以。既然提到了awk命令,那就再说一个骚操作:文件去重!!!

    好吧,我知道一旦说去重,你首先想到的一定是以下两条命令:

    1. sort A.txt | uniq
    2. sort -u A.txt

    这个方法固然是好,但是sort命令排序十分耗时。处理小文件时我们可能体会不到,但是一旦遇到上百万行的文件,光是等待时间就够我们喝一壶茶了。这个时候awk命令就体现出其重要性了。命令如下:

    awk '!a[$0]++' A.txt
    

    这里不讲太多,简单说一下这个命令执行的步骤:

    1. 首先执行a[$0],将输入文件的一整行当作数组a的key值
    2. 接着执行!a[$0],对a[$0]的值取反,如果a[0]返回值为0,则整个表达式`!a[0]`为1,执行awk的默认操作,打印输出这一行
    3. 最后是!a[$0]++,对a[$0]的值加1,因此当之后再遇到相同行时,!a[$0]会被看作是0,不hi行默认的打印操作

    也许你觉得我讲的还是不够清楚,那么这里给你推荐一个问答,里面的第一个回答(最高赞)将这个去重的逻辑讲的十分清楚。

    一点题外话

    大部分做生信的人,对于算法都是一知半解,只求解决问题不求代码优美。但写代码,我们毕竟不是专业的。我们的终极目的是解决生物学问题,而不是过多的纠结代码的优美性。

    代码写的再好看,解决不了问题,依然发不了sci

    另外代码不要晚上写,一个原因是晚上写代码容易出bug(这是一个涉及玄学的话题),另外一个原因就是熬夜令人头秃。

    最近头有点凉,也不知道是天气冷了,还是头发秃了

    image.png

    相关文章

      网友评论

          本文标题:Linux中的文件内查找小技巧

          本文链接:https://www.haomeiwen.com/subject/dtramctx.html