GNU awk、FPAT 和匹配负字符串正则表达式与正则表达式和特殊字符

GNU awk, FPAT and matching negative string regex with regex and special chars

TL(请参阅问题末尾的 TL;DR)

我使用管道作为字段分隔符 (|) 和反斜杠引号对作为引号 (\") 来引用数据中带有分隔符的字段,例如:

1|\"2\"|\"3.1|3.2\"|4  # basically 1, 2, 3.1|3.2, 4

即(在 awk 中):

==1
==\"2\"
==\"3.1|3.2\"
==4

我决定尝试使用 GNU awk 的 FPAT 来解决现场问题,因为将否定匹配正则表达式写入 \" 似乎并不那么糟糕。

我是约this answer to Regular expression to match a line that doesn't contain a word with a link to (an offsite link) an online generator of negative regular expressions given an input phrase.

来的

由于生成器目前仅支持字母数字和space字符\"(反斜杠引号)被替换为bq并且生成器提供的正则表达式:

^([^b]|b+[^bq])*b*$ 

| 替换为 p 并且上面的数据替换为:

1pbq2bqpbq3.1p3.2bqp4
1|\"2\"|\"3.1|3.2\"|4  # original for comparision

来自 GNU awk 文档 (FPAT="([^,]*)|(\"[^\"]+\")") 的示例 FPAT 用于生成 FPAT:

FPAT="([^p]*)|(bq([^b]|b+[^bq])*b*bq)"

并完成了试验:

$ gawk 'BEGIN {
    FPAT="([^p]*)|(bq([^b]|b+[^bq])*b*bq)"
    OFS=ORS
}
{
    print ,,,
}' data

哪个输出:

1
bq2bq
bq3.1p3.2bq
4

没错。在生成的程序中用 |"s 替换 pqs:

$ gawk 'BEGIN {
    FPAT="([^|]*)|(b\"([^b]|b+[^b\"])*b*b\")"
    OFS=ORS
}
{
    print ,,,
}' data

输出:

1
b"2b"
b"3.1|3.2b"
4

还是对的。然而,当用 \s 替换 bs 并添加一些转义时,导致:

(TL;DR 如何修复下面脚本中的转义)

$ gawk 'BEGIN {
    FPAT="([^|]*)|(\\"([^\]|\+[^\\"])*\*\\")"
    OFS=ORS
} 
{
    print ,,,
}' data

并且输出失败或与之前的不同:

1
\"2\"
\"3.1
3.2\"

所以我的 \ 可能有问题,但在尝试和犯错太多次之后,我的脑子里充满了反斜杠,所有的想法几乎都逃脱了(双关语意)。由于社区是关于分享的,所以我想和你们分享我的头痛。

编辑:显然它与引号中的反斜杠有关,因为 if 而不是定义 FPAT="..." 我使用 GNU awk's strongly typed 输入 FPAT=@/.../ 我得到正确的输出:

$ gawk 'BEGIN {
    FPAT=@/([^|]*)|(\\"([^\]|\+[^\\"])*\*\\")/
    OFS=ORS
} 
{
    print ,,,
}' data

现在输出:

1
\"2\"
\"3.1|3.2\"
4

你好像想用[^\\"]来表示not the string \",但其实不是那个意思,它的意思是neither the char \ nor the char "。您 需要 FPAT 正则表达式的那部分中有一个单独的字符来求反,所以​​方法是将输入中的每个 \" 转换为一个单独的字符不能出现在输入中(我在下面使用 \n 因为通常是 RS 但你可以使用任何不能出现在记录中的字符),然后将记录拆分为字段,然后在使用每个单独的字段之前恢复 \"s:

$ cat tst.awk
BEGIN { FPAT="([^|]*)|(\n[^\n]+\n)" }
{
    gsub(/\"/,"\n")              # Replace each\" with \n in the record
    [=10=] = [=10=]                       # Re-split the record into fields
    for (i=1; i<=NF; i++) {
        gsub("\n","\\"",$i)      # Replace each \n with \" in the field
        print "$"i"=" $i
    }
}

$ awk -f tst.awk file
=1
=\"2\"
=\"3.1|3.2\"
=4

如果您的输入中没有不能出现的特定字符,那么很容易操纵您的输入,这样您喜欢的任何字符在字段拆分期间都不会出现(我再次使用 \n在这里,但这次即使您的输入是包含 \ns 的多行记录,假设您适当地设置了 RS 以允许读取多行记录,它也会工作):

$ cat tst.awk
BEGIN { FPAT="([^|]*)|(\n[^\n]+\n)" }
{
    gsub(/@/,"@A")
    gsub(/\n/,"@B")
    gsub(/\"/,"\n")
    [=12=] = [=12=]
    for (i=1; i<=NF; i++) {
        gsub("\n","\\"",$i)
        gsub("@B","\n",$i)
        gsub("@A","@",$i)
        print "$"i"=" $i
    }
}

$ awk -f tst.awk file
=1
=\"2\"
=\"3.1|3.2\"
=4