删除部分重复的连续行,但保留第一行和最后一行

删除部分重复的连续行,但保留第一行和最后一行

我有带有时间戳的日志文件,每行有六个值,我想通过删除具有相同值的连续行(忽略时间戳)并保留每个重复集的第一行和最后一行来减少数据量。最好使用 bash 脚本。应该是魔法sed或者awk命令组合。

即使我必须多次解析文件,一次读取 3 行并删除中间行也是一个很好的解决方案。

原始文件:

1447790360      99999   99999   20.25   20.25   20.25   20.50
1447790362      20.25   20.25   20.25   20.25   20.25   20.50
1447790365      20.25   20.25   20.25   20.25   20.25   20.50
1447790368      20.25   20.25   20.25   20.25   20.25   20.50
1447790371      20.25   20.25   20.25   20.25   20.25   20.50
1447790374      20.25   20.25   20.25   20.25   20.25   20.50
1447790377      20.25   20.25   20.25   20.25   20.25   20.50
1447790380      20.25   20.25   20.25   20.25   20.25   20.50
1447790383      20.25   20.25   20.25   20.25   20.25   20.50
1447790386      20.25   20.25   20.25   20.25   20.25   20.50
1447790388      20.25   20.25   99999   99999   99999   99999
1447790389      99999   99999   20.25   20.25   20.25   20.50
1447790391      20.00   20.25   20.25   20.25   20.25   20.50
1447790394      20.25   20.25   20.25   20.25   20.25   20.50
1447790397      20.25   20.25   20.25   20.25   20.25   20.50
1447790400      20.25   20.25   20.25   20.25   20.25   20.50

期望的结果:

1447790360      99999   99999   20.25   20.25   20.25   20.50
1447790362      20.25   20.25   20.25   20.25   20.25   20.50
1447790386      20.25   20.25   20.25   20.25   20.25   20.50
1447790388      20.25   20.25   99999   99999   99999   99999
1447790389      99999   99999   20.25   20.25   20.25   20.50
1447790391      20.00   20.25   20.25   20.25   20.25   20.50
1447790394      20.25   20.25   20.25   20.25   20.25   20.50
1447790400      20.25   20.25   20.25   20.25   20.25   20.50

答案1

uniq 是(某种程度上)完美的工具,默认情况下,在 uniq 中,您可以保留/显示集合中的第一行但不是最后一行。

uniq 有一个 -f 标志,允许您跳过前几个字段。

来自 man uniq:

   -f, --skip-fields=N
          avoid comparing the first N fields

   -s, --skip-chars=N
          avoid comparing the first N characters

   A field is a run of blanks (usually spaces and/or TABs), then non-blank characters.  Fields are skipped before chars.

使用 uniq -c 的示例来显示计数,看看 uniq 正在做什么:

-bash-4.2$ uniq -c -f 1 original_file
  1 1447790360      99999   99999   20.25   20.25   20.25   20.50
  9 1447790362      20.25   20.25   20.25   20.25   20.25   20.50
  1 1447790388      20.25   20.25   99999   99999   99999   99999
  1 1447790389      99999   99999   20.25   20.25   20.25   20.50
  1 1447790391      20.00   20.25   20.25   20.25   20.25   20.50
  3 1447790394      20.25   20.25   20.25   20.25   20.25   20.50

不错。非常接近想要的。而且很容易做到。但缺少 group 中的最后一个匹配行。 。 。 。

uniq 中的分组选项对于这个问题也很有趣。 。 。

   --group[=METHOD]
          show all items, separating groups with an empty line METHOD={separate(default),prepend,append,both}

   -D, --all-repeated[=METHOD]
          print all duplicate lines groups can be delimited with an empty line METHOD={none(default),prepend,separate}

例如,uniq by group 。 。 。

    -bash-4.2$ uniq --group=both -f 1 original_file 

1447790360      99999   99999   20.25   20.25   20.25   20.50

