JavaScript如何按空格分割字符串,但忽略引号中的空格(注意不要按冒号分割)?

47

我需要帮助在JavaScript中通过空格(“ ”)拆分字符串,忽略引号表达式内部的空格。

我有这个字符串:

var str = 'Time:"Last 7 Days" Time:"Last 30 Days"';
我期望我的字符串应该被拆分成2个部分:
['Time:"Last 7 Days"', 'Time:"Last 30 Days"']

但我的代码被分成了4部分:

['Time:', '"Last 7 Days"', 'Time:', '"Last 30 Days"']

这是我的代码:

str.match(/(".*?"|[^"\s]+)(?=\s*|\s*$)/g);

谢谢!


虽然相关,但这个链接的问题并不是重复的:_这个_问题明确希望将直接相邻的未引用字符串(例如foo:"bar none")识别为一个_单一_标记(并且也没有提到需要处理转义双引号的需求)。 - mklement0
4个回答

94
s = 'Time:"Last 7 Days" Time:"Last 30 Days"'
s.match(/(?:[^\s"]+|"[^"]*")+/g) 

// -> ['Time:"Last 7 Days"', 'Time:"Last 30 Days"']

解释:

(?:         # non-capturing group
  [^\s"]+   # anything that's not a space or a double-quote
  |         #   or…
  "         # opening double-quote
    [^"]*   # …followed by zero or more chacacters that are not a double-quote
  "         # …closing double-quote
)+          # each match is one or more of the things described in the group

结果是,要修复你原来的表达式,你只需要在组中添加一个+

str.match(/(".*?"|[^"\s]+)+(?=\s*|\s*$)/g)
#                         ^ here.

1
如果您解释一下正则表达式,那么这将是一个很好的答案。 - T.J. Crowder
@Awalias 不仅使用 match 方法就可以了,但是如果之后进行 replace ,您可以使用捕获组等并省略引号。您最好发布一个新问题。 - kch
4
适用于单引号或双引号的 kch 模式修改版:s.match(/(?:[^\s"']+|['"][^'"]*["'])+/g) - jobrad
1
如果引号被转义了,而不使用负向先行断言(由于兼容性),该怎么办? - user10398534
不确定这是否只是误报,但此代码片段被 CodeQL 在 GitHub 上标记为“低效的正则表达式:正则表达式的这一部分可能会在包含许多 '!' 重复的字符串上导致指数回溯。”。有关此问题的问题在此处:https://github.com/github/codeql/issues/5964 - Glenn 'devalias' Grant
显示剩余3条评论

7

支持ES6的解决方案:

  • 除引号内部的空格外,按空格拆分
  • 删除引号但不删除反斜杠转义的引号
  • 转义的引号变成引号

代码:

str.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

输出:

[ 'Time:Last 7 Days', 'Time:Last 30 Days' ]

0

这对我有用...

var myString = 'foo bar "sdkgyu sdkjbh zkdjv" baz "qux quux" skduy "zsk"'; console.log(myString.split(/([^\s"]+|"[^"]*")+/g));

输出: Array ["", "foo", " ", "bar", " ", ""sdkgyu sdkjbh zkdjv"", " ", "baz", " ", ""qux quux"", " ", "skduy", " ", ""zsk"", ""]


0
我知道这个问题是关于正则表达式的,但是你越是用正则表达式来做字符串拆分或子字符串提取的任务,你就越会注意到你的正则表达式的复杂性比你的字符串的复杂性增长得更快。首先,你必须避免按分隔符拆分字符串,如果分隔符在引号区域内,你只需使用一些几乎随机的正则表达式,它以某种方式工作;然后你需要考虑双引号区域,你修改你的正则表达式,使其增长3-4倍;依此类推。最终,你的正则表达式变成了一个如此复杂的东西,没有一个清醒的人会想要支持它。所以,尽管正则表达式的解决方案一开始看起来非常高级和优雅,但我最终停止使用它,并切换到了一个能够考虑引号区域的自定义函数。
interface IZoneToken {
  begin: string;
  end: string;
}

const ZONE_TOKENS: { [key: string]: IZoneToken } = {
  SINGLE_QUOTES: { begin: '\'', end: '\'' },
  DOUBLE_QUOTES: { begin: '\"', end: '\"' },
  ROUND_BRACKETS: { begin: '(' , end: ')' }
};

function splitRespectingZones(input: string, delimiter: string, zone_tokens: IZoneToken[]): string[] {
  let current_substring = '',
    current_zone_token;
  const substrings = [],
    current_zone_tokens = [];

  for (let i = 0; i < input.length; i++) {
    const symbol = input[i];

    if (symbol === delimiter && !current_zone_tokens.length) {
      substrings.push(current_substring);
      current_substring = '';
    } else {
      if (current_zone_token = zone_tokens.find(x => symbol === x.begin || symbol === x.end)) {
        if (current_zone_token === current_zone_tokens.last()) {
          if (current_zone_token.end === symbol) {
            current_zone_tokens.pop()
          } else {
            current_zone_tokens.push(current_zone_token)
          }
        } else {
          current_zone_tokens.push(current_zone_token)
        }
        current_zone_token = undefined;
      }
      current_substring += symbol;
    }
  }
  if (current_substring) {
    substrings.push(current_substring)
  }
  return substrings;
}

如你所见,你可以使用任意数量的自定义区域标记,无论是双引号、花括号还是其他括号。
splitRespectingZones(`some 'ran dom' "str ings" (fo r) ("example")`, ' ', [
  { begin: '\'', end: '\'' },
  { begin: '\"', end: '\"' },
  { begin: '(' , end: ')' }
])

// ['some', "'ran dom'", '"str ings"', '(fo r)', '("example")']

这段话有点啰嗦,但如果你需要修改这个函数,你会喜欢它的逻辑是多么简单直接。
问题中的案例示例:
splitRespectingZones('Time:"Last 7 Days" Time:"Last 30 Days"', ' ', [ { begin: '\"', end: '\"' } ])
// ['Time:"Last 7 Days"', 'Time:"Last 30 Days"']

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