【问题标题】:How to delete duplicate lines in a file without sorting it in Unix如何删除文件中的重复行而不在 Unix 中对其进行排序
【发布时间】:2010-11-29 11:41:18
【问题描述】:

有没有办法在 Unix 中删除文件中的重复行?

我可以使用sort -uuniq 命令来做到这一点,但我想使用sedawk

这可能吗?

【问题讨论】:

  • 如果您的意思是连续重复,那么仅uniq 就足够了。
  • 否则,我相信awk 是可能的,但在更大的文件上会非常消耗资源。
  • 重复的stackoverflow.com/q/24324350stackoverflow.com/q/11532157 有有趣的答案,理想情况下应该在这里迁移。

标签: unix shell scripting sed awk


【解决方案1】:
awk '!seen[$0]++' file.txt

seen 是一个关联数组,AWK 会将文件的每一行传递给它。如果数组中没有一行,则seen[$0] 将评估为假。 ! 是逻辑 NOT 运算符,会将 false 反转为 true。 AWK 将打印表达式计算结果为 true 的行。

++ 递增 seen 以便在第一次找到一行之后 seen[$0] == 1 然后是 seen[$0] == 2,依此类推。 AWK 将除 0""(空字符串)之外的所有内容评估为 true。如果在seen 中放置了重复行,则!seen[$0] 将评估为假,并且该行不会被写入输出。

【讨论】:

  • 要将其保存在文件中,我们可以这样做awk '!seen[$0]++' merge_all.txt > output.txt
  • 这里有一个重要的警告:如果您需要对多个文件执行此操作,并且您在命令末尾添加更多文件,或者使用通配符...... 'seen' 数组将充满重复所有文件中的行。如果您想独立处理每个文件,则需要执行for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done 之类的操作
  • @NickK9 在多个文件中累积重复数据删除本身就很棒。不错的提示
  • 这也归功于'++'运算符的结果不是递增后的值,而是前一个值。
【解决方案2】:

来自http://sed.sourceforge.net/sed1line.txt: (请不要问我这是如何工作的;-))

 # delete duplicate, consecutive lines from a file (emulates "uniq").
 # First line in a set of duplicate lines is kept, rest are deleted.
 sed '$!N; /^\(.*\)\n\1$/!P; D'

 # delete duplicate, nonconsecutive lines from a file. Beware not to
 # overflow the buffer size of the hold space, or else use GNU sed.
 sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'

【讨论】:

  • geekery;-) +1,但资源消耗在所难免。
  • '$!N; /^(.*)\n\1$/!P; D'的意思是“如果你不在最后一行,读另一行。现在看看你有什么,如果它不是东西,然后是换行符,然后又是同样的东西,打印出这些东西。现在删除东西(直到换行符)。”
  • 'G; s/\n/&&/; /^([ -~]*\n).*\n\1/d; s/\n//; H; P' 大致意思是“将整个保留空间附加到这一行,然后如果你看到重复的行将整个内容扔掉,否则将整个混乱复制回保留空间并打印第一部分(这是你刚刚的行阅读。”
  • $! 部分是否必要? sed 'N; /^\(.*\)\n\1$/!P; D' 不做同样的事情吗?我想不出一个在我的机器上两者不同的例子(我确实在最后一个空行尝试了两个版本,它们都很好)。
  • 差不多 7 年后,没有人回答 @amichair ... 让我很难过。 ;) 不管怎样,[ -~] 表示从 0x20(空格)到 0x7E(波浪号)的 ASCII 字符范围。这些被认为是the printable ASCII characters(链接页面也有 0x7F/delete,但这似乎不对)。这使得任何不使用 ASCII 或使用制表符的人的解决方案都被打破了。更便携的[^\n] 包含更多的字符......事实上,除了一个之外,所有这些字符。
【解决方案3】:

类似于jonas's AWK solution的Perl单行:

perl -ne 'print if ! $x{$_}++' file

此变体在比较之前删除尾随空格:

perl -lne 's/\s*$//; print if ! $x{$_}++' file

这个变体就地编辑文件:

perl -i -ne 'print if ! $x{$_}++' file

此变体在原地编辑文件,并备份file.bak

perl -i.bak -ne 'print if ! $x{$_}++' file

【讨论】:

  • 如何将 otuput 重定向到标准输出?管道不适用于这种方法。
  • 我的原始答案输出到标准输出,以及第一个变体
【解决方案4】:

使用 Vim 的另一种方式(与 Vi 兼容)

从文件中删除重复的连续行:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

从文件中删除重复的、不连续的和非空的行:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

