파이썬에서 문자열을 공백으로 나눕니다 (따옴표 붙은 부분 문자열 유지)


269

다음과 같은 문자열이 있습니다.

this is "a test"

따옴표 내 공백을 무시하면서 공백으로 나누기 위해 파이썬으로 무언가를 작성하려고합니다. 내가 찾고있는 결과는 다음과 같습니다.

['this','is','a test']

추신. "응용 프로그램에 따옴표 안에 따옴표가 있으면 어떻게됩니까?"


1
이 질문을 해주셔서 감사합니다. pypar 빌드 모듈을 수정하는 데 필요한 것입니다.
Martlark

답변:


392

split내장 shlex모듈 에서 원합니다 .

>>> import shlex
>>> shlex.split('this is "a test"')
['this', 'is', 'a test']

이것은 당신이 원하는 것을 정확하게해야합니다.


13
인용 부호를 유지하려면 "posix = False"를 사용하십시오. shlex.split('this is "a test"', posix=False)반환['this', 'is', '"a test"']
Boon

@MatthewG. Python 2.7.3의 "수정"은 유니 코드 문자열을 전달 shlex.split()하여 UnicodeEncodeError예외 를 트리거 함을 의미합니다 .
Rockallite

57

shlex특히 모듈을 살펴보십시오 shlex.split.

>>> import shlex
>>> shlex.split('This is "a test"')
['This', 'is', 'a test']

40

여기에 정규식 접근 방식이 복잡하고 잘못 보입니다. 정규식 구문은 "공백 또는 따옴표로 묶은 것"을 쉽게 설명 할 수 있고 대부분의 정규식 엔진 (파이썬 포함)이 정규식으로 분할 될 수 있기 때문에 놀랍습니다. 따라서 정규 표현식을 사용하려면 정확히 무엇을 의미하는지 말하지 않겠습니까? :

test = 'this is "a test"'  # or "this is 'a test'"
# pieces = [p for p in re.split("( |[\\\"'].*[\\\"'])", test) if p.strip()]
# From comments, use this:
pieces = [p for p in re.split("( |\\\".*?\\\"|'.*?')", test) if p.strip()]

설명:

