regex 使用awk模拟“grep -oE”的停止条件

zsbz8rwp  于 2023-04-13  发布在  其他
关注(0)|答案(4)|浏览(106)

我试图用标准的awk调用来模拟GNU grep -Eo
该男子对-o选项的说法是:

-o --only-matching

  仅打印匹配行的匹配(非空)部分,每个部分在单独的输出行上。

现在我有这个代码:

#!/bin/sh

regextract() {
    [ "$#" -ge 2 ] || return 1
    __regextract_ere=$1
    shift
    awk -v FS='^$' -v ERE="$__regextract_ere" '
        {
            while ( match($0,ERE) && RLENGTH > 0 ) {
                print substr($0,RSTART,RLENGTH)
                $0 = substr($0,RSTART+1)
            }
        }
    ' "$@"
}

我的问题是:如果匹配的部分是0-length,我需要继续尝试匹配剩下的行,还是应该移动到下一行(就像我已经做的那样)?我找不到需要前者的input+regex示例,但我觉得它可能存在。有什么想法吗?

k4aesqcs

k4aesqcs1#

下面是一个POSIX awk版本,它可以与a*(或任何POSIX awk regex)一起使用:

echo abcaaaca |
awk -v regex='a*' '
{
    while (match($0, regex)) {
        if (RLENGTH) print substr($0, RSTART, RLENGTH)
        $0 = substr($0, RSTART + (RLENGTH > 0 ? RLENGTH : 1))
        if ($0 == "") break
    }
}'

图纸:

a
aaa
a

POSIX awk和grep -E使用POSIX扩展正则表达式,除了awk允许C转义(像\t),但grep -E不允许。如果你想要严格的兼容性,你必须处理这个问题。

fcipmucu

fcipmucu2#

如果您可以考虑gnu-awk解决方案,那么使用RSRT可能会给予grep -Eo的相同行为。

# input data
cat file
FOO:TEST3:11
BAR:TEST2:39
BAZ:TEST0:20

使用grep -Eo

grep -Eo '[[:alnum:]]+' file
FOO
TEST3
11
BAR
TEST2
39
BAZ
TEST0
20

使用gnu-awkRSRT使用相同的正则表达式:

awk -v RS='[[:alnum:]]+' 'RT != "" {print RT}' file
FOO
TEST3
11
BAR
TEST2
39
BAZ
TEST0
20

更多示例:

grep -Eo '\<[[:digit:]]+' file
11
39
20

awk -v RS='\\<[[:digit:]]+' 'RT != "" {print RT}' file
11
39
20
pxiryf3j

pxiryf3j3#

感谢各种各样的评论和回答,我认为我现在有一个工作的,健壮的,(也许)高效的代码:

在AIX/Solaris/FreeBSD/macOS/Linux上测试

#!/bin/sh
  
regextract() {

    [ "$#" -ge 1 ] || return 1
    [ "$#" -eq 1 ] && set -- "$1" -

    awk '
        BEGIN {
            ere = ARGV[1]
            delete ARGV[1]
        }
        {
            tail = $0
            while ( tail != "" && match(tail,ere) ) {
                if (RLENGTH) {
                    print substr(tail,RSTART,RLENGTH)
                    tail = substr(tail,RSTART+RLENGTH)
                } else
                    tail = substr(tail,RSTART+1)
            }
        }
    ' "$@"
}

regextract "$@"

备注:

  • 我沿着文件参数传递ERE字符串,这样awk就不会预处理它(感谢@anubhava指出这一点);C风格的转义序列仍然会被awk的正则表达式引擎翻译(感谢@dan指出这一点)。
  • 因为分配$0会重置所有字段的值,所以我选择FS = '^$'来限制开销

在一个单独的变量中复制$0可以抵消在while循环中分配$0所引起的开销(感谢@EdMorton指出这一点)。

几个例子:
# Multiple matches in a single line:
echo XfooXXbarXXX | regextract 'X*'
X
XX
XXX

# Passing the regex string to awk as a parameter versus a file argument:
echo '[a]' | regextract_as_awk_param '\[a]'
a
echo '[a]' | regextract '\[a]'
[a]

# The regex engine of awk translates C-style escape sequences:
printf '%s\n' '\t' | regextract '\t'
printf '%s\n' '\t' | regextract '\\t'
\t
pqwbnv8z

pqwbnv8z4#

您的代码将无法匹配可能包含零个或多个字符的字符,请考虑以下简单示例,让file.txt内容为

1A2A3

然后

grep -Eo A* file.txt

给出输出

A
A

你的while的条件是match($0,ERE) && RLENGTH > 0,在这种情况下,前一部分为true,但后一部分为false,因为在第一个字符之前找到的匹配是零长度(RSTART被设置为1),因此while的主体将被执行零次。

相关问题