it-swarm-vi.tech

Tách một chuỗi theo khoảng trắng - bảo toàn các chuỗi con được trích dẫn - trong Python

Tôi có một chuỗi như thế này:

this is "a test"

Tôi đang cố gắng viết một cái gì đó bằng Python để phân tách nó theo không gian trong khi bỏ qua khoảng trắng trong dấu ngoặc kép. Kết quả tôi đang tìm kiếm là:

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

Tái bút Tôi biết bạn sẽ hỏi "điều gì xảy ra nếu có dấu ngoặc kép trong dấu ngoặc kép, trong ứng dụng của tôi, điều đó sẽ không bao giờ xảy ra.

223
Adam Pierce

Bạn muốn tách, từ mô đun shlex .

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

Điều này sẽ làm chính xác những gì bạn muốn.

342
Jerub

Hãy xem mô-đun shlex, đặc biệt là shlex.split.

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

Tôi thấy cách tiếp cận regex ở đây có vẻ phức tạp và/hoặc sai. Điều này làm tôi ngạc nhiên, vì cú pháp regex có thể dễ dàng mô tả "khoảng trắng hoặc vật được bao quanh bởi dấu ngoặc kép" và hầu hết các công cụ regex (bao gồm cả Python) có thể phân tách trên regex. Vì vậy, nếu bạn sẽ sử dụng regexes, tại sao không nói chính xác ý bạn là gì?:

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()]

Giải trình:

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

shlex có thể cung cấp nhiều tính năng hơn, mặc dù.

31
Kate

Tùy thuộc vào trường hợp sử dụng của bạn, bạn cũng có thể muốn kiểm tra mô-đun csv:

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

Đầu ra: 

['this', 'is', 'a string']
['and', 'more', 'stuff']
23
Ryan Ginstrom

Tôi sử dụng shlex.split để xử lý 70.000.000 dòng nhật ký mực, quá chậm. Thế là tôi chuyển sang re.

Vui lòng thử điều này, nếu bạn có vấn đề về hiệu suất với shlex.

import re

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

Vì câu hỏi này được gắn thẻ regex, tôi quyết định thử một cách tiếp cận regex. Trước tiên tôi thay thế tất cả các khoảng trắng trong các phần trích dẫn bằng\x00, sau đó phân tách bằng dấu cách, sau đó thay thế\x00 trở lại khoảng trắng trong mỗi phần.

Cả hai phiên bản đều làm điều tương tự, nhưng bộ chia là dễ đọc hơn một chút sau đó là bộ chia2.

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
gooli

Để giữ nguyên dấu ngoặc kép, hãy sử dụng chức năng này:

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

Kiểm tra tốc độ của các câu trả lời khác nhau:

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

Có vẻ như vì lý do hiệu suất re nhanh hơn. Đây là giải pháp của tôi bằng cách sử dụng một toán tử ít tham lam nhất để giữ các dấu ngoặc kép bên ngoài:

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

Kết quả:

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

Nó để các cấu trúc như aaa"bla blub"bbb lại với nhau vì các mã thông báo này không được phân tách bằng dấu cách. Nếu chuỗi chứa các ký tự thoát, bạn có thể khớp như vậy:

>>> 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.\""

Xin lưu ý rằng điều này cũng khớp với chuỗi trống "" bằng phần \S của mẫu.

2
hochl

Để giải quyết các vấn đề về unicode trong một số phiên bản Python 2, tôi đề nghị:

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

Vấn đề chính với cách tiếp cận shlex được chấp nhận là nó không bỏ qua các ký tự thoát bên ngoài các chuỗi con được trích dẫn và cho kết quả hơi bất ngờ trong một số trường hợp góc.

Tôi có trường hợp sử dụng sau đây, trong đó tôi cần một hàm phân tách để phân tách các chuỗi đầu vào sao cho các chuỗi con được trích dẫn đơn hoặc trích dẫn kép được giữ nguyên, với khả năng thoát dấu ngoặc kép trong chuỗi con đó. Các trích dẫn trong một chuỗi không trích dẫn không nên được xử lý khác với bất kỳ ký tự nào khác. Một số trường hợp thử nghiệm ví dụ với đầu ra dự kiến:

  chuỗi đầu vào | sản lượng dự kiến ​​
 ============================================= == 
 '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'] 
 '"" kiểm tra' | ['', 'kiểm tra']
 "'' kiểm tra" | ['', 'kiểm tra']
 "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 $ '"]

Tôi đã kết thúc với hàm sau để phân tách một chuỗi sao cho kết quả đầu ra dự kiến ​​cho tất cả các chuỗi đầu vào:

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)]

Ứng dụng thử nghiệm sau đây kiểm tra kết quả của các phương pháp khác (shlexcsv hiện tại) và triển khai phân tách tùy chỉnh:

#!/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)')

Đầu ra:

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 -> ngoại lệ: Không có trích dẫn đóng 
 [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 -> ngoại lệ: Không có trích dẫn đóng 
 [FAIL] abc'def '-> [' abcdef '] 
 [FAIL] abc'def' ghi -> ['abcdef', 'ghi'] 
 [FAIL] abc'def'ghi -> ['abcdefghi'] 
 [FAIL] abc "def -> ngoại lệ: Không có trích dẫn kết thúc 
 [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', '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 '] 
 [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 mỗi lần lặp 
 csv: 0,030ms mỗi lần lặp
re: 0,049ms mỗi lần lặp

Vì vậy, hiệu suất tốt hơn nhiều so với shlex và có thể được cải thiện hơn nữa bằng cách biên dịch biểu thức chính quy, trong trường hợp đó, nó sẽ vượt trội hơn so với cách tiếp cận csv.

1
Ton van den Heuvel

Các vấn đề unicode với shlex đã thảo luận ở trên (câu trả lời hàng đầu) dường như được giải quyết (gián tiếp) trong 2.7.2+ theo per http://bugs.python.org/su6988#msg146200

(câu trả lời riêng vì tôi không thể bình luận)

1
Tyris

Hmm, dường như không thể tìm thấy nút "Trả lời" ... dù sao, câu trả lời này dựa trên cách tiếp cận của Kate, nhưng phân tách chính xác các chuỗi với các chuỗi chứa dấu ngoặc kép thoát và cũng loại bỏ dấu ngoặc kép bắt đầu và kết thúc của chuỗi con:

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

Điều này hoạt động trên các chuỗi như 'This is " a \\\"test\\\"\\\'s substring"' (không may là đánh dấu điên rồ để ngăn Python xóa các lối thoát).

Nếu kết quả thoát trong chuỗi trong danh sách trả về không muốn, bạn có thể sử dụng phiên bản hàm bị thay đổi một chút này:

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

Tôi đề nghị:

chuỗi kiểm tra:

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

để chụp cũng "" và '':

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

kết quả:

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

bỏ qua trống "" và '':

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

kết quả:

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