[\\\"'] = double-quote or single-quote
.* = anything
( |X) = space or X
.strip() = remove space and empty-string separators

shlex는 아마도 더 많은 기능을 제공 할 것입니다.


1
나는 똑같은 생각을했지만 re.findall (r '[^ \ s "] + |"[^ "] *"'에서 t 대신 [t.strip ( ' "' ')을 제안 할 것입니다. 테스트 " ')]
다리우스 베이컨

2
+1 Shlex보다 훨씬 빠르기 때문에 이것을 사용하고 있습니다.
hanleyp

3
왜 트리플 백 슬래시? 간단한 백 슬래시는 동일하지 않습니까?
Doppelganger

1
실제로, 이것에 대해 내가 싫어하는 한 가지는 따옴표 앞 / 뒤의 모든 것이 올바르게 분리되지 않는다는 것입니다. 이 'PARAMS val1 = "Thing"val2 = "Thing2"'와 같은 문자열이 있으면 문자열이 세 조각으로 나눠 질 것으로 예상되지만 5로 나뉩니다. 정규식을 수행한지 오래되었으므로 지금 솔루션을 사용하여 해결하려고하는 느낌이 들지 않습니다.
leetNightshade

1
정규식을 사용할 때는 원시 문자열을 사용해야합니다.
asmeurer

29

사용 사례에 따라 csv모듈 을 체크 아웃 할 수도 있습니다 .

import csv
lines = ['this is "a string"', 'and more "stuff"']
for row in csv.reader(lines, delimiter=" "):
    print(row)

산출:

['this', 'is', 'a string']
['and', 'more', 'stuff']

2
shlex가 필요한 문자를 제거 할 때 유용
scraplesh

1
CSV의 연속 사용이 큰 따옴표 (사이드 바이 사이드에서와 같이이 "") 하나의 큰 따옴표를 표현하기 "때문에 작은 따옴표에 두 개의 따옴표를 켜집니다 'this is "a string""''this is "a string"""'것 모두지도에['this', 'is', 'a string"']
보리스

15

shlex.split을 사용하여 70,000,000 라인의 오징어 로그를 처리합니다. 너무 느립니다. 그래서 나는 다시 전환했다.

shlex에 성능 문제가있는 경우이 기능을 사용해보십시오.

import re

def line_split(line):
    return re.findall(r'[^"\s]\S*|".+?"', line)

8

이 질문에 정규식 태그가 지정되었으므로 정규식 접근법을 사용하기로 결정했습니다. 따옴표 부분의 모든 공백을 \ x00으로 바꾼 다음 공백으로 나눈 다음 \ x00을 각 부분의 공백으로 바꿉니다.

두 버전 모두 동일한 작업을 수행하지만 splitter는 splitter2보다 약간 읽기 쉽습니다.

import re

s = 'this is "a test" some text "another test"'

def splitter(s):
    def replacer(m):
        return m.group(0).replace(" ", "\x00")
    parts = re.sub('".+?"', replacer, s).split()
    parts = [p.replace("\x00", " ") for p in parts]
    return parts

def splitter2(s):
    return [p.replace("\x00", " ") for p in re.sub('".+?"', lambda m: m.group(0).replace(" ", "\x00"), s).split()]

print splitter2(s)

대신 re.Scanner를 사용해야합니다. 더 안정적입니다 (실제로는 re.Scanner를 사용하여 shlex와 유사하게 구현했습니다).
Devin Jeanpierre

+1 흠, 이것은 꽤 현명한 아이디어이며, 문제를 여러 단계로 나누므로 대답이 끔찍하게 복잡하지 않습니다. Shlex는 그것을 조정하려고해도 내가 필요한 것을 정확하게하지 않았습니다. 그리고 단일 패스 정규식 솔루션은 정말 이상하고 복잡해졌습니다.
leetNightshade

6

성능상의 이유로 re더 빠릅니다. 외부 인용 부호를 유지하는 가장 욕심 많은 연산자를 사용하는 솔루션은 다음과 같습니다.

re.findall("(?:\".*?\"|\S)+", s)

결과:

['this', 'is', '"a test"']

aaa"bla blub"bbb이러한 토큰은 공백으로 분리되지 않으므로 구성을 같이 남겨 둡니다 . 문자열에 이스케이프 문자가 포함되어 있으면 다음과 같이 일치시킬 수 있습니다.

>>> a = "She said \"He said, \\\"My name is Mark.\\\"\""
>>> a
'She said "He said, \\"My name is Mark.\\""'
>>> for i in re.findall("(?:\".*?[^\\\\]\"|\S)+", a): print(i)
...
She
said
"He said, \"My name is Mark.\""

패턴 ""\S일부를 사용하여 빈 문자열과도 일치합니다 .


1
이 솔루션의 또 다른 중요한 장점은 구분 문자 (예 : ,via '(?:".*?"|[^,])+') 와 관련한 융통성입니다 . 인용 문자 (들)에도 동일하게 적용됩니다.
a_guest 2016 년

4

허용되는 주요 문제 shlex 접근 방식 은 따옴표로 묶인 부분 문자열 외부의 이스케이프 문자를 무시하지 않으며 일부 경우에 예기치 않은 결과가 발생한다는 것입니다.

작은 따옴표 또는 큰 따옴표로 묶인 하위 문자열이 유지되도록 입력 문자열을 분할하는 분할 기능이 필요한 다음 유스 케이스가 있습니다. 인용 부호가없는 문자열 내 인용 부호는 다른 문자와 다르게 취급해서는 안됩니다. 예상되는 결과가있는 몇 가지 테스트 사례

입력 문자열 | 예상 출력
=================================================
 'abc def'| [ 'abc', 'def']
 "abc \\ s def"| [ 'abc', '\\ s', 'def']
 ' "abc def"ghi'| [ 'abc def', 'ghi']
 " 'abc def'ghi"| [ 'abc def', 'ghi']
 ' "abc \\"def "ghi'| [ 'abc"def', 'ghi']
 " 'abc \\'def 'ghi"| [ "abc 'def",'ghi ']
 " 'abc \\ s def'ghi"| [ 'abc \\ s def', 'ghi']
 ' "abc \\ s def"ghi'| [ 'abc \\ s def', 'ghi']
 ' ""테스트 "| ['', '테스트']
 " ''테스트"| ['', '테스트']
 "abc'def"| [ "abc'def"]
 "abc'def '"| [ "abc'def '"]
 "abc'def 'ghi"| [ "abc'def '",'ghi ']
 "abc'def'ghi"| [ "abc'def'ghi"]
 'abc "def'| [ 'abc"def']
 'abc "def"'| [ 'abc "def"']
 'abc "def"ghi'| [ 'abc "def"', 'ghi']
 'abc "def"ghi'| [ 'abc "def"ghi']
 "r'AA 'r'. * _ xyz $ '"| [ "r'AA '", "r'. * _ xyz $ '"]

나는 모든 입력 문자열에 대해 예상되는 결과가 나오도록 문자열을 분할하기 위해 다음 함수로 끝났습니다.

import re

def quoted_split(s):
    def strip_quotes(s):
        if s and (s[0] == '"' or s[0] == "'") and s[0] == s[-1]:
            return s[1:-1]
        return s
    return [strip_quotes(p).replace('\\"', '"').replace("\\'", "'") \
            for p in re.findall(r'"(?:\\.|[^"])*"|\'(?:\\.|[^\'])*\'|[^\s]+', s)]

테스트 응용 프로그램을 검사에게 다른 접근의 결과 (다음 shlexcsv지금) 및 사용자 정의 분할 구현을 :

#!/bin/python2.7

import csv
import re
import shlex

from timeit import timeit

def test_case(fn, s, expected):
    try:
        if fn(s) == expected:
            print '[ OK ] %s -> %s' % (s, fn(s))
        else:
            print '[FAIL] %s -> %s' % (s, fn(s))
    except Exception as e:
        print '[FAIL] %s -> exception: %s' % (s, e)

def test_case_no_output(fn, s, expected):
    try:
        fn(s)
    except:
        pass

def test_split(fn, test_case_fn=test_case):
    test_case_fn(fn, 'abc def', ['abc', 'def'])
    test_case_fn(fn, "abc \\s def", ['abc', '\\s', 'def'])
    test_case_fn(fn, '"abc def" ghi', ['abc def', 'ghi'])
    test_case_fn(fn, "'abc def' ghi", ['abc def', 'ghi'])
    test_case_fn(fn, '"abc \\" def" ghi', ['abc " def', 'ghi'])
    test_case_fn(fn, "'abc \\' def' ghi", ["abc ' def", 'ghi'])
    test_case_fn(fn, "'abc \\s def' ghi", ['abc \\s def', 'ghi'])
    test_case_fn(fn, '"abc \\s def" ghi', ['abc \\s def', 'ghi'])
    test_case_fn(fn, '"" test', ['', 'test'])
    test_case_fn(fn, "'' test", ['', 'test'])
    test_case_fn(fn, "abc'def", ["abc'def"])
    test_case_fn(fn, "abc'def'", ["abc'def'"])
    test_case_fn(fn, "abc'def' ghi", ["abc'def'", 'ghi'])
    test_case_fn(fn, "abc'def'ghi", ["abc'def'ghi"])
    test_case_fn(fn, 'abc"def', ['abc"def'])
    test_case_fn(fn, 'abc"def"', ['abc"def"'])
    test_case_fn(fn, 'abc"def" ghi', ['abc"def"', 'ghi'])
    test_case_fn(fn, 'abc"def"ghi', ['abc"def"ghi'])
    test_case_fn(fn, "r'AA' r'.*_xyz$'", ["r'AA'", "r'.*_xyz$'"])

def csv_split(s):
    return list(csv.reader([s], delimiter=' '))[0]

def re_split(s):
    def strip_quotes(s):
        if s and (s[0] == '"' or s[0] == "'") and s[0] == s[-1]:
            return s[1:-1]
        return s
    return [strip_quotes(p).replace('\\"', '"').replace("\\'", "'") for p in re.findall(r'"(?:\\.|[^"])*"|\'(?:\\.|[^\'])*\'|[^\s]+', s)]

if __name__ == '__main__':
    print 'shlex\n'
    test_split(shlex.split)
    print

    print 'csv\n'
    test_split(csv_split)
    print

    print 're\n'
    test_split(re_split)
    print

    iterations = 100
    setup = 'from __main__ import test_split, test_case_no_output, csv_split, re_split\nimport shlex, re'
    def benchmark(method, code):
        print '%s: %.3fms per iteration' % (method, (1000 * timeit(code, setup=setup, number=iterations) / iterations))
    benchmark('shlex', 'test_split(shlex.split, test_case_no_output)')
    benchmark('csv', 'test_split(csv_split, test_case_no_output)')
    benchmark('re', 'test_split(re_split, test_case_no_output)')

산출:

shlex

[OK] abc def-> [ 'abc', 'def']
[FAIL] abc \ s def-> [ 'abc', 's', 'def']
[OK] "abc def"ghi-> [ 'abc def', 'ghi']
[OK] 'abc def'ghi-> [ 'abc def', 'ghi']
[OK] "abc \"def "ghi-> [ 'abc"def', 'ghi']
[FAIL] 'abc \'def 'ghi-> 예외 : 마감 시세 없음
[OK] 'abc \ s def'ghi-> [ 'abc \\ s def', 'ghi']
[OK] "abc \ s def"ghi-> [ 'abc \\ s def', 'ghi']
[OK] ""test-> [ '', 'test']
[OK] ''테스트-> [ '', '테스트']
[FAIL] abc'def-> 예외 : 인용문 없음
[실패] abc'def '-> ['abcdef ']
[FAIL] abc'def 'ghi-> ['abcdef ','ghi ']
[실패] abc'def'ghi-> [ 'abcdefghi']
[FAIL] abc "def-> 예외 : 인용문 없음
[FAIL] abc "def"-> [ 'abcdef']
[FAIL] abc "def"ghi-> [ 'abcdef', 'ghi']
[FAIL] abc "def"ghi-> [ 'abcdefghi']
[실패] r'AA 'r'. * _ xyz $ '-> ['rAA ','r. * _ xyz $ ']

csv

[OK] abc def-> [ 'abc', 'def']
[OK] abc \ s def-> [ 'abc', '\\ s', 'def']
[OK] "abc def"ghi-> [ 'abc def', 'ghi']
[FAIL] 'abc def'ghi-> [ " 'abc", "def'", 'ghi']
[FAIL] "abc \"def "ghi-> [ 'abc \\', 'def"', 'ghi']
[FAIL] 'abc \'def 'ghi-> [ "'abc", "\\ '", "def'", 'ghi']
[FAIL] 'abc \ s def'ghi-> [ " 'abc",'\\ s ', "def'", 'ghi']
[OK] "abc \ s def"ghi-> [ 'abc \\ s def', 'ghi']
[OK] ""test-> [ '', 'test']
[실패] ''테스트-> [ " ''", '테스트']
[OK] abc'def-> [ "abc'def"]
[OK] abc'def '-> [ "abc'def'"]
[OK] abc'def 'ghi-> [ "abc'def'", 'ghi']
[OK] abc'def'ghi-> [ "abc'def'ghi"]
[OK] abc "def-> [ 'abc"def']
[OK] abc "def"-> [ 'abc "def"']
[OK] abc "def"ghi-> [ 'abc "def"', 'ghi']
[OK] abc "def"ghi-> [ 'abc "def"ghi']
[OK] r'AA 'r'. * _ xyz $ '-> [ "r'AA'", "r '. * _ xyz $'"]

레

[OK] abc def-> [ 'abc', 'def']
[OK] abc \ s def-> [ 'abc', '\\ s', 'def']
[OK] "abc def"ghi-> [ 'abc def', 'ghi']
[OK] 'abc def'ghi-> [ 'abc def', 'ghi']
[OK] "abc \"def "ghi-> [ 'abc"def', 'ghi']
[OK] 'abc \'def 'ghi-> [ "abc'def", 'ghi']
[OK] 'abc \ s def'ghi-> [ 'abc \\ s def', 'ghi']
[OK] "abc \ s def"ghi-> [ 'abc \\ s def', 'ghi']
[OK] ""test-> [ '', 'test']
[OK] ''테스트-> [ '', '테스트']
[OK] abc'def-> [ "abc'def"]
[OK] abc'def '-> [ "abc'def'"]
[OK] abc'def 'ghi-> [ "abc'def'", 'ghi']
[OK] abc'def'ghi-> [ "abc'def'ghi"]
[OK] abc "def-> [ 'abc"def']
[OK] abc "def"-> [ 'abc "def"']
[OK] abc "def"ghi-> [ 'abc "def"', 'ghi']
[OK] abc "def"ghi-> [ 'abc "def"ghi']
[OK] r'AA 'r'. * _ xyz $ '-> [ "r'AA'", "r '. * _ xyz $'"]

shlex : 반복 당 0.281ms
csv : 반복 당 0.030ms
re : 반복 당 0.049ms

따라서 성능은보다 훨씬 우수 shlex하며 정규식을 사전 컴파일하여 더 향상시킬 수 있습니다.이 경우 csv접근 방식 보다 성능이 우수합니다 .


당신이 무슨 말을하는지 모르겠다 :```>>> shlex.split ( '이것은 "테스트이다"') [ 'this', 'is', 'a test'] >>> shlex.split ( ' 이것은 \\ "a test \\" ') ['this ','is ',' "a ','test" '] >>> shlex.split ('이것은 "a \\"test \\ "입니다. " ') ['this ','is ','a"test " ']```
morsik

@morsik, 요점이 뭐야? 어쩌면 사용 사례가 내 것과 맞지 않습니까? 테스트 사례를 보면 shlex사용 사례에서 예상대로 작동하지 않는 모든 사례를 볼 수 있습니다.
Ton van den Heuvel 16:15에

3

따옴표를 유지하려면이 함수를 사용하십시오.

def getArgs(s):
    args = []
    cur = ''
    inQuotes = 0
    for char in s.strip():
        if char == ' ' and not inQuotes:
            args.append(cur)
            cur = ''
        elif char == '"' and not inQuotes:
            inQuotes = 1
            cur += char
        elif char == '"' and inQuotes:
            inQuotes = 0
            cur += char
        else:
            cur += char
    args.append(cur)
    return args

더 큰 문자열과 비교할 때 기능이 너무 느립니다.
Faran2007

3

다른 답변의 속도 테스트 :

import re
import shlex
import csv

line = 'this is "a test"'

%timeit [p for p in re.split("( |\\\".*?\\\"|'.*?')", line) if p.strip()]
100000 loops, best of 3: 5.17 µs per loop

%timeit re.findall(r'[^"\s]\S*|".+?"', line)
100000 loops, best of 3: 2.88 µs per loop

%timeit list(csv.reader([line], delimiter=" "))
The slowest run took 9.62 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 2.4 µs per loop

%timeit shlex.split(line)
10000 loops, best of 3: 50.2 µs per loop

1

흠, "답장"버튼을 찾을 수 없습니다 ... 어쨌든,이 답변은 Kate의 접근 방식을 기반으로하지만 이스케이프 된 따옴표가 포함 된 하위 문자열로 문자열을 올바르게 분할하고 하위 문자열의 시작 및 끝 따옴표를 제거합니다.

  [i.strip('"').strip("'") for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]

이것은 문자열에서 작동합니다 'This is " a \\\"test\\\"\\\'s substring"'(불행히도 파이썬이 이스케이프를 제거하지 못하게하려면 미친 마크 업이 필요합니다).

리턴 된 목록의 문자열에서 결과 이스케이프를 원하지 않는 경우, 약간 변경된이 함수 버전을 사용할 수 있습니다.

[i.strip('"').strip("'").decode('string_escape') for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]

1

일부 Python 2 버전에서 유니 코드 문제를 해결하려면 다음을 제안하십시오.

from shlex import split as _split
split = lambda a: [b.decode('utf-8') for b in _split(a.encode('utf-8'))]

파이썬 2.7.5의 경우 다음과 같아야합니다. split = lambda a: [b.decode('utf-8') for b in _split(a)]그렇지 않으면 다음과 같이 됩니다.UnicodeDecodeError: 'ascii' codec can't decode byte ... in position ...: ordinal not in range(128)
Peter Varo

1

옵션으로 tssplit을 시도하십시오.

In [1]: from tssplit import tssplit
In [2]: tssplit('this is "a test"', quote='"', delimiter='')
Out[2]: ['this', 'is', 'a test']

0

나는 제안한다 :

테스트 문자열 :

s = 'abc "ad" \'fg\' "kk\'rdt\'" zzz"34"zzz "" \'\''

""및 ''도 캡처하려면 :

import re
re.findall(r'"[^"]*"|\'[^\']*\'|[^"\'\s]+',s)

결과:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz', '""', "''"]

빈 ""및 ''를 무시하려면 :

import re
re.findall(r'"[^"]+"|\'[^\']+\'|[^"\'\s]+',s)

결과:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz']

같이 쓸 re.findall("(?:\".*?\"|'.*?'|[^\s'\"]+)", s)수도 있습니다.
hochl

-3

간단한 것보다 하위 문자열을 신경 쓰지 않으면

>>> 'a short sized string with spaces '.split()

공연:

>>> s = " ('a short sized string with spaces '*100).split() "
>>> t = timeit.Timer(stmt=s)
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
171.39 usec/pass

또는 문자열 모듈

>>> from string import split as stringsplit; 
>>> stringsplit('a short sized string with spaces '*100)

성능 : 문자열 모듈이 문자열 방법보다 성능이 우수한 것 같습니다

>>> s = "stringsplit('a short sized string with spaces '*100)"
>>> t = timeit.Timer(s, "from string import split as stringsplit")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
154.88 usec/pass

또는 RE 엔진을 사용할 수 있습니다

>>> from re import split as resplit
>>> regex = '\s+'
>>> medstring = 'a short sized string with spaces '*100
>>> resplit(regex, medstring)

공연

>>> s = "resplit(regex, medstring)"
>>> t = timeit.Timer(s, "from re import split as resplit; regex='\s+'; medstring='a short sized string with spaces '*100")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
540.21 usec/pass

매우 긴 문자열의 경우 전체 문자열을 메모리에로드하지 말고 대신 행을 분할하거나 반복 루프를 사용하십시오


11
질문의 요점을 놓친 것 같습니다. 문자열에 분할 할 필요가없는 인용 된 섹션이 있습니다.
rjmunro

-3

이 시도:

  def adamsplit(s):
    result = []
    inquotes = False
    for substring in s.split('"'):
      if not inquotes:
        result.extend(substring.split())
      else:
        result.append(substring)
      inquotes = not inquotes
    return result

일부 테스트 문자열 :

'This is "a test"' -> ['This', 'is', 'a test']
'"This is \'a test\'"' -> ["This is 'a test'"]

실패 할 것으로 생각되는 문자열의 반복을 제공하십시오.
pjz

생각해 ? adamsplit("This is 'a test'")['This', 'is', "'a", "test'"]
Matthew Schinckel

OP는 "따옴표 내"만 표시하고 큰 따옴표가 포함 된 예만 있습니다.
pjz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.