如何指导我的 awk 命令在第 2 列上工作

如何指导我的 awk 命令在第 2 列上工作

我希望此awk命令将第 2 列的最后一个下划线替换为制表符。现在,它将每行的最后一个下划线替换为制表符,请注意,每行的列中的下划线数量可能不同。我尝试了很多方法来指示命令仅在第 2 列上工作。我知道我已经很接近了,有人可以做最后的调整吗?

制表符分隔的示例文件:

OTU1 this_is_the_second_column 100 0 450 this_is_the_sixth_column 1 5 3.2
OTU2 this_is_another_column_to_parse 103 4 650 this_is_another_test_string_too 4 7 4.6

它应该是什么样子:

OTU1 this_is_the_second column 100 0 450 this_is_the_sixth_column 1 5 3.2
OTU2 this_is_another_column_to parse 103 4 650 this_is_another_test_string_too 4 7 4.6

这是我当前的代码:

gawk -F'\t' -v OFS='\t' 'BEGIN{FS=OFS="_"}{last=$NF;NF--;print $0"\t"last}' test1.tab > test1_reformat.tab

任何帮助是极大的赞赏

谢谢

答案1

由于您似乎有 GNU awk,因此您可以使用它根子函数捕获下划线后面的非下划线尾随序列,并在制表符后面重新替换它:

gawk 'BEGIN {OFS=FS="\t"} {$2 = gensub(/_([^_]*)$/, "\t\\1", "1", $2)} 1' test1.tab

或者(并且 - 我认为 - 可移植)使用该match函数进行一些字符串切片:

awk 'BEGIN{OFS=FS="\t"} match($2,/_[^_]*$/) {$2 = substr($2,1,RSTART-1) "\t" substr($2,RSTART+1)} 1' test1.tab

答案2

干得好

gawk '
    BEGIN { OFS = FS = "\t" }              # Output as input as tabs
    {
        n = split($2, a, "_");             # Split $2 by "_" into array
        for(i = 1; i<n; i++) {
            s = (i>1 ? s "_" : "") a[i]    # Rejoin fields with "_"
        }
        $2 = s OFS a[n];                   # Join last with OFS
        print
    }
' file

如果您删除注释,您可以在一行上运行它,但我不建议在生产代码中这样做。

示例输入的输出(由于此处的格式限制,制表符扩展为空格)

OTU1    this_is_the_second      column  100     0       450     this_is_the_sixth_column        1       5       3.2
OTU2    this_is_another_column_to       parse   103     4       650     this_is_another_test_string_too 4       7       4.6

答案3

不要使解决方案过于复杂。这是实现您想要的目标的一种方法

假设您的输入如下并存储在infile(我添加另一行只是为了演示目的):

OTU1 this_is_the_second_column 100 0 450 this_is_the_sixth_column 1 5 3.2
OTU2 this_is_another_column_to_parse 103 4 650 this_is_another_test_string_too 4 7 4.6
OTU2 this_is_another_column_to_parse_and_parse 103 4 650 this_is_another_test_string_too 4 7 4.6

然后你可以做如下事情:

awk -vOFS="\t" '{p = match($2, /_[^_]*$/); if (p) $2 = substr($2, 1, p-1) "\t" substr($2, p+1)}1' infile

输出:

OTU1    this_is_the_second  column  100 0   450 this_is_the_sixth_column    1   5   3.2
OTU2    this_is_another_column_to   parse   103 4   650 this_is_another_test_string_too 4   7   4.6
OTU2    this_is_another_column_to_parse_and parse   103 4   650 this_is_another_test_string_too 4   7   4.6

答案4

使用perl:

$ perl -F"\t" -le 'BEGIN{ $, = "\t" };
                   $F[1] =~ s/^(.*)_(.*)/$1$,$2/;
                   print @F' test1.tab  
OTU1    this_is_the_second      column  100     0       450     this_is_the_sixth_column        1       5       3.2
OTU2    this_is_another_column_to       parse   103     4       650     this_is_another_test_string_too 4       7       4.6
  • -F"\t"打开 perl 的自动分割模式(类似于 awk,但使用名为的数组@F而不是 $1、$2、$3 等)并告诉 perl 在制表符上分割。 -F还打开 Perl 的-n模式,这使得它像sed -n.查看man perlrun并搜索-F-a、 和-n

    请注意,perl 中的数组索引从零开始,$F[0]第一个元素也是从零开始,$F[1]第二个元素也是从零开始,依此类推。

  • $,是 perl 的输出字段分隔符变量(在perlvar手册页中有记录)。将其设置在 BEGIN 块 ( BEGIN{ $, = "\t" }) 中可确保它在脚本启动时仅运行一次,而不是每个输入行运行一次。

  • $F[1] =~ s/^(.*)_(.*)/$1$,$2/_将第二个字段中的最后一个更改为$,。 perl 中的正则表达式匹配默认是贪婪的,所以^(.*)_将匹配并捕获之前的所有内容最后的 _

  • @F然后打印该数组。

这是可移植的,因为如果您安装了任何版本的 perl,它都可以工作(即不需要像 GNU awk for 那样的非标准版本gensub()


或者,使用 perl 的join()函数(请参阅perldoc -f join)而不是设置$,

perl -F"\t" -le '$F[1] =~ s/^(.*)_(.*)/$1\t$2/;
                 print join "\t", @F' test1.tab

另一种替代方法是使用该splice()函数(请参阅 参考资料perldoc -f splice)实际将一个新元素插入到@F字段 2 和 3 之间的数组中(这与包含字段分隔符的第二个元素不同)。新元素成为第三个元素 ( $F[2]),并且所有后续元素的索引都增加 1。

如果您需要在插入新字段后对数组进行进一步处理,这将非常有用(与 awk 不同,在数组中插入或删除元素很简单,因为 perl 内置了数组操作函数)

perl -F"\t" -le '$F[1] =~ s/^(.*)_(.*)/$1/;
                 splice @F, 2, 0, $2;
                 print join "\t", @F' test1.tab

值得注意的是:来自替换运算符的捕获组s///将持续存在,直到它们超出范围或直到另一个正则表达式匹配或替换成功 - 这就是为什么$2可以与 一起使用splice

这也意味着该版本将无法正常工作,如果$F[1]在任何输入行上,此版本都将无法正常工作包含一个_字符(它将插入第三个字段,其中包含上次成功匹配的 $2 ,或者插入一个空字段,直到第一次成功替换)。要处理这个问题,您必须测试替换是否成功,例如:

perl  -F"\t" -le 'if ($F[1] =~ s/^(.*)_(.*)/$1/) {
                    splice @F, 2, 0, $2;
                  } else {
                    splice @F, 2, 0, ""; # insert empty field 3
                  };
                  print join "\t", @F' test1.tab

其他版本也不会真正正常工作 - 它们将输出具有可变数量的制表符分隔字段的行 - 当第二个字段包含下划线时为十个字段,否则为九个字段。

它们使用起来不安全,除非: 1. 保证第二个字段始终包含至少一个下划线;或者 2. 你不需要关心输出是有九个字段还是十个字段。

如果替换失败,可以通过将字段分隔符附加到第二个字段来修复这些版本 - 例如,如下所示:

$F[1] .= $, unless $F[1] =~ s/^(.*)_(.*)/$1$,$2/;

或这个:

$F[1] .= "\t" unless $F[1] =~ s/^(.*)_(.*)/$1\t$2/;

顺便说一句,到目前为止,其他答案中的 awk 版本都具有相同的潜在问题,即它们还将输出可变数量的字段,具体取决于第二个字段的内容。修复它们并不难。

相关内容