【讨论】:

    【解决方案5】:

    Andre Miller posted 在输入文件以空行和无字符结尾时,除了最新版本的 sed 之外,可以使用单行。在我的 Mac 上,我的 CPU 只是旋转。

    如果最后一行是空白且没有任何字符,则这是一个无限循环:

    sed '$!N; /^\(.*\)\n\1$/!P; D'

    它没有挂起,但是你丢失了最后一行:

    sed '$d;N; /^\(.*\)\n\1$/!P; D'

    解释在sed FAQ的最后:

    GNU sed 维护者认为,尽管存在可移植性问题
    这会导致,将 N 命令更改为打印(而不是
    删除)模式空间更符合直觉
    关于“追加下一行”的命令应该如何表现。
    另一个有利于改变的事实是“{N;command;}”将
    如果文件有奇数行,则删除最后一行,但是
    如果文件有偶数行,则打印最后一行。

    转换使用前 N 行为的脚本(删除
    到达 EOF 时的模式空间)到与
    兼容的脚本 所有版本的 sed,改变一个单独的“N;”到“$d;N;”

    【讨论】:

      【解决方案6】:

      第一个解也是来自http://sed.sourceforge.net/sed1line.txt

      $ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
      1
      2
      3
      4
      5
      

      核心思想是:

      每个重复的连续行在其最后出现时打印一次,并使用D命令实现循环

      解释:

      1. $!N;:如果当前行不是最后一行,使用N命令将下一行读入pattern space
      2. /^(.*)\n\1$/!P:如果当前pattern space的内容是两个重复的字符串,用\n隔开,表示下一行是相同 有了当前行,我们可以按照我们的核心思想打印它;否则,这意味着当前行是其所有重复连续行的 last 外观。我们现在可以使用P 命令打印当前模式空间 中的字符,直到\n\n 也被打印)。
      3. D:我们使用D命令删除当前pattern space中的字符直到\n\n也被删除),然后pattern space 是下一行。
      4. D 命令将强制sed 跳转到其第一个 命令$!N,但从文件或标准输入读取下一行流。

      第二种解决方案很容易理解(来自我自己):

      $ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
      1
      2
      3
      4
      5
      

      核心思想是:

      在每个重复的连续行中第一次出现打印一次,并使用: 命令和t 命令来实现循环。

      解释:

      1. 从输入流或文件中读取一个新行并打印一次。
      2. 使用:loop 命令设置一个名为loop标签
      3. 使用N 将下一行读入模式空间
      4. 如果下一行与当前行相同,则使用s/^(.*)\n\1$/\1/ 删除当前行。我们使用s 命令来执行删除操作。
      5. 如果s命令执行成功,则使用tloop命令强制sed跳转到名为loop标签 >,它将对下一行执行相同的循环,直到 最新打印的行没有重复的连续行;否则,使用D 命令到delete最新打印行 相同的行,并强制sed 跳转到第一个命令,即p命令。当前模式空间的内容是下一个新行。

      【讨论】:

      • 在 Windows 上使用 busybox 的相同命令:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
      【解决方案7】:

      这可以使用 AWK 来实现。

      以下行将显示唯一值:

      awk file_name | uniq
      

      您可以将这些唯一值输出到新文件:

      awk file_name | uniq > uniq_file_name
      

      新文件 uniq_file_name 将只包含唯一值,没有任何重复。

      【讨论】:

      • 我认为 awk 在这里有点矫枉过正。
      • 这只会删除连续的重复项。
      【解决方案8】:

      uniq 会被尾随空格和制表符所欺骗。为了模仿人类进行比较的方式,我在比较之前修剪了所有尾随空格和制表符。

      我认为$!N; 需要花括号,否则它会继续,这就是无限循环的原因。

      我在Ubuntu 20.10(Groovy Gorilla)中有 Bash 5.0 和 sed 4.7。在字符集匹配时,第二个单行代码不起作用。

      共有三种变体。第一个是消除相邻的重复行,第二个是消除重复行,无论它们出现在哪里,第三个是消除文件中除最后一个实例之外的所有行。

      pastebin

      # First line in a set of duplicate lines is kept, rest are deleted.
      # Emulate human eyes on trailing spaces and tabs by trimming those.
      # Use after norepeat() to dedupe blank lines.
      
      dedupe() {
       sed -E '
        $!{
         N;
         s/[ \t]+$//;
         /^(.*)\n\1$/!P;
         D;
        }
       ';
      }
      
      # Delete duplicate, nonconsecutive lines from a file. Ignore blank
      # lines. Trailing spaces and tabs are trimmed to humanize comparisons
      # squeeze blank lines to one
      
      norepeat() {
       sed -n -E '
        s/[ \t]+$//;
        G;
        /^(\n){2,}/d;
        /^([^\n]+).*\n\1(\n|$)/d;
        h;
        P;
        ';
      }
      
      lastrepeat() {
       sed -n -E '
        s/[ \t]+$//;
        /^$/{
         H;
         d;
        };
        G;
        # delete previous repeated line if found
        s/^([^\n]+)(.*)(\n\1(\n.*|$))/\1\2\4/;
        # after searching for previous repeat, move tested last line to end
        s/^([^\n]+)(\n)(.*)/\3\2\1/;
        $!{
         h;
         d;
        };
        # squeeze blank lines to one
        s/(\n){3,}/\n\n/g;
        s/^\n//;
        p;
       ';
      }
      

      【讨论】:

        【解决方案9】:

        用途:

        cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'
        

        它使用 AWK 删除重复的行。

        【讨论】:

        • 这会打乱行的顺序。
        • 什么是大约 20 GB 的文本文件?太慢了。
        • 一如既往,the cat is useless. 无论如何,uniq 已经自己完成了这项工作,并且不需要每行输入恰好是一个单词。
        猜你喜欢
        • 2011-02-13
        • 1970-01-01
        • 1970-01-01
        • 2012-07-16
        • 2014-11-05
        • 2021-11-19
        • 1970-01-01
        相关资源
        最近更新 更多