如何拆分但在python中忽略带引号的字符串中的分隔符?

时间:2010-05-07 02:13:06

标签: python regex

我需要在分号上分割这样的字符串。但是我不想拆分字符串('或')内的分号。我没有解析文件;只是一个没有换行符的简单字符串。

part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5

结果应该是:

  • 第1部分
  • “这是;第2部分;”
  • '这是;第3部分
  • 第4部分
  • 这个“是;部分”5

我认为这可以用正则表达式完成,但如果没有;我对另一种方法持开放态度。

17 个答案:

答案 0 :(得分:49)

大多数答案似乎都过于复杂。您需要返回引用。你需要依赖re.findall是否给出重叠匹配。鉴于无法使用csv模块解析输入,因此正则表达式是唯一可行的方法,您只需使用与字段匹配的模式调用re.split。

请注意,匹配字段比匹配分隔符要容易得多:

import re
data = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
PATTERN = re.compile(r'''((?:[^;"']|"[^"]*"|'[^']*')+)''')
print PATTERN.split(data)[1::2]

,输出为:

['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']

正如Jean-Luc Nacif Coelho正确指出的那样,这将无法正确处理空组。取决于可能或不重要的情况。如果它确实很重要,可以通过例如将';;'替换为';<marker>;'来处理它,其中<marker>必须是一些你知道不会出现的字符串(没有分号)在拆分前的数据中。您还需要在以下情况后恢复数据:

>>> marker = ";!$%^&;"
>>> [r.replace(marker[1:-1],'') for r in PATTERN.split("aaa;;aaa;'b;;b'".replace(';;', marker))[1::2]]
['aaa', '', 'aaa', "'b;;b'"]

然而,这是一个kludge。还有更好的建议吗?

答案 1 :(得分:27)

re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)

每次找到分号时,前瞻扫描整个剩余的字符串,确保存在偶数个单引号和偶数个双引号。 (双引号字段内的单引号,反之亦然。)如果前瞻成功,分号就是分隔符。

Duncan's solution不同,{{3}}与字段匹配而不是分隔符,这个字段与空字段没有问题。 (甚至不是最后一个:与许多其他split实现不同,Python不会自动丢弃尾随的空字段。)

答案 2 :(得分:14)

>>> a='A,"B,C",D'
>>> a.split(',')
['A', '"B', 'C"', 'D']

It failed. Now try csv module
>>> import csv
>>> from StringIO import StringIO
>>> data = StringIO(a)
>>> data
<StringIO.StringIO instance at 0x107eaa368>
>>> reader = csv.reader(data, delimiter=',') 
>>> for row in reader: print row
... 
['A,"B,C",D']

答案 3 :(得分:11)

以下是带注释的pyparsing方法:

from pyparsing import (printables, originalTextFor, OneOrMore, 
    quotedString, Word, delimitedList)

# unquoted words can contain anything but a semicolon
printables_less_semicolon = printables.replace(';','')

# capture content between ';'s, and preserve original text
content = originalTextFor(
    OneOrMore(quotedString | Word(printables_less_semicolon)))

# process the string
print delimitedList(content, ';').parseString(test)

['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 
 'this "is ; part" 5']

通过使用pyparsing提供的quotedString,您还可以获得对转义引号的支持。

您还不清楚如何在分号分隔符之前或之后处理前导空格,并且示例文本中的所有字段都没有。 Pyparsing会将“a; b; c”解析为:

['a', 'b', 'c']

答案 4 :(得分:9)

您似乎有一个分号分隔的字符串。为什么不使用csv模块来完成所有艰苦的工作?

在我的头顶,这应该工作

import csv 
from StringIO import StringIO 

line = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''

data = StringIO(line) 
reader = csv.reader(data, delimiter=';') 
for row in reader: 
    print row 

这应该给你一些像 ("part 1", "this is ; part 2;", 'this is ; part 3', "part 4", "this \"is ; part\" 5")

修改
不幸的是,由于混合字符串引号(单引号和双引号),这不太合适(即使你按照我的意图使用StringIO)。你实际得到的是

['part 1', 'this is ; part 2;', "'this is ", " part 3'", 'part 4', 'this "is ', ' part" 5']

