按空格拆分字符串,保留引号内的部分,允许转义引号。

30

我目前有一个正则表达式,可以通过所有空格来分隔字符串,除非它在引号段内:

keywords = 'pop rock "hard rock"';
keywords = keywords.match(/\w+|"[^"]+"/g);
console.log(keywords); // [pop, rock, "hard rock"]

然而,我也希望关键字中可以包含引号,就像这样:

keywords = 'pop rock "hard rock" "\"dream\" pop"';
这应该返回什么。
[pop, rock, "hard rock", "\"dream\" pop"]

最简单的方法是什么?

4个回答

34
你可以将你的正则表达式更改为:
keywords = keywords.match(/\w+|"(?:\\"|[^"])+"/g);

你使用的是(?:\\"|[^"])+而不是[^"]+,它允许出现\"或其他字符,但不允许未转义的引号。

需要注意的是,如果你想让字符串包含一个斜杠,应该写成:

keywords = 'pop rock "hard rock" "\\"dream\\" pop"'; //note the escaped slashes.

此外,\w+[^"]+之间存在轻微的不一致性 - 例如,它将匹配单词"ab*d",但不匹配没有引号的ab*d。考虑改用[^"\s]+,这将匹配非空格字符。


1
我建议您使用 \\. 而不是 \\",因为反斜杠也可以被转义,您不会想错过 "foo\\\\" - Tim Pietzcker
1
考虑这个问题:在字符串"\\" "foo"(为了更清楚只有两个反斜杠),第一个"将与正则表达式开头的字面上的 "匹配。然后,[^"]将匹配第一个 \。剩下的 \" 将被 \\" 匹配(因为它先出现在选择中)。然后, [^"]将匹配空格和正则表达式末尾的 "将匹配 "foo" 的开头引号,从而破坏解析。 - Tim Pietzcker
1
它的工作方式就像应该的一样。"(?:\"|[^"])+ 这个应该是不言自明的"<其实并不是;-),我以前从未在正则表达式中使用过这个,我的同事不得不向我解释。"考虑使用 [^"\s]+ 代替" <这是我已经调整过的东西。感谢您的帮助! - Blaise
1
@Kobi 说得好。对于相关人员,我已经在正则表达式前面添加了(?<!\\)(?:\\\\)*。也就是说,以反斜杠开头,然后必须有偶数个反斜杠(即转义的反斜杠)。换句话说,开头的引号必须由0、2、4、6等数量的反斜杠作为前缀,否则(即1、3等数量的反斜杠)我们将不认为它是开头的引号。 - Timo
在JavaScript中,如果您不想使用引号,您可以只使用keywords = keywords.match(/\w+|"(?:\\"|[^"])+"/g).map((a) -> if a.match(/".+"/g) then a.slice(1, -1) else a)。这不是纯正则表达式,但仍然不需要JavaScript尚未具备的功能:正则表达式后顾。 - Gustavo6046
显示剩余13条评论

10

ES6解决方案支持:

  • 除引号内部分外,按空格拆分
  • 去除引号,但不包括反斜杠转义的引号
  • 转义引号变成引号
  • 引号可以放置在任何位置

代码:

keywords.match(/\\?.|^$/g).reduce((p, c) => {
        if(c === '"'){
            p.quote ^= 1;
        }else if(!p.quote && c === ' '){
            p.a.push('');
        }else{
            p.a[p.a.length-1] += c.replace(/\\(.)/,"$1");
        }
        return  p;
    }, {a: ['']}).a

输出:

[ 'pop', 'rock', 'hard rock', '"dream" pop' ]

1
你的代码真的很难读,但它能够正常工作,而且正是我所需要的。 - Christian Ivicevic
1
这个有效并且消除了引号,这正是我所需要的;虽然阅读起来有些困难,但我同意。 - user.friendly
这个正则表达式会吞掉换行符(\n),但是通过添加s修饰符(/\\?.|^$/gs)可以很容易地修复它。 - undefined

4
如果Kobi的答案对于示例字符串有效,但是当在引号之间有多个连续的转义字符(反斜杠)时,它就无法正常工作,正如Tim Pietzcker在评论中注意到的那样。为了处理这些情况,可以这样编写模式(对于匹配方法):
(?=\S)[^"\s]*(?:"[^\\"]*(?:\\[\s\S][^\\"]*)*"[^"\s]*)*

演示

(?=\S) 确保当前位置至少有一个非空格字符,因为接下来描述的所有允许的子串 (包括引号之间的空格) 都是可选的。

详情:

(?=\S)   # followed by a non-whitespace
[^"\s]*  #"# zero or more characters that aren't a quote or a whitespace
(?: # when a quoted substring occurs:
    "       #"# opening quote
    [^\\"]* #"# zero or more characters that aren't a quote or a backslash
    (?: # when a backslash is encountered:
        \\ [\s\S] # an escaped character (including a quote or a backslash)
        [^\\"]* #"#
    )*
    "         #"# closing quote
    [^"\s]*   #"#
)*

你能否在简单的英语中添加每个部分的意图? - Timo
@Timo:我添加了模式细节。 - Casimir et Hippolyte
1
这个代码可以工作并且保留引号,正如我认为提问者所要求的那样。 - user.friendly
这个很好用!但是如果我想让它也适用于单引号怎么办? - RedGuy11

0

我想指出我和你使用了相同的正则表达式,

/\w+|"[^"]+"/g

但是它在空引号字符串上没有起作用,例如:

"" "hello" "" "hi"

所以我不得不将+量词更改为*。 这给了我:

str.match(/\w+|"[^"]*"/g);

这很好。

(例如:https://regex101.com/r/wm5puK/1)


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