我有一些表 ( table.txt
) 被错误地构建并在结果中出现冗余,如下所示:
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1972 1 1 549
1972 1 2 746
...
相反,我想要:
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1972 1 1 549
1972 1 2 746
...
所以问题是结果在表中出现了两次。这意味着(使用提供的示例)在“1971”之后,我应该预期年份“1972”,而不是再次“1971”。有没有办法使用 sh/bash 删除多余的结果?
我必须注意到,我的数据从 1971 年一直持续到 2099 年,即使在 2000 年之后,它们的格式也完全相同,如下所示:
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
...
2000 1 1 875
2000 1 2 456
...
2099 12 31 321
答案1
这是两个互斥的sed
循环:
sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' <<""
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1972 1 1 549
1972 1 2 746
...
1972 12 31 999
1972 1 1 933
1972 1 2 837
...
1972 12 31 343
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1972 1 1 549
1972 1 2 746
...
1972 12 31 999
基本上sed
有两种状态 - p
rint 和吃。在第一个状态 - p
rint 状态 -sed
自动p
rint 每个输入行,然后根据模式检查它/ 12 * 31 /
。如果当前模式空间不!
匹配,它将被d
删除并sed
拉入下一个输入行,并在 rint 命令处从顶部再次启动脚本,p
而根本不尝试运行d
elete 命令后面的任何内容。
当输入线做/ 12 * 31 /
然而,matchsed
进入了脚本的后半部分 -吃环形。首先它定义了一个:
名为 的分支标签n
;然后它用 ext 输入行覆盖当前模式空间n
,然后将当前模式空间与//
最后一个匹配的模式进行比较。因为之前匹配的行刚刚被 ext 覆盖n
,所以这个的第一次迭代吃循环不匹配,并且每次它不!
回溯sed
b
到:n
标签以获取n
ext 输入行并再次将其与//
最后一个匹配的模式进行比较。
当最终进行另一个匹配时(大约 365 个n
ext 行之后),当它完成其脚本时,sed
不会自动打印它,拉入下一个输入行,并在第一个状态下的 rint 命令处-n
从顶部重新开始。p
因此,每个循环状态都会在同一键上跳转到下一个状态,并同时尽可能少地查找下一个键。
请注意,整个脚本无需调用单个编辑例程即可完成,并且只需要编译单个正则表达式。生成的自动机非常简单 - 它只理解[123 ]
和[^123 ]
。更重要的是,至少一半的比较很可能是在没有任何编译的情况下进行的,因为在吃循环根本就是//
空循环。因此,可以通过每个输入行sed
一次调用来完全完成该循环。regexec()
sed
可能p
对rint 循环也做类似的事情。
定时的
我很好奇这里的各种答案可能会如何表现,所以我想出了自己的表格:
dash <<""
d=0 D=31 IFS=: set 1970 1
while case "$*:${d#$D}" in (*[!:]) ;;
($(($1^($1%4)|(d=0))):1:)
D=29 set $1 2;;
(*:1:) D=28 set $1 2;;
(*[3580]:)
D=30 set $1 $(($2+1));;
(*:) D=31 set $(($1+!(t<730||(t=0)))) $(($2%12+1))
esac
do printf '%-6d%-4d%-4d%d\n' "$@" $((d+=1)) $((t+=1))
done| head -n1000054 >/tmp/dates
dash <<<'' 6.62s user 6.95s system 166% cpu 8.156 total
这将输入 100 万多行,/tmp/dates
并将 1970 - 3338 年每年的输出翻倍。该文件如下所示:
tail -n1465 </tmp/dates | head; echo; tail </tmp/dates
3336 12 27 728
3336 12 28 729
3336 12 29 730
3336 12 30 731
3336 12 31 732
3337 1 1 1
3337 1 2 2
3337 1 3 3
3337 1 4 4
3337 1 5 5
3338 12 22 721
3338 12 23 722
3338 12 24 723
3338 12 25 724
3338 12 26 725
3338 12 27 726
3338 12 28 727
3338 12 29 728
3338 12 30 729
3338 12 31 730
……无论如何,有一些。
然后我尝试了不同的命令:
for cmd in "sort -uVk1,3" \
"sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn'" \
"awk '"'{u=$1 $2 $3 $4;if (!a[u]++) print;}'\'
do eval "time ($cmd|wc -l)" </tmp/dates
done
500027
( sort -uVk1,3 | wc -l; ) \
1.85s user 0.11s system 280% cpu 0.698 total
500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.64s user 0.09s system 110% cpu 0.659 total
500027
( awk '{u=$1 $2 $3 $4;if (!a[u]++) print;}' | wc -l; ) \
1.46s user 0.15s system 104% cpu 1.536 total
sort
和命令sed
都在不到一半的时间内完成awk
- 这些结果是典型的。我确实运行了它们好几次。看起来所有命令都写出了正确的行数 - 所以它们可能都有效。
sort
每次运行的完成时间都相当不错(通常领先一点),但比sed
其他两个命令中的任何一个都需要做更多的实际工作来实现其结果。它正在运行并行作业来完成其任务,并从我的多核 CPU 中受益匪浅。并且在它们处理的整个时间里都与分配给它们的单核挂钩。sed
sort
awk
sed
这里的结果来自标准的、最新的 GNU sed
,但我确实尝试了另一个。事实上,我用其他二进制文件尝试了所有三个命令,但只有该sed
命令真正适用于我的传家宝工具。我猜想,由于非标准语法,其他人只是在开始工作之前就因错误而退出。
尽可能使用标准语法是件好事——在许多情况下,您可以自由地使用更简单、更完善、更高效的实现:
PATH=/usr/heirloom/bin/posix2001:$PATH; time ...
500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.31s user 0.12s system 136% cpu 0.318 total
答案2
尝试通过管道将其传输到 awk
awk '!a[$0]++' files.txt > new_files.txt
mv new_files.txt files.txt
这只会输出一次行。
编辑:(不确定连接 var 是否可以解决问题)
awk '{u=$1 $2 $3 $4 ; if ( !a[u]++ ) print ; } ' ...
答案3
$ (head -1 table.txt ; tail -n +2 table.txt | sort -u -V -k1,3)
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
1971 2 1 587
1971 12 31 685
1972 1 1 549
1972 1 2 746
2000 1 1 875
2000 1 2 456
2099 12 31 321