如何让 awk 在匹配后提供下一列

如何让 awk 在匹配后提供下一列

我有以下文件(somefile.txt):

/A/1/B/1/C/1/D/1/E/1/F/2/G/1/H/1/I/1/J/1/K/1/
/B/1/C/1/D/1/E/1/F/5/G/1/H/1/I/1/J/1/K/1/
/C/1/D/1/E/1/F/9/G/1/H/1/I/1/J/1/K/1/
/D/1/E/1/F/7/G/1/H/1/I/1/J/1/K/1/
/A/1/B/1/C/1/D/1/E/1/F/8/G/1/H/1/I/1/J/1/K/1/
/A/1/B/1/C/1/D/1/E/1/F/3/G/1/H/1/I/1/J/1/K/1/
/A/1/B/1/C/1/D/1/E/1/F/6/G/1/H/1/I/1/J/1/K/1/
/B/1/C/1/D/1/E/1/F/8/G/1/H/1/I/1/J/1/K/1/
/D/1/E/1/F/3/G/1/H/1/I/1/J/1/K/1/
/C/1/D/1/E/1/F/6/G/1/H/1/I/1/J/1/K/1

我希望得到以下结果(后面的下一个数字F):

2
5
9
7
8
3
6
8
3
6

鉴于每行的列数是可变的,有没有办法我可以执行如下操作?:

awk -F'/' '/F/ {print <column_of_match> + 1 }' somefile.txt

答案1

使用 Perl,因为数组切片很方便,所以能够将数组中的每对元素视为哈希的键和值:

$ perl -F/ -lane '%f = @F[1..$#F]; print $f{F}' input.txt 
2
5
9
7
8
3
6
8
3
6

Perl-F-a(autosplit) 的工作方式与 awk 类似 - 但它不是将行自动拆分为 $1、$2、$3 等,而是将每一行自动拆分为一个名为 的数组@F

该脚本将数组切片@F(除了第零个元素之外的所有元素)转换为名为 的散列(关联数组) ,并使用键“F”%f打印 的元素。%f


为了强调它的作用/工作原理(以及为什么我们需要排除 @F 的空字符串第零个元素),以下是使用时的内容@F%f外观数据::转储模块dump功能:

$ perl -F/ -MData::Dump=dump -lane '
    %f = @F[1..$#F];
    print join("\n", $_, dump(@F), dump(\%f), $f{F}), "\n"' input.txt 
/A/1/B/1/C/1/D/1/E/1/F/2/G/1/H/1/I/1/J/1/K/1/
("", "A", 1, "B", 1, "C", 1, "D", 1, "E", 1, "F", 2, "G", 1, "H", 1, "I", 1, "J", 1, "K", 1)
{ A => 1, B => 1, C => 1, D => 1, E => 1, F => 2, G => 1, H => 1, I => 1, J => 1, K => 1 }
2

/B/1/C/1/D/1/E/1/F/5/G/1/H/1/I/1/J/1/K/1/
("", "B", 1, "C", 1, "D", 1, "E", 1, "F", 5, "G", 1, "H", 1, "I", 1, "J", 1, "K", 1)
{ B => 1, C => 1, D => 1, E => 1, F => 5, G => 1, H => 1, I => 1, J => 1, K => 1 }
5

/C/1/D/1/E/1/F/9/G/1/H/1/I/1/J/1/K/1/
("", "C", 1, "D", 1, "E", 1, "F", 9, "G", 1, "H", 1, "I", 1, "J", 1, "K", 1)
{ C => 1, D => 1, E => 1, F => 9, G => 1, H => 1, I => 1, J => 1, K => 1 }
9
...and so on...

F注意:如果输入中没有,这将打印一个空行。如果这不是您想要的,请执行以下操作:

perl -F/ -lane '%f = @F[1..$#F];
                if (defined $f{F}) {
                  print $f{F}
                } else {
                   print STDERR "Error on input line $.: F has absconded"
                }' input.txt

答案2

这是使用的答案sed

$ sed -n 's|.*F/\([0-9]\).*|\1|p' <<EOF
/A/1/B/1/C/1/D/1/E/1/F/2/G/1/H/1/I/1/J/1/K/1/
/B/1/C/1/D/1/E/1/F/5/G/1/H/1/I/1/J/1/K/1/
/C/1/D/1/E/1/F/9/G/1/H/1/I/1/J/1/K/1/
/D/1/E/1/F/7/G/1/H/1/I/1/J/1/K/1/
/A/1/B/1/C/1/D/1/E/1/F/8/G/1/H/1/I/1/J/1/K/1/
/A/1/B/1/C/1/D/1/E/1/F/3/G/1/H/1/I/1/J/1/K/1/
/A/1/B/1/C/1/D/1/E/1/F/6/G/1/H/1/I/1/J/1/K/1/
/B/1/C/1/D/1/E/1/F/8/G/1/H/1/I/1/J/1/K/1/
/D/1/E/1/F/3/G/1/H/1/I/1/J/1/K/1/
/C/1/D/1/E/1/F/6/G/1/H/1/I/1/J/1/K/1
EOF
2
5
9
7
8
3
6
8
3
6

的解释-n 's|.*F/\([0-9]\).*|\1|p'

  • -n意味着除非明确告知,否则不要打印任何内容
  • p表达式中的尾部表示:“如果此表达式匹配,则打印此行”。这意味着没有 an 的行将F/[0-9]不会被打印。
  • s|foo|bar|表达式中的意思是: 替换foobar。您通常将其视为,但由于表达式中s/foo/bar/有 a ,所以我过去常常避免转义它。/|
  • 比赛部分(foo):
    • .*F/[0-9].*意思是:所有带有F/然后一个数字的行。
    • .*F/\([0-9]\).*F/意思是:匹配包含一个数字的整行,但记住该数字
  • 替换部分(bar):
    • \1指的是我们记住的那个数字。

简而言之:

  • 找到任何匹配 的行*F/[0-9]*,并将其仅替换为数字。

如果可以使用多位正整数,则可以轻松调整表达式:

sed -n 's|.*/F/\([0-9]\+\)/.*|\1|p'

答案3

只需使用与分隔符 和 匹配的模式F,将该子字符串拆分为一个数组,然后打印该子字段。

测试代码:

$ awk 'match ($0, "/F/[^/]/") {
    split (substr ($0, RSTART, RLENGTH), V, "/");
    print V[3];
}' Match.txt

无需迭代字段或使用两个进程。

您也可以通过调整字符串索引来删除不需要split, 的部分,但这会使其不那么通用,并且更有可能出现一次性错误。

awk 'match ($0, "/F/[^/]/") {
    print substr ($0, RSTART+3, RLENGTH-4);
}' Match.txt

答案4

这是解决您的问题的一个可能的解决方案,它涉及使用 awk 两次,一次用于在正确的位置分割,下一次抓取数字并打印它。

这是脚本:

awk -F "/F/" '{print $2}' prova.txt  | awk -F "/" '{print $1}'

在第一部分中,我们将输入字符串拆分为/F/,以便第二部分的第一个字母就是我们要查找的数字,而在脚本的第二部分中,我们只是隔离该数字。

当我们每行最多有一个时,这是有效的F(甚至在没有 F 时也有效,因为它只会打印空行。

相关内容