对于文件中的每一行,如果这些值小于另一个字段中的值,则将特定列中的字段打印到 NF

对于文件中的每一行,如果这些值小于另一个字段中的值,则将特定列中的字段打印到 NF

我有一个具有以下格式的文件,其中每行的字段数是可变的:

NC_000001.11_NM_001005484.2 69270   234 69037   65565   69037
NC_000001.11_NM_001005484.2 69511   475 69037   65565   69037
NC_000001.11_NM_001005484.2 69761   725 69037   65565   69037
NC_000001.11_NM_001385640.1 942155  20  942136  924432  925922  930155  931039  935772 939040   939272  941144  942136  942410  942559  943253  943698  943908  

对于每一行,我想打印前四个字段。对于其余字段($5 到 NF),如果这些字段中的值小于 $4 中的值,我想打印该字段。

输出示例:

NC_000001.11_NM_001005484.2 69270   234 69037   65565   
NC_000001.11_NM_001005484.2 69511   475 69037   65565   
NC_000001.11_NM_001005484.2 69761   725 69037   65565   
NC_000001.11_NM_001385640.1 942155  20  942136  924432  925922  930155  931039  935772 939040   939272  941144  

我尝试过多种不同的 awk 选项,但都失败了。 awk 新手,希望得到任何帮助。

答案1

如果您不关心输出中的空白,那么您需要的是:

$ cat tst.awk
{
    out = $1 OFS $2 OFS $3 OFS $4
    for (i=5; i<=NF; i++) {
        if ( $i < $4 ) {
            out = out OFS $i
        }
    }
    print out
}

$ awk -f tst.awk file
NC_000001.11_NM_001005484.2 69270 234 69037 65565
NC_000001.11_NM_001005484.2 69511 475 69037 65565
NC_000001.11_NM_001005484.2 69761 725 69037 65565
NC_000001.11_NM_001385640.1 942155 20 942136 924432 925922 930155 931039 935772 939040 939272 941144

column如果您愿意,您可以通过管道进行视觉对齐:

$ awk -f tst.awk file | column -t
NC_000001.11_NM_001005484.2  69270   234  69037   65565
NC_000001.11_NM_001005484.2  69511   475  69037   65565
NC_000001.11_NM_001005484.2  69761   725  69037   65565
NC_000001.11_NM_001385640.1  942155  20   942136  924432  925922  930155  931039  935772  939040  939272  941144

否则,如果您希望输出中的间距看起来像输入中的间距(即前 4 个字段看起来有 1 个或更多空格,其余字段有 2 个或更多空格)并假设某些行可能只有 4或更少的字段,然后使用任何 POSIX awk(对于字符类和正则表达式间隔):

$ cat tst.awk
BEGIN { OFS="\t" }
match($0,/([^[:space:]]+[[:space:]]+){3}[^[:space:]]+/) {
    out = substr($0,RSTART,RLENGTH)
    for (i=5; i<=NF; i++) {
        if ( $i < $4 ) {
            out = out OFS $i
        }
    }
    $0 = out
}
{ print }

如果 $4 之后的字段应以制表符分隔:

$ awk -f tst.awk file
NC_000001.11_NM_001005484.2 69270   234 69037   65565
NC_000001.11_NM_001005484.2 69511   475 69037   65565
NC_000001.11_NM_001005484.2 69761   725 69037   65565
NC_000001.11_NM_001385640.1 942155  20  942136  924432  925922  930155  931039  935772  939040  939272  941144

或者如果它们应该用空格分隔:

$ awk -f tst.awk file | column -s$'\t' -t
NC_000001.11_NM_001005484.2 69270   234 69037   65565
NC_000001.11_NM_001005484.2 69511   475 69037   65565
NC_000001.11_NM_001005484.2 69761   725 69037   65565
NC_000001.11_NM_001385640.1 942155  20  942136  924432  925922  930155  931039  935772  939040  939272  941144

上面保留了前 4 个字段之间的空白,以便您输入中的制表符和/或空格的任何组合,然后在每 5 个及后续字段之前打印一个制表符,您可以使用它column来更改为等效的如果您愿意,可以留空,两者看起来都像问题中的输入和输出。

我正在构建一个out在上面的循环中命名的新字符串,并将其分配给$0循环之后的一次,而不是修改$0$i在循环内,因为每次更改$iawk 都必须从它的字段重新构建$0,并且每次更改$0awk 都必须重新分割$0为字段,因此两者效率低下,并且可能会导致意外错误,具体取决于字段的内容,因此您不应在循环内修改$0或 ,$i除非您有一个非常具体的目的需要这样做。

答案2

这已经使用 GNU Awk 5.1.0,API:3.0 进行了测试,因为在split此解决方案中使用第四个参数可能不适用于与此处使用的语法不兼容的其他版本。

awk '{n=split($0, a, " ", b); line=""; for (i = 1; i <= n; i++) { if (i < 5 || a[i] < $4) line=(line a[i] b[i])}; print line; }' file.txt

解释:

  • n=split($0, a, " ", b);- 这会将整行 ( $0) 分割为值(存储在a)和空格(存储在b),因此我们可以尝试保留原始文件的格式。存储的值为n我们提供了处理每行的字段数。split数组ab索引都从 1 开始。
  • line=""- 以空字符串开头
  • for (i = 1; i <= n; i++)- 让我们迭代每个字段,split 从索引 1 开始,所以我们的循环。<=部分确保最后一个(第 n 个)字段也被处理
  • if (i < 5 || a[i] < $4)- 前 4 个字段或字段值小于第四个字段时条件为真(您所需的条件)
  • line=(line a[i] b[i])- 将实际字段和空格与之前满足“if”条件要求的字段和空格连接起来
  • print line- 打印line包含我们所需输出的变量

答案3

这会从行尾到行首(即按相反顺序)迭代字段,如果字段编号 ( NF) 大于 4,则删除该字段该字段的值大于字段 4 ( $4) 的值。

$ awk '{
    for (i=NF; i>=1; i--) {
      if ((i > 4) && ($i >= $4)) {
        $i=""
      }
    };
    print
    }' input.txt
NC_000001.11_NM_001005484.2 69270 234 69037 65565 
NC_000001.11_NM_001005484.2 69511 475 69037 65565 
NC_000001.11_NM_001005484.2 69761 725 69037 65565 
NC_000001.11_NM_001385640.1 942155 20 942136 924432 925922 930155 931039 935772 939040 939272 941144 

顺便说一句,不清楚您的输入是空格还是制表符分隔。如果您想要制表符分隔的输出(而不是每个字段之间有一个空格),请-v OFS='\t'在启动脚本的单引号之前添加到 awk 命令中。例如

awk -v OFS='\t' '...awk script here...' input.txt

顺便说一句,awk 会在输出行中留下许多额外的字段分隔符,无论字段在被删除之前位于何处。如果您想删除这些内容,请在语句之前添加以下行print

    $0=$0; $1=$1;

这将有效地删除任何空字段,方法是强制 awk 重新评估输入行并将其再次拆分为字段(在 FS 上拆分,字段分隔符,默认为任意数量的空白)。这有点像黑客,因为 awk 没有任何方法可以实际删除行中的字段,因此您必须在修改行后强制它执行此操作。

相关内容