我有带有时间戳的日志文件,每行有六个值,我想通过删除具有相同值的连续行(忽略时间戳)并保留每个重复集的第一行和最后一行来减少数据量。最好使用 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
相同。l0
这p
只是一个标记,用于标记上一行是否已打印。
答案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