有时候删除文件里的重复行是一个很常见的需求,这个用 shell 命令有很多处理方法。

第一种方案是用 sort 命令的 -u 参数:

$ sort -u input.txt > output.txt

第二种方案是用 awk 命令,它的关键在于用一个字典来保存记录:

$ awk '!seen[$0]++' input.txt > output.txt

这和第一种方案的区别在于,即使文件中重复行不连续,依然可以删除。

第三种方案是用 sed 命令,但是其实不大推荐,它相比第一种方案复杂多,而且很容易写错:

$ sort -n input.txt | sed '$!N;/^\(.*\)\n\1$/!P;D' > output.txt

这种方案里面用到了 sed 的高级命令(N、P、D),相关介绍可以参考之前写的文章 Sed and awk 笔记之 sed 篇:高级命令(一)

这里有一个小技巧,如果遇到比较复杂的 sed 命令拿捏不准的化,可以用 sedsed 这个脚本来调试执行,看每一步执行的情况。

使用方法也很简单,比如调试上面这个 sed 命令,命令执行结果会展示每一步执行后模式空间和保持空间的内容,一目了然:

$ sort -n input.txt | python sedsed.py -d '$!N;/^\(.*\)\n\1$/!P;D'

转载请注明转自: 团子的小窝 , 本文固定链接: 如何删除文件中的重复行

  1. kodango's avatar
    发表于 2019-05-06 13:22:34 回复 #1

    关于 sed 命令的解释补充在这里:

    1、sed读取的内容是保存到模式空间的

    2、sed的命令有小写和大写两套,小写处理的是单行内容,大写命令处理的是多行内容;
    2.1、比如n读取下一行到模式空间(覆盖原有内容),N是读取下一行并追加到模式空间,多行用 \n 拼接;
    2.2、N命令上面解释了,P命令是打印模式空间的第一行(小写p是打印整个模式空间,结合2理解下),D是删除模式空间的第一行并且如果此时模式空间不为空,不会导致读取新一行

    3、理解了上面几点后,就可以开始分析 sed '$!N;/^\(.*\)\n\1$/!P;D' 这个命令了
    3.1、$!N 如果不是文件的最后一行,则读取下一行追加到模式空间
    3.2、/^\(.*\)\n\1$/!P 如果模式空间的内容(文件连续的两行)不是重复的,则输出第一行(反过来看,意味着如果是重复的就不输出了)
    3.3、D 删除模式空间的第一行,然后继续跳到 3.1 执行(结合 2.2 理解,D 命令此时不会导致读取新行)

    举个例子,假设输入的 文件内容如下:

    bugfix
    develop
    master
    release
    release
    test

    1、读取 bugfix 到模式空间(这一次的读取是 sed 默认的读取,一行一行的处理,后面的读取都是 $!N 命令执行的结果)
    2、bugfix 不是最后一行,读取下一行 develop 并追加到模式空间,此时内容为“bugfix\ndevelop“
    3、不匹配 /^\(.*\)\n\1$/,打印模式空间第一行 bugfix,并且删除第一行,此时模式空间内容为 ”develop“
    4、develop 不是最后一行,读取下一行 master 并追加到模式空间,此时内容为”develop\nmaster“
    5、不匹配 /^\(.*\)\n\1$/,打印模式空间第一行 develop,并且删除第一行,此时模式空间内容为 ”master“
    6、master 不是最后一行,读取下一行 release 并追加到模式空间,此时内容为” master\nrelease“
    7、不匹配 /^\(.*\)\n\1$/,打印模式空间第一行 master,并且删除第一行,此时模式空间内容为 ”release“
    8、release 不是最后一行,读取下一行 release 并追加到模式空间,此时内容为” release\nrelease“
    9、匹配 /^\(.*\)\n\1$/,不打印,并且删除第一行,此时模式空间内容为 ”release“
    10、release 不是最后一行,读取下一行 test 并追加到模式空间,此时内容为” release\ntest“
    11、不匹配 /^\(.*\)\n\1$/,打印模式空间第一行 release,并且删除第一行,此时模式空间内容为 ”test“
    12、test 是最后一行,不再读取新内容,此时模式空间内容为 ”test“
    13、不匹配 /^\(.*\)\n\1$/,打印模式空间第一行 test,并且删除第一行,此时模式空间内容为空
    14、所有行处理完毕,命令结束

    最后输出内容为:
    bugfix
    develop
    master
    release
    test