如果您可以将数据更改为仅在适当的位置包含单引号或双引号,那么它应该可以正常工作,但这种方式可以稍微否定一下这个问题。

答案 5 :(得分:3)

虽然可以通过前瞻/后端/后向引用来完成PCRE,但由于需要匹配平衡的引号对,实际上并不是真正的regex设计任务。

相反,最好只创建一个迷你状态机,然后像这样解析字符串。

修改

事实证明,由于Python re.findall的便利附加功能可以保证非重叠匹配,因此使用Python中的正则表达式可能会比原本更直接。有关详细信息,请参阅注释。

但是,如果您对非正则表达式的实现看起来很好奇:

x = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""

results = [[]]
quote = None
for c in x:
  if c == "'" or c == '"':
    if c == quote:
      quote = None
    elif quote == None:
      quote = c
  elif c == ';':
    if quote == None:
      results.append([])
      continue
  results[-1].append(c)

results = [''.join(x) for x in results]

# results = ['part 1', '"this is ; part 2;"', "'this is ; part 3'",
#            'part 4', 'this "is ; part" 5']

答案 6 :(得分:3)

>>> x = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> import re
>>> re.findall(r'''(?:[^;'"]+|'(?:[^']|\\.)*'|"(?:[^']|\\.)*")+''', x)
['part 1', "this is ';' part 2", "'this is ; part 3'", 'part 4', 'this "is ; part" 5']

答案 7 :(得分:2)

我们可以创建自己的函数

def split_with_commas_outside_of_quotes(string):
    arr = []
    start, flag = 0, False
    for pos, x in enumerate(string):
        if x == '"':
            flag= not(flag)
        if flag == False and x == ',':
            arr.append(string[start:pos])
            start = pos+1
    arr.append(string[start:pos])
    return arr

答案 8 :(得分:1)

因为你没有'\ n',所以用它来代替任何';'那不在引号字符串中

>>> new_s = ''
>>> is_open = False

>>> for c in s:
...     if c == ';' and not is_open:
...         c = '\n'
...     elif c in ('"',"'"):
...         is_open = not is_open
...     new_s += c

>>> result = new_s.split('\n')

>>> result
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']

答案 9 :(得分:1)

此正则表达式将执行此操作:(?:^|;)("(?:[^"]+|"")*"|[^;]*)

答案 10 :(得分:0)

我的方法是将所有未引用的分号出现替换为另一个永远不会出现在文本中的字符,然后拆分该字符。以下代码使用带有函数参数的re.sub函数来搜索和替换所有出现的srch字符串,而不是用单引号或双引号或括号,括号或大括号括起来,并带有repl字符串:

def srchrepl(srch, repl, string):
    """
    Replace non-bracketed/quoted occurrences of srch with repl in string.
    """
    resrchrepl = re.compile(r"""(?P<lbrkt>[([{])|(?P<quote>['"])|(?P<sep>["""
                          + srch + """])|(?P<rbrkt>[)\]}])""")
    return resrchrepl.sub(_subfact(repl), string)


def _subfact(repl):
    """
    Replacement function factory for regex sub method in srchrepl.
    """
    level = 0
    qtflags = 0
    def subf(mo):
        nonlocal level, qtflags
        sepfound = mo.group('sep')
        if  sepfound:
            if level == 0 and qtflags == 0:
                return repl
            else:
                return mo.group(0)
        elif mo.group('lbrkt'):
            if qtflags == 0:
                level += 1
            return mo.group(0)
        elif mo.group('quote') == "'":
            qtflags ^= 1            # toggle bit 1
            return "'"
        elif mo.group('quote') == '"':
            qtflags ^= 2            # toggle bit 2
            return '"'
        elif mo.group('rbrkt'):
            if qtflags == 0:
                level -= 1
            return mo.group(0)
    return subf

如果您不关心括号中的字符,可以大量简化此代码 假设您想使用竖线或竖条作为替代字符,您可以这样做:

mylist = srchrepl(';', '|', mytext).split('|')

BTW,它使用Python 3.1中的nonlocal,如果需要,将其更改为全局。

答案 11 :(得分:0)

即使我确定有一个干净的正则表达式解决方案(到目前为止我喜欢@ noiflection的答案),这是一个快速而又脏的非正则表达式的答案。

s = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""

inQuotes = False
current = ""
results = []
currentQuote = ""
for c in s:
    if not inQuotes and c == ";":
        results.append(current)
        current = ""
    elif not inQuotes and (c == '"' or c == "'"):
        currentQuote = c
        inQuotes = True
    elif inQuotes and c == currentQuote:
        currentQuote = ""
        inQuotes = False
    else:
        current += c

results.append(current)

print results
# ['part 1', 'this is ; part 2;', 'this is ; part 3', 'part 4', 'this is ; part 5']

(我从来没有把这类东西放在一起,随意批评我的表格!)

答案 12 :(得分:0)

一般化解决方案:

import re
regex = '''(?:(?:[^{0}"']|"[^"]*(?:"|$)|'[^']*(?:'|$))+|(?={0}{0})|(?={0}$)|(?=^{0}))'''

delimiter = ';'
data2 = ''';field 1;"field 2";;'field;4';;;field';'7;'''
field = re.compile(regex.format(delimiter))
print(field.findall(data2))

输出:

['', 'field 1', '"field 2"', '', "'field;4'", '', '', "field';'7", '']

此解决方案:

  • 捕获所有空组(包括开头和结尾)
  • 适用于大多数流行的分隔符,包括空格,制表符和 逗号
  • 将其他类型的引号内的引号视为非特殊字符
  • 如果遇到不匹配的未引用引号,则将该行的剩余部分视为引用

答案 13 :(得分:0)

尽管主题很旧,以前的答案也很有效,但我还是建议在python中实现自己的split函数。

如果您不需要处理大量字符串并且可以轻松自定义,那么此方法就很好。

这是我的职能:

# l is string to parse; 
# splitchar is the separator
# ignore char is the char between which you don't want to split

def splitstring(l, splitchar, ignorechar): 
    result = []
    string = ""
    ignore = False
    for c in l:
        if c == ignorechar:
            ignore = True if ignore == False else False
        elif c == splitchar and not ignore:
            result.append(string)
            string = ""
        else:
            string += c
    return result

因此您可以运行:

line= """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
splitted_data = splitstring(line, ';', '"')

结果:

['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']

优点是此函数可用于空字段以及字符串中任意数量的分隔符。

希望这会有所帮助!

答案 14 :(得分:0)

无需拆分分隔符模式,只需捕获所需的内容即可:

>>> import re
>>> data = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> re.findall(r';([\'"][^\'"]+[\'"]|[^;]+)', ';' + data)
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ', ' part" 5']

答案 15 :(得分:0)

最简单的就是使用shlex(简单词法分析)——Python内置的一个模块

import shlex
shlex.split("""part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5 """ )

['part',
 '1;this is ; part 2;;this is ; part 3;part',
 '4;this',
 'is ; part',
 '5']

答案 16 :(得分:-1)

在我看来,这是一个半优雅的解决方案。

新解决方案:

import re
reg = re.compile('(\'|").*?\\1')
pp = re.compile('.*?;')
def splitter(string):
    #add a last semicolon
    string += ';'
    replaces = []
    s = string
    i = 1
    #replace the content of each quote for a code
    for quote in reg.finditer(string):
        out = string[quote.start():quote.end()]
        s = s.replace(out, '**' + str(i) + '**')
        replaces.append(out)
        i+=1
    #split the string without quotes
    res = pp.findall(s)

    #add the quotes again
    #TODO this part could be faster.
    #(lineal instead of quadratic)
    i = 1
    for replace in replaces:
        for x in range(len(res)):
            res[x] = res[x].replace('**' + str(i) + '**', replace)
        i+=1
    return res

旧解决方案:

我选择匹配,如果有一个开场报价并等待它关闭,匹配结束分号。你想要匹配的每个“部分”需要以分号结束。 所以这匹配的东西是这样的:

  • 'foobar的; .sska';
  • “akjshd; asjkdhkj ..”;
  • asdkjhakjhajsd.jhdf;

代码:

mm = re.compile('''((?P<quote>'|")?.*?(?(quote)\\2|);)''')
res = mm.findall('''part 1;"this is ; part 2;";'this is ; part 3';part 4''')

您可能需要对res进行一些后处理,但它包含您想要的内容。