我希望此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 版本都具有相同的潜在问题,即它们还将输出可变数量的字段,具体取决于第二个字段的内容。修复它们并不难。