web-dev-qa-db-ja.com

Pythonで、引用符で囲まれた部分文字列を保持して、文字列をスペースで分割します

私はこのような文字列を持っています:

this is "a test"

Pythonで何かを記述して、引用符内のスペースを無視してスペースで分割しようとしています。私が探している結果は次のとおりです。

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

PS。私のアプリケーションでは、引用符の中に引用符があるとどうなるのか、と尋ねるでしょう。

237
Adam Pierce

shlex モジュールから分割したい。

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

これはまさにあなたが望むことをする必要があります。

355
Jerub

shlexモジュール、特にshlex.splitをご覧ください。

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

複雑で間違っているように見える正規表現アプローチがここにあります。正規表現構文は「空白または引用符で囲まれたもの」を簡単に記述することができ、ほとんどの正規表現エンジン(Pythonを含む)は正規表現で分割できるため、これは驚きです。したがって、正規表現を使用する場合、どういう意味かを正確に言うだけではどうですか?:

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はおそらくより多くの機能を提供します。

35
Kate

ユースケースに応じて、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']
25
Ryan Ginstrom

Shlex.splitを使用して70,000,000行のsquidログを処理しますが、非常に遅いです。だから私は再に切り替えました。

Shlexのパフォーマンスに問題がある場合は、これを試してください。

import re

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

この質問は正規表現でタグ付けされているため、正規表現アプローチを試すことにしました。最初に引用符部分のすべてのスペースを\ x00に置き換え、次にスペースで分割してから、\ x00を各部分のスペースに戻します。

どちらのバージョンも同じことを行いますが、splitterはsplit2よりも少し読みやすくなっています。

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)
8
elifiner

パフォーマンス上の理由から、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にも一致することに注意してください。

5
hochl

受け入れられている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 'ギ」| ["abc 'def"、' ghi '] 
 "' abc \\ s def 'ghi" | ['abc \\ s def'、 'ghi'] 
 '"abc \\ s def" ghi' | ['abc \\ s def'、 'ghi'] 
 '"" test' | [''、 'test'] 
 "'' test" | [''、 'test'] 
 "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)]

次のテストアプリケーションは、他のアプローチの結果(現時点ではshlexおよびcsv)とカスタム分割実装を確認します。

#!/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 __== '__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] '' test-> [' '、' test '] 
 [FAIL] abc'def->例外:閉じ引用符なし
 [FAIL] abc'def'-> ['abcdef'] 
 [FAIL ] abc'def 'ghi-> [' abcdef '、' ghi '] 
 [FAIL] abc'def'ghi-> [' abcdefghi '] 
 [FAIL] abc "def->例外:閉じ引用符なし
 [FAIL] abc "def"-> ['abcdef'] 
 [FAIL] abc "def" ghi-> ['abcdef'、 'ghi'] 
 [FAIL] abc "def" ghi-> ['abcdefghi'] 
 [FAIL] r'AA 'r'。* _ xyz $ '-> [' rAA '、' r。* _ xyz $ '] 
 
 csv 
 
 [OK] abc def-> [' abc '、' de f '] 
 [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'] 
 [FAIL] '' 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 $ '"] 
 
 re 
 
 [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] '' 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 $'"] 
 
 shlex:反復あたり0.281ms 
 csv:反復あたり0.030ms 
 re:反復あたり0.049ms

そのため、パフォーマンスはshlexよりもはるかに優れており、正規表現をプリコンパイルすることでさらに改善できます。この場合、csvアプローチよりも優れています。

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
3
THE_MAD_KING

さまざまな回答のスピードテスト:

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
2
har777

一部のPython 2バージョンのUnicode問題を回避するには、次のことをお勧めします。

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

上記のshlexに関するUnicodeの問題(トップアンサー)は、 http://bugs.python.org/issue6988#msg1462 に従って2.7.2+で(間接的に)解決されるようです

(コメントできないため、個別の回答)

1
Tyris

うーん、「返信」ボタンが見つからないようです...とにかく、この答えはケイトによるアプローチに基づいていますが、エスケープされた引用符を含む部分文字列で文字列を正しく分割し、部分文字列の開始引用符と終了引用符も削除します:

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

これは'This is " a \\\"test\\\"\\\'s substring"'などの文字列で機能します(残念ながら、Pythonがエスケープを削除しないようにするには非常識なマークアップが必要です)。

返されたリスト内の文字列の結果のエスケープが必要ない場合、このわずかに変更されたバージョンの関数を使用できます。

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

私は提案します:

テスト文字列:

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']
0
hussic