AWK正则表达式分割函数使用多个分隔符

3

我正在尝试使用Awk的split函数将输入拆分为三个字段,以便使用值作为field[1]、field[2]、field[3]。我尝试提取第一个值: 冒号及其后面的所有内容,然后是第一个制表符(\t)之前的所有内容(十六进制),最后一个字段将包括其他所有内容。

我尝试了多个正则表达式,最接近解决这个问题的是:

echo -e "ffffffff81000000: 48 8d 25 51 3f 60 01\tleaq asdf asdf asdf" \
| awk '{split($0,field,/([:])([ ])|([\t])/); \
print "length of field:" length(field);for (x in field) print field[x]}'

但是结果不包括冒号 - 我也不确定我写的正则表达式是否正确:

length of field:3
ffffffff81000000
48 8d 25 51 3f 60 01
leaq asdf asdf asdf

提前致谢。


您正在使用冒号作为字段分隔符的一部分,这将导致它消失。 - Fravadona
我确实得到了第一个值ffffffff81000000:当我将其取出时,包括冒号,但是其他值都被分开了,例如48 8d 25 51 3f 60 01 leaq asdf asdf asdf。 - Zeteroo
5个回答

3

使用gnu-awkRS变量(表示记录分隔符):

s=$'ffffffff81000000: 48 8d 25 51 3f 60 01\tleaq asdf asdf asdf'
awk -v RS='^\\S+|[^\t:]+' '{gsub(/^\s*|\s*$/, "", RT); print RT}' <<< "$s"

ffffffff81000000:
48 8d 25 51 3f 60 01
leaq asdf asdf asdf

解释:

  • RS='^\\S+|[^\t:]+':将RS设置为以1个或多个非空格字符开头,或者以1个或多个非制表符、非冒号字符开头。
  • gsub(/^\s*|\s*$/, "", RT):从RT变量中删除开始或结尾的空格,该变量由于RS而得到填充。
  • print RT打印RT变量。

如果您想要同时打印字段的长度,则使用:

awk -v RS='^\\S+|[^\t:]+' '{gsub(/^\s*|\s*$/, "", RT); print RT} END {print "length of field:", NR}' <<< "$s"

ffffffff81000000:
48 8d 25 51 3f 60 01
leaq asdf asdf asdf
length of field: 3

如果你没有 gnu-awk,那么这里有一个与之相同的 POSIX awk 解决方案:
awk '{
   while (match($0, /^[^[:blank:]]+|[^\t:]+/)) {
      print substr($0, RSTART, RLENGTH)
      $0 = substr($0, RSTART+RLENGTH)
   }
}' <<< "$s"

ffffffff81000000:
 48 8d 25 51 3f 60 01
leaq asdf asdf asdf

1
我本来想加一个 while 的解决方案,但你也加进去了,不错的解决方案,谢谢分享。 - RavinderSingh13
1
非常好的match解决方案。小问题 - 我认为您不需要第一个否定。它可以是/[^[:blank:]]+|[^\t:]+/ - Eugene
谢谢。我使用 ^[^[:blank:]]+ 是因为我只想要匹配开头的 1 个或多个非空格字符,而不是后面的字符。 - anubhava

2
您的正则表达式可以简化为:
split($0,field,/: |\t/)

但是即使不包含冒号字符,结果也会相同,因为分隔符模式未包含在分割后的结果中。

如果您想在拆分函数中使用诸如“由冒号前导的空格”之类的复杂模式作为分隔符,则需要使用PCRE,而awk不支持此功能。

这里有一个python示例:

#!/usr/bin/python

import re

s = "ffffffff81000000: 48 8d 25 51 3f 60 01\tleaq asdf asdf asdf"
print(re.split(r'(?<=:) |\t', s))

输出:

['ffffffff81000000:', '48 8d 25 51 3f 60 01', 'leaq asdf asdf asdf']

你会发现冒号包含在结果中。


2

使用您的awk代码并进行一些更改:

echo -e "ffffffff81000000: 48 8d 25 51 3f 60 01\tleaq asdf asdf asdf" | awk -v OFS='\n' '
{
sub(/: */,":\t")
split($0,field,/[\t]/)
print "length of field:" length(field), field[1], field[2],field[3]
}'
length of field:3
ffffffff81000000:
48 8d 25 51 3f 60 01
leaq asdf asdf asdf

正如您所看到的:

  • 添加了一个带有sub()的选项卡,
  • 因此split()的分隔符仅为[\t]
  • OFS\n
  • 最后只需print

1
你可以使用sub: 替换为:\t,并将\t替换为\n。在awk文本行中,除非您的编程操作将其放置在那里,否则您不会找到\n;因此它是一个有用的分隔符。现在,您可以在\n上拆分,并且您的代码将按照您的想象工作:
echo -e "ffffffff81000000: 48 8d 25 51 3f 60 01\tleaq asdf asdf asdf" \
| awk '{sub(/: /,":\t"); gsub(/\t/,"\n"); split($0,field,/\n/)
print "length of field:" length(field)
for (x=1; x<=length(field); x++) print field[x]}'

输出:

length of field:3
ffffffff81000000:
48 8d 25 51 3f 60 01
leaq asdf asdf asdf

1
尽管我建议使用gsub而不是sub,因为可能有更多的制表符分隔字段。 - anubhava

0

我认为对于这样的工作,你应该使用GNU awk来作为match()函数的第三个参数,而不是使用split()函数。

$ echo -e "ffffffff81000000: 48 8d 25 51 3f 60 01\tleaq asdf asdf asdf" |
awk '
    match($0,/([^:]+:)\s*([^\t]+)\t(.*)/,field) {
        print "length of field:" length(field);for (x in field) print x, field[x]
    }
'
length of field:12
0start 1
0length 58
3start 40
1start 1
2start 19
3length 19
2length 20
1length 17
0 ffffffff81000000: 48 8d 25 51 3f 60 01        leaq asdf asdf asdf
1 ffffffff81000000:
2 48 8d 25 51 3f 60 01
3 leaq asdf asdf asdf

请注意,结果数组包含比仅填充与正则表达式段匹配的字符串的3个字段更多的信息。如果您不需要这些额外的字段,请忽略它们:
$ echo -e "ffffffff81000000: 48 8d 25 51 3f 60 01\tleaq asdf asdf asdf" |
awk '
    match($0,/([^:]+:)\s*([^\t]+)\t(.*)/,field) {
        for (x=1; x<=3; x++) print x, field[x]
    }
'
1 ffffffff81000000:
2 48 8d 25 51 3f 60 01
3 leaq asdf asdf asdf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接