1447790362      20.25   20.25   20.25   20.25   20.25   20.50
1447790365      20.25   20.25   20.25   20.25   20.25   20.50
1447790368      20.25   20.25   20.25   20.25   20.25   20.50
1447790371      20.25   20.25   20.25   20.25   20.25   20.50
1447790374      20.25   20.25   20.25   20.25   20.25   20.50
1447790377      20.25   20.25   20.25   20.25   20.25   20.50
1447790380      20.25   20.25   20.25   20.25   20.25   20.50
1447790383      20.25   20.25   20.25   20.25   20.25   20.50
1447790386      20.25   20.25   20.25   20.25   20.25   20.50

1447790388      20.25   20.25   99999   99999   99999   99999

1447790389      99999   99999   20.25   20.25   20.25   20.50

1447790391      20.00   20.25   20.25   20.25   20.25   20.50

1447790394      20.25   20.25   20.25   20.25   20.25   20.50
1447790397      20.25   20.25   20.25   20.25   20.25   20.50
1447790400      20.25   20.25   20.25   20.25   20.25   20.50

然后 grep 查找每个空行之前和之后的行并去除空行:

-bash-4.2$ uniq --group=both -f 1 original_file |grep -B1 -A1 ^$ |grep -Ev "^$|^--$"
1447790360      99999   99999   20.25   20.25   20.25   20.50
1447790362      20.25   20.25   20.25   20.25   20.25   20.50
1447790386      20.25   20.25   20.25   20.25   20.25   20.50
1447790388      20.25   20.25   99999   99999   99999   99999
1447790389      99999   99999   20.25   20.25   20.25   20.50
1447790391      20.00   20.25   20.25   20.25   20.25   20.50
1447790394      20.25   20.25   20.25   20.25   20.25   20.50
1447790400      20.25   20.25   20.25   20.25   20.25   20.50

哒哒哒!不错。

答案2

带有awk一个衬垫:

awk '{n=$2$3$4$5$6$7}l1!=n{if(p)print l0; print; p=0}l1==n{p=1}{l0=$0; l1=n}END{print}' file

重点是操作几个变量:n存储当前行中除第一个字段之外的所有字段,前一行和整个上一行l1相同。l0p只是一个标记,用于标记上一行是否已打印。

答案3

Perl 来救援:

perl -ne '($t, $r) = /([0-9]+\s+)(.*)/;
          print "$pt$p\n$_" if $r ne $p;
          $p = $r;
          $pt = $t;
          }{
          print $t, $r' input-file \
| sort -nu | tail -n+2
  • -n逐行读取输入。
  • $t是时间戳加上空格,$r是“其余”。
  • $p是之前的休息时间,$pt是之前的时间戳。
  • 最后一行总是被打印

Perl 将某些行打印两次,sort -nu应该删除重复项。tail删除第一个空行。

答案4

sed -e:t -e'$!{1N;N;s/\( .*\)\(\n[^ ]*\1\)\{2\}$/\1\2/;tt' -e'P;D;}' <in >out

...这样可行。它递归地替换掉一系列三个输入行中的第二个输入行,它认为从第二个空格分隔的字段开始是相同的。它将继续绘制另一条输入线来替换它所替换的每条输入线,直到它无法再这样做为止。只有当它无法匹配三个这样的相似行时,它才会在删除它P之前在缓冲区中打印第一个行D,然后循环返回以再次尝试剩余的两个行和下一行输入。

使用 GNU 或 BSD sed

sed -Ee:t -e'1N;$!N;s/( .*)(\n[^ ]*\1){2}$/\1\2/;tt' -eP\;D <in >out

1447790360      99999   99999   20.25   20.25   20.25   20.50
1447790362      20.25   20.25   20.25   20.25   20.25   20.50
1447790386      20.25   20.25   20.25   20.25   20.25   20.50
1447790388      20.25   20.25   99999   99999   99999   99999
1447790389      99999   99999   20.25   20.25   20.25   20.50
1447790391      20.00   20.25   20.25   20.25   20.25   20.50
1447790394      20.25   20.25   20.25   20.25   20.25   20.50
1447790400      20.25   20.25   20.25   20.25   20.25   20.50

相关内容