【问题标题】:AWK FPAT not working as expected for string parsingAWK FPAT 无法按预期进行字符串解析
【发布时间】:2021-02-10 11:09:36
【问题描述】:

我必须解析一个非常长的字符串(来自标准输入)。它基本上是一个 .sql 文件。我必须从中获取数据。我正在努力解析数据,以便可以将其转换为 csv。为此,我正在使用 awk。就我而言,样本 sn-p(两条记录)如下:

b="(abc@xyz.com,www.example.com,'field2,(2)'),(dfr@xyz.com,www.example.com,'field0'),"
echo $b|awk 'BEGIN {FPAT = "([^\\)]+)|('\''[^'\'']+'\'')"}{print $1}'

在我的正则表达式中,我说的是“)”括号上的拆分,或者如果找到单引号,则忽略所有文本,直到找到最后一个引号。但我的输出如下:

(abc@xyz.com,www.example.com,'field2,(2

我期待这个输出

(abc@xyz.com,www.example.com,'field2,(2)'

我的代码中的问题在哪里。我搜索了很多并检查了 awk 手册,但没有成功。

【问题讨论】:

  • 试试awk 'BEGIN {FPAT = "(,|'\''[^'\'']+'\''|\\([^()]*\\)|[^[:space:]()'\''])+"}{print $1}'
  • 所有'\''s 都令人困惑。我建议您首先使用' 以外的字符(例如#)开发您的正则表达式,然后将每个# 更改为\047(或'\'',如果您真的想使用它)。因此,在您处理它时将您的输入更改为b="(abc@xyz.com,www.example.com,#field2,(2)#),(dfr@xyz.com,www.example.com,#field0#),",将您的FPAT 更改为"([^\\)]+)|(#[^#]+#)",然后在您使用“正常”字符找出正则表达式后将其全部更改。你真的是指[^\\)] - 除了反斜杠或)以外的任何字符?

标签: sql regex bash awk string-parsing


【解决方案1】:

我在下面的第一个答案是错误的,您正在尝试做的事情有一个 ERE:

$ echo "$b" | awk -v FPAT="[(]([^)]|'[^']*')*)" '{for (i=1; i<=NF; i++) print $i}'
(abc@xyz.com,www.example.com,'field2,(2)')
(dfr@xyz.com,www.example.com,'field0')

原始答案,另一种方法:

您需要先通过 2 次方法将引用字段中的所有 )s 替换为输入中尚不存在的内容(例如 RS),然后识别 (...) 字段并将 RS 放回原处在打印之前发送到)s:

$ echo "$b" |
awk -F"'" -v OFS= '
    {
        for (i=2; i<=NF; i+=2) {
            gsub(/)/,RS,$i)
            $i = FS $i FS
        }
        FPAT = "[(][^)]*)"
        $0 = $0
        for (i=1; i<=NF; i++) {
            gsub(RS,")",$i)
            print $i
        }
        FS = FS
    }
'
(abc@xyz.com,www.example.com,'field2,(2)')
(dfr@xyz.com,www.example.com,'field0')

由于 FPAT,上述内容仅适用于 gawk(或者我们可以使用 gawk patsplit()),而其他 awk 则使用了 while-match()-substr() 循环:

$ echo "$b" |
awk -F"'" -v OFS= '
    {
        for (i=2; i<=NF; i+=2) {
            gsub(/)/,RS,$i)
            $i = FS $i FS
        }
        while ( match($0,/[(][^)]*)/) ) {
            field = substr($0,RSTART,RLENGTH)
            gsub(RS,")",field)
            print field
            $0 = substr($0,RSTART+RLENGTH)
        }
    }
'
(abc@xyz.com,www.example.com,'field2,(2)')
(dfr@xyz.com,www.example.com,'field0')

【讨论】:

    【解决方案2】:

    使用您在 GNU awk 中显示的示例编写和测试。这可以在简单的字段分隔符设置中完成,请尝试以下一次,其中b 是您的 shell 变量,其中包含您的显示值。

    echo "$b" | awk -F'\\),\\(' '{print $1}'
    (abc@xyz.com,www.example.com,'field2,(2)'
    

    说明:只需将awk 程序的字段分隔符设置为\\),\\( 以供您输入并打印它的第一个字段。

    【讨论】:

      【解决方案3】:

      Ed 建议的类似正则表达式方法,但我通常更喜欢使用 RSRT 而不是 FPAT

      b="(abc@xyz.com,www.example.com,'field2,(2)'),(dfr@xyz.com,www.example.com,'field0'),"
      awk -v RS="[(]('[^']*'|[^)])*[)]" 'RT {print RT}' <<< "$b"
      (abc@xyz.com,www.example.com,'field2,(2)')
      (dfr@xyz.com,www.example.com,'field0')
      

      【讨论】:

        【解决方案4】:

        如果你想接近一关,不妨试试这个

        {mawk/mawk2/gawk} 'BEGIN { OFS = FS = "\047"; ORS = RS = "\n";
        
                XFS = "\376\004\377"; 
                XRS = "\051" ORS;
            
            } ! /[\051]/ { print; next; } { for (x=1; x <= NF; x += 2) { 
        
                gsub(/[\051][^\050]*/, XFS, $(x)); } } gsub(XFS, XRS) || 1'
        

        我用 2 个 gsub 这样做,以防它开始发送下面的行并产生意想不到的后果。 \051 = ")", \050 是开放的。

        • 进一步增强了它,告诉它在没有找到右括号的情况下立即打印并继续(所以根本不需要拆分)

        一旦我用单引号 \047 将其拆分后,它只会循环遍历奇数字段(因为偶数字段恰好是您要避免在单引号中截断的字段)。

        对于 XFS,只需使用几乎不可能遇到的字节来选择您选择的任意组合。如果您想安全起见,可以测试该行中是否存在 XFS,并使用一些替代组合。基本上是在不会与实际输入数据发生冲突的行中间插入一个分隔符。它本身并不是万无一失的,但遇到 UTF16 字节顺序标记和 ASCII 控制字符组合的可能性相当低。

        (如果您遇到 XFS,很可能您一开始就已经损坏了数据,因为 300 系列八进制必须后跟 200 系列八进制才能成为有效的 UTF8)

        这样,我根本不需要 FPAT。

        *在结尾处更新为“|| 1”作为安全保护,但实际上并不需要。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-07-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-12
          相关资源
          最近更新 更多