awk中的多个字段分隔符单引号''和双引号“”

时间:2015-04-10 11:01:50

标签: regex awk

之前我曾要求在两个连续的内容中打印文本。 &#34 ;.例如,我有以下字符串:

gfdg "jkfgh" "jkfd fdgj fd-" ghjhgj
gfggf "kfdjfdgfhbg" "fhfghg" jhgj
jhfjhg "dfgdf" fgf
fgfdg "dfj jfdg jhfgjd" "hfgdh jfdhgd jkfghfd" hgjghj

我想只打印以下内容:

"jkfgh" "jkfd fdgj fd-"
"kfdjfdgfhbg" "fhfghg"
"dfgdf"
"dfj jfdg jhfgjd" "hfgdh jfdhgd jkfghfd"

我得到了使用此命令的答案:

awk -F'"' '{for (i=2;i<5;i+=2) printf "%s%s%s%s", FS, $i, FS, (i>5-2?"\n":" ")}' sample.txt

现在我必须在我的问题中添加' '。即我的文字可以在' '以及" "内。以下示例:

gfdg "jkfgh" "jkfd fdgj fd-" ghjhgj
gfggf "kfdjfdgfhbg" "fhfghg" jhgj
jhfjhg "dfgdf 'ffdg' gfd" "dgffd 'fdg'"fgf
fgfdg 'dfj "jfdg" jhfgjd' 'hfgdh jfdhgd jkfghfd' hgjghj

我想得到以下结果:

"jkfgh" "jkfd fdgj fd-"
"kfdjfdgfhbg" "fhfghg"
"dfgdf 'ffdg' gfd" "dgffd 'fdg'"
'dfj "jfdg" jhfgjd' 'hfgdh jfdhgd jkfghfd'
有人可以帮帮我吗?

6 个答案:

答案 0 :(得分:3)

{
  a = ""
  s = $0
  # while s contains a delimiter (either " or ')
  while (match(s, /['"]/)) {
    # save the delimiter
    c = substr(s, RSTART, 1)
    # remove up to and including the delimiter
    s = substr(s, RSTART + 1)
    # find the matching delimiter
    i = index(s, c)
    # append the saved delimiter and the first segment of s to the accumulator
    a = a " " c substr(s, 1, i)
    # remove the segment
    s = substr(s, i + 1)
  }
  # print the accumulator (dropping the first space)
  print substr(a, 2)
}

答案 1 :(得分:2)

最简单的事情可能是一次一个字符:

$ cat tst.awk
BEGIN { FS="" }
{
    rec = ""
    for (i=1;i<=NF;i++) {
        if ( ($i=="\"") && !inSq ) {
            rec = rec (inDq ? $i : (rec ? " " : ""))
            inDq = !inDq
        }
        else if ( ($i=="'") && !inDq ) {
            rec = rec (inSq ? $i : (rec ? " " : ""))
            inSq = !inSq
        }

        if ( inDq || inSq ) {
            rec = rec $i
        }
    }
    print rec
}

$ awk -f tst.awk file
"jkfgh" "jkfd fdgj fd-"
"kfdjfdgfhbg" "fhfghg"
"dfgdf 'ffdg' gfd" "dgffd 'fdg'"
'dfj "jfdg" jhfgjd' 'hfgdh jfdhgd jkfghfd'

你可以在gawk中使用FPAT,但是我可能不会考虑它。即使报价中的换行符有多种方式,也可以通过gawk中的RS='^$'将整个文件作为一条记录读取来实现上述功能。

我真的很喜欢Dave Sines&#39;回答(https://stackoverflow.com/a/29564199/1745001),但认为它可能更简洁,所以我按摩它:

$ cat tst.awk
{
    rec = ""
    while (match($0,/['"]/)) {
        delim   = substr($0,RSTART,1)
        fldLgth = index(substr($0,RSTART+1),delim) + 1
        rec     = (rec ? rec " " : "") substr($0,RSTART,fldLgth)
        $0      = substr($0,RSTART+fldLgth)
    }
    print rec
}
$ awk -f tst.awk file
"jkfgh" "jkfd fdgj fd-"
"kfdjfdgfhbg" "fhfghg"
"dfgdf 'ffdg' gfd" "dgffd 'fdg'"
'dfj "jfdg" jhfgjd' 'hfgdh jfdhgd jkfghfd'

如果您喜欢,请接受戴夫的回答,并将此作为替代实施。

答案 2 :(得分:1)

https://stackoverflow.com/a/29513125/45375引用我的答案的核心 - 在那里你提出了基本相同的问题(只是被一些误解所模糊)。

如果你有 GNU Awk ,你可以近似对引用字符串的识别 使用特殊FPAT变量,而不是定义分隔符来分割行,允许定义描述字段的正则表达式(并忽略未识别的标记)就这样):

gawk -v FPAT="\"[^\"]*\"|'[^']*'" '{
  for(i=1;i<=NF;++i) printf "%s%s", $i, (i==NF ? "\n" : " ")
}' sample.txt

适用于单引号和双引号字符串,但 支持嵌入式转义引号 相同类型

说明:

  • FPAT="\"[^\"]*\"|'[^']*'"将字段定义为双引号或单引号字符串,甚至是空字符串。
  • 请注意,这会自动排除每个输入行上的不带引号的令牌 - 它们 不会反映在$1,...和NF中。
  • 因此,循环for(i=1;i<=NF;++i)已经仅限于枚举匹配的字段。根据需要,字段确实包括封闭引号。

答案 3 :(得分:0)

真正的要求笼罩在迷茫的迷雾之中,但强大且一般地解析可能是双引号或单引号的空白分隔的标记的主题是一个有趣的主题。

即使可以使用awk完成,也很麻烦,正如现有答案所证明的那样; awk的字段解析功能不直接支持带引号的字符串。

这是一个更简单的perl解决方案,它使用了Text::Parsewords module - 您的perl发布可能会或可能不会发布(例如,预装在OSX上) 10.10,但不是Ubuntu 14.04):

perl -MText::Parsewords -lne '
  my @flds = Text::ParseWords::parse_line("\\s+", 1, $_);
  print join(" ", grep /^["\047]/, @flds);
' sample.txt
  • Text::ParseWords::parse_line("\\s+", 1, $_)将每个输入行($_)解析为令牌,基于空格作为分隔符,识别单引号和双引号字符串,支持\ -escaped 嵌入相同类型的引号; 1作为第二个参数表示引号应保留
  • grep /^["\047]/, @flds仅匹配并返回以"'开头的代币('表示为转义序列\047,因为{{1}无法直接嵌入单引号的 shell 字符串中。)
  • '以空格作为分隔符连接结果标记并打印结果。

警告:此解决方案在一个方面与OP的所需示例输出不同:print join(" ", ...被识别为整个的令牌,而不仅仅是"dgffd 'fdg'"fgf前缀。<登记/> 如果您在这种情况下只需要前缀,请使用以下内容作为Perl脚本的第二行,但请注意,这样做意味着提取将因嵌入式转义引号而出现故障:

"dgffd 'fdg'"

答案 4 :(得分:0)

由于对您的其他问题(隐式)的特定评论问题否认它只是您要排除的第一个最后一个单词,并且因为您的(有限)示例都没有显示不需要的嵌入式裸文本:

BEGIN {
    FS = ""
}
{
for (CharFromStart=1;CharFromStart<=NF;CharFromStart++) {
        if ( $CharFromStart ~/"|'/) {
           break
        }
    }
for (CharFromEnd=NF;CharFromEnd>0;CharFromEnd--) {
        if ( $CharFromEnd ~/"|'/) {
           break
        }
    }
if ( CharFromStart <= CharFromEnd ) {
    print ">"substr($0,CharFromStart,(CharFromEnd-CharFromStart+1))"<"
    }
else {
    print "Move along please, nothing to see here"
    }
}

使用一些增强的测试数据:

gfdg "jkfgh" "jkfd fdgj fd-" ghjhgj
gfggf "kfdjfdgfhbg" "fhfghg" jhgj
jhfjhg "dfgdf 'ffdg' gfd" "dgffd 'fdg'"fgf
fgfdg 'dfj "jfdg" jhfgjd' 'hfgdh jfdhgd jkfghfd' hgjghj
jhfjhg "dfgdf 'ffdg' gfd" "dgffd 'fdg'" fgf
jhfjhg "dfgdf         'ffdg   ' gfd"        "        dgffd 'fdg'"fgf
kiuj jajdj "dfgdf         'ffdg   ' gfd"        "        dgffd 'fdg'" s fgf
dslkjflkdsj ldsk gfdkg ;kdsa;lfkdsl f ljflkdsjf l
ldsfl dsjfhkjds dshfjkhds kdskjfhdskjhf " dsflkdsjflk
' dlfkjdslfj kdsjflkdslj djlkfjdslkjf 
dskfjds dshfdkjsh dshjkjfhds "
"""

给出:

>"jkfgh" "jkfd fdgj fd-"<
>"kfdjfdgfhbg" "fhfghg"<
>"dfgdf 'ffdg' gfd" "dgffd 'fdg'"<
>'dfj "jfdg" jhfgjd' 'hfgdh jfdhgd jkfghfd'<
>"dfgdf 'ffdg' gfd" "dgffd 'fdg'"<
>"dfgdf         'ffdg   ' gfd"        "        dgffd 'fdg'"<
>"dfgdf         'ffdg   ' gfd"        "        dgffd 'fdg'"<
Move along please, nothing to see here
>"<
>'<
>"<
>"""<

这可以通过将Field Separator的FS内置变量设置为空来实现。这会导致该行上的每个字符都被视为一个单独的字段。

一个循环&#34; up&#34;使用$ variablename查找第一个引号或撇号的行。一个循环&#34; down&#34;查找最后一个引号或撇号的行。

快速检查至少找到一个,并打印从第一个引号或撇号到最后一行的行的子串。

如果线上只有一个引号或撇号,它将被打印,但很容易不打印。

如果引用或撇号是&#34;不平衡&#34;,提取没有问题(除非您想要实际知道)。相对于第一个引用或撇号,嵌入的空格,制表符或类似内容将保持原样。

答案 5 :(得分:0)

一种简单的方法

awk '{$1="";sub(/^ /,"")sub(/fgf/,"")}NR!=3{NF=NF-1}1' file
    "jkfgh" "jkfd fdgj fd-"
    "kfdjfdgfhbg" "fhfghg"
    "dfgdf 'ffdg' gfd" "dgffd 'fdg'"
    'dfj "jfdg" jhfgjd' 'hfgdh jfdhgd jkfghfd'