用空格和冒号分割字符串,但如果在引号内则不分割

Posted

技术标签:

【中文标题】用空格和冒号分割字符串,但如果在引号内则不分割【英文标题】:split string by spaces and colon but not if inside quotes 【发布时间】:2016-01-07 13:59:51 【问题描述】:

有这样的字符串:

$str = "dateto:'2015-10-07 15:05' xxxx datefrom:'2015-10-09 15:05' yyyy asdf"

想要的结果是:

[0] => Array (
    [0] => dateto:'2015-10-07 15:05'
    [1] => xxxx
    [2] => datefrom:'2015-10-09 15:05'
    [3] => yyyy
    [4] => asdf
)

我得到了什么:

preg_match_all("/\'(?:[^()]|(?R))+\'|'[^']*'|[^(),\s]+/", $str, $m);

是:

[0] => Array (
    [0] => dateto:'2015-10-07
    [1] => 15:05'
    [2] => xxxx
    [3] => datefrom:'2015-10-09
    [4] => 15:05'
    [5] => yyyy
    [6] => asdf
)

还尝试使用preg_split("/[\s]+/", $str),但如果值在引号之间,则不知道如何转义。谁能告诉我如何,也请解释正则表达式。谢谢!

【问题讨论】:

【参考方案1】:

我会使用 PCRE 动词 (*SKIP)(*F)

preg_split("~'[^']*'(*SKIP)(*F)|\s+~", $str);

DEMO

【讨论】:

谢谢!你介意解释一下"~'[^']*'(*SKIP)(*F)|\s+~"我只是了解其中的一部分,我想全部了解 '[^']*' 匹配所有单引号块,并且以下 (*SKIP)(*F) 使匹配失败。并且下面的|\s+ 匹配所有剩余的空格。【参考方案2】:

通常,当您要拆分字符串时,使用 preg_split 并不是最好的方法(这似乎有点违反直觉,但大多数情况下确实如此)。一种更有效的方法是使用描述所有非分隔符(此处为空格)的模式来查找所有项目(带有preg_match_all):

$pattern = <<<'EOD'
~(?=\S)[^'"\s]*(?:'[^']*'[^'"\s]*|"[^"]*"[^'"\s]*)*~
EOD;

if (preg_match_all($pattern, $str, $m))
    $result = $m[0];

图案细节:

~                    # pattern delimiter

(?=\S)               # the lookahead assertion only succeeds if there is a non-
                     # white-space character at the current position.
                     # (This lookahead is useful for two reasons:
                     #    - it allows the regex engine to quickly find the start of
                     #      the next item without to have to test each branch of the
                     #      following alternation at each position in the strings
                     #      until one succeeds.
                     #    - it ensures that there's at least one non-white-space.
                     #      Without it, the pattern may match an empty string.
                     # )

[^'"\s]*          #"'# all that is not a quote or a white-space

(?:                  # eventual quoted parts
    '[^']*' [^'"\s]*  #"# single quotes
  |
    "[^"]*" [^'"\s]*    # double quotes
)*
~

demo

请注意,使用这个有点长的模式,您的示例字符串的五个项目只需 60 步即可找到。您也可以使用这种更短/更简单的模式:

~(?:[^'"\s]+|'[^']*'|"[^"]*")+~

但它的效率有点低。

【讨论】:

感谢您的详细解答!我想知道更多的事情:“但大多数时候这是真的”是否有经验法则或一些链接我可以阅读关于何时/为什么使用哪个?你是如何编写正则表达式的?你有一个工具可以做到这一点,或者你知道正则表达式规则并把它写下来?如果只是写下来:你是如何学习正则表达式规则的? @caramba:这更像是一个经验法则,但背后的想法相对简单:1)当分隔符必须考虑这种环境时,模式很快变得复杂和低效(特别是如果你需要检查之前的字符是什么,或者是否需要先检查字符串直到结尾)。 2) 有时用否定来定义更容易。 @caramba:关于我如何编写一个模式,一般来说,知识、实践和测试是相关的。例如,(?:[^'\s]+|'[^']*')*+ 这样的模式如果你“展开”它会更有效,就像这样:[^'\s]*(?:'[^']*'[^'\s]*)*+,你可以在 Friedl 书中找到这些信息,但你也可以使用 regex101 或 regexbuddy 来查看它,它显示了需要的步骤。但即使有了知识和食谱,你也总是需要进行实验,尤其是你必须非常了解你的敌人:弦乐。【参考方案3】:

对于您的示例,您可以使用 preg_split 和 negative lookbehind (?&lt;!\d),即:

<?php
$str = "dateto:'2015-10-07 15:05' xxxx datefrom:'2015-10-09 15:05' yyyy asdf";
$matches = preg_split('/(?<!\d)(\s)/', $str);
print_r($matches);

输出:

    Array
    (
        [0] => dateto:'2015-10-07 15:05'
        [1] => xxxx
        [2] => datefrom:'2015-10-09 15:05'
        [3] => yyyy
        [4] => asdf
    )

演示:

http://ideone.com/EP06Nt


正则表达式解释:

(?<!\d)(\s)

Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind) «(?<!\d)»
   Match a single character that is a “digit” «\d»
Match the regex below and capture its match into backreference number 1 «(\s)»
   Match a single character that is a “whitespace character” «\s»

【讨论】:

谢谢!好的“负面回顾”,但是 ` ' ` 定义的黑客在哪里?如果dateto:"has-double-quotes",我怎么能改变?

以上是关于用空格和冒号分割字符串,但如果在引号内则不分割的主要内容,如果未能解决你的问题,请参考以下文章

在空格处分割R字符串,但当空格在单引号之间时不分割

如何用空格分割字符串,不包括Python中双引号之间的空格? [复制]

在 Python 中用冒号分割字符串

oracle 用逗号分割一个带引号的字符串

pyspark 用字符串中的空格分割 csv - jupyter notebook

Java正则表达式用逗号分割字符串,但忽略引号和括号[重复]