大家早上好,
我想用空行来划分文件的匹配行组。作为 awk 的新手,我做了一些修改并想出了这个:
awk '!($0 in a) {print "\n"; a[$0]}; {print}'
在我的脑海中读作
如果当前行不在数组“a”中,则打印换行符并将该行添加到“a”中。打印当前行。
如果我对测试文件运行它,输出看起来像
abc
abc
def
def
def
ghi
即打印了两行空行而不是一行。多余的线从哪里来?
这是我使用的测试文件:
abc
abc
def
def
def
ghi
答案1
您不需要关联的数组:
awk 'prev!=""{ print prev!=$0? prev ORS : $0 } { prev=$0 }
END{ if(prev!="") print prev }' infile
输出:
abc
abc
def
def
def
ghi
关于为什么你awk
打印换行符两次是因为你正在使用print
语句,默认情况下它打印你正在打印的内容 + ORS (氧输出右埃科德S分隔符,默认为换行符),您需要使用printf "\n"
代替或仅使用print ""
;并使用您自己的解决方案,您可以执行以下操作(应用一些修复):
awk '!($0 in a) { if(c++) print "" } { a[$0]; print}' infile
或更紧凑:
awk '!($0 in a) && c++{ print ""} ++a[$0]' infile
答案2
$ awk '{print ($0!=p ? s : "") $0; p=$0; s=ORS}' file
abc
abc
def
def
def
ghi
代码中导致打印 2 个空行而不是 1 个空行的错误是使用print "\n"
而不是print ""
,后者是打印 的值所需的全部ORS
:
$ awk 'BEGIN{print "---"; print "\n"; print "---"}'
---
---
$ awk 'BEGIN{print "---"; print ""; print "---"}'
---
---
您也可以使用,printf "\n"
但这会打印您希望/假设的硬编码值,ORS
而不是简单地ORS
使用print ""
.
我的解决方案和您的解决方案之间的主要功能差异是您的脚本会在输出开始处打印一个空行,而我的则不会(感谢s=ORS
打印第一行后的设置)并且您正在保存整个输入文件虽然a[]
我只保存 1 个输入行,但前一个输入行已读取,所以p
:
- 您的脚本将使用大量内存,因此对于巨大的输入文件可能会失败,而我的脚本适用于任何大小的输入文件。
- 如果任何输入行以前从未出现在输入中的任何位置,您的脚本只会打印一个空行,而我的脚本会在每次输入更改时打印一个空行,因此如果输入行不总是分组,它们的行为会彼此不同一起,例如:
$ printf 'foo\nbar\nfoo\n'
foo
bar
foo
$ printf 'foo\nbar\nfoo\n' | awk '!($0 in a) {print ""; a[$0]}; {print}'
foo
bar
foo
$ printf 'foo\nbar\nfoo\n' | awk '{print ($0!=p ? s : "") $0; p=$0; s=ORS}'
foo
bar
foo
为了在代码中执行您想要执行的操作,而不是使用名为的数组,a[]
我们惯用地命名该数组seen[]
并在测试它的同时更新它,而不是单独进行更新,因此您的代码将惯用地编写为awk '!seen[$0]++{print ""} 1'
而不是awk '!($0 in a) {print ""; a[$0]}; {print}'
:
$ printf 'foo\nbar\nfoo\n' | awk '!seen[$0]++{print ""} 1'
foo
bar
foo
如果您希望该功能在输出中不带前导空行,请选择:
$ printf 'foo\nbar\nfoo\n' | awk '{print (seen[$0]++ ? "" : s) $0; s=ORS}'
foo
bar
foo
$ printf 'foo\nbar\nfoo\n' | awk '!seen[$0]++ && NR>1{print ""} 1'
foo
bar
foo
$ printf 'foo\nbar\nfoo\n' | awk '!seen[$0]++{if (NR>1) print ""} 1'
foo
bar
foo
$ printf 'foo\nbar\nfoo\n' | awk '!seen[$0]++{printf s; s=ORS} 1'
foo
bar
foo
如果 ORS 包含 printf 格式化字符,最后一个将会失败,例如:
$ printf 'foo\nbar\nfoo\n' | awk -v ORS='\n%s\n' '!seen[$0]++{printf s; s=ORS} 1'
foo
%s
awk: cmd. line:1: (FILENAME=- FNR=2) fatal: not enough arguments to satisfy format string
`
%s
'
^ ran out for this one
因此,如果这是一个问题,您可以将其编写得更健壮,如下所示:
$ printf 'foo\nbar\nfoo\n' | awk -v ORS='\n%s\n' '!seen[$0]++{printf "%s", s; s=ORS} 1'
foo
%s
%s
bar
%s
foo
%s