使用 awk 删除第一行中“-”的列

使用 awk 删除第一行中“-”的列

我有桌子

M       -       A       A       -
-       A       G       -       -
M       -       -       -       G

我想执行:如果第一行的任何列包含“-”,则跳过打印该列

预期输出是

M       A       A 
-       G       - 
M       -       - 

我尝试过类似的方法但没有成功

awk 'NR==1 && $i!="-" {print $i}'

有谁知道如何更正命令?

答案1

的一个变体艾德·莫顿的回答,它通过字段编号记住哪些字段不在-第一行,然后根据数组中保存的索引重新形成输入中的每条记录,out然后再打印新记录:

FNR == 1 {
    for (i = 1; i <= NF; ++i)
        if ($i != "-") out[++nf] = i
}

{
    for (i = 1; i <= nf; ++i)
        a[i] = $(out[i])

    $0 = ""

    for (i = 1; i <= nf; ++i)
        $i = a[i]

    print
}

在这里,我为了可读性牺牲了一点效率,在单独的循环中重建记录,而不是在第二个块的单个循环中打印所需的字段。

测试:

$ awk -f script.awk file
M A A
- G -
M - -

使用制表符作为输出字段分隔符运行:

$ awk -v OFS='\t' -f script.awk file
M       A       A
-       G       -
M       -       -

一段稍微太长的单行代码,取决于输入数据是否以制表符分隔:

$ cut -f "$(awk -v OFS=',' '{ nf=split($0,a); $0=""; for (i=1; i<=nf; ++i) if (a[i]!="-") $(++NF)=i; print; exit }' file)" file
M       A       A
-       G       -
M       -       -

这用于awk输出字段编号不在-第一行作为逗号分隔列表。然后将该列表移交给该列表,cut -f由该列表实际输出文件中的数据。请注意,文件名(此处简称file)在命令行上给出两次,一次 for ,awk然后再次 for cut

答案2

$ cat tst.awk
NR == 1 {
    for (i=1; i<=NF; i++) {
        if ($i != "-") {
            f[++numOutFlds] = i
        }
    }
}
{
    for (i=1; i<=numOutFlds; i++) {
        printf "%s%s", $(f[i]), (i<numOutFlds ? OFS : ORS)
    }
}

$ awk -f tst.awk file
M A A
- G -
M - -

答案3

awk隐式循环输入记录(行)和文件,但不循环必须显式执行的字段。在您的情况下,您需要循环第一行(标题行)中的字段来决定要包含哪些列,然后循环遍历每一个行(标题和非标题)以包含该行上所需的列。

您不清楚是否要查找以下标头字段等于(字符串)“-”或者可能将其作为(子)字符串。我还假设您有(所有)单个选项卡作为字段分隔符,而不是多个空格,这会更乏味(并且无法在视觉上与您的帖子区分开)。

awk -F"\t" 'NR==1{for(i=1;i<=NF;i++)s[i]=$i!="-"} {x="";for(i=1;i<=NF;i++)if(s[i])x=x FS $i;print substr(x,2)}'
# for _matches_ "-" instead of _equals_ "-" change $i!="-" to $i!~/-/
# note if a nonheader line has more fields than the header did,
# all extra fields are nonselected (as if their header field was/matched -)

# or (re)use the flags for both what to include _and_ when to terminate the line
awk -F"\t" 'NR==1{t=RS;for(i=NF;i;i--)if(s[i]=($i!="-"?t:""))t=FS} {for(i=1;i<=NF;i++)if(s[i])printf "%s%s",$i,s[i]}'
# some people may consider this too clever

答案4

我们可以使用 来完成此操作sed,尽管代码是在扩展正则表达式模式下使用 GNU sed,但这只是作为 的治疗方法backslashitis

该方法是从第一行开始创建地图。要保留的字段映射为 x,其他字段映射为破折号。将此地图保存在货舱中。

然后,对于所有线路,附加此地图并在 BOL 处放置一个标记。

在循环中,如果我们看到 \n- 并且将标记前进到下一个字段,我们将继续删除当前行的前导字段。

当该标记与当前行和保留空间之间的换行符碰撞时,循环结束(由于 G 命令)。

$ sed -Ee '
    1{
      h
      y/-/\n/
      s/\S+/x/g;s/[[:blank:]]+//g
      y/\n/-/
      x
    }

    G;s/^/\n/

    :a
      s/\n(\S+\s*)(.*\n)x/\1\n\2/
      s/\n(\S+\s*)(.*\n)-/\n\2/
    /\n\n/!ba

    s/\s+$//
' file

结果

M       A       A
-       G       -
M       -       -

相关内容