根据条件在 awk 中添加一个空行

根据条件在 awk 中添加一个空行

大家早上好,

我想用空行来划分文件的匹配行组。作为 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

  1. 您的脚本将使用大量内存,因此对于巨大的输入文件可能会失败,而我的脚本适用于任何大小的输入文件。
  2. 如果任何输入行以前从未出现在输入中的任何位置,您的脚本只会打印一个空行,而我的脚本会在每次输入更改时打印一个空行,因此如果输入行不总是分组,它们的行为会彼此不同一起,例如:
    $ 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

相关内容