it-swarm-id.com

Pisahkan sebuah string dengan spasi - mempertahankan substring yang dikutip - dengan Python

Saya memiliki string seperti ini:

this is "a test"

Saya mencoba menulis sesuatu dengan Python untuk membaginya dengan spasi sambil mengabaikan spasi dalam tanda kutip. Hasil yang saya cari adalah:

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

PS. Saya tahu Anda akan bertanya "apa yang terjadi jika ada tanda kutip di dalam tanda kutip, yah, dalam aplikasi saya, itu tidak akan pernah terjadi.

223
Adam Pierce

Anda ingin split, dari shlex module.

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

Ini harus melakukan apa yang Anda inginkan.

342
Jerub

Lihatlah modul shlex, khususnya shlex.split.

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

Saya melihat pendekatan regex di sini yang terlihat rumit dan/atau salah. Ini mengejutkan saya, karena sintaksis regex dapat dengan mudah menggambarkan "ruang kosong atau hal-dikelilingi-oleh-kutipan", dan sebagian besar mesin regex (termasuk Python) dapat dibagi pada sebuah regex. Jadi, jika Anda akan menggunakan regex, mengapa tidak mengatakan dengan tepat apa yang Anda maksud ?:

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

Penjelasan:

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

shlex mungkin menyediakan lebih banyak fitur.

31
Kate

Tergantung pada kasus penggunaan Anda, Anda mungkin juga ingin memeriksa modul csv:

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

Keluaran: 

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

Saya menggunakan shlex.split untuk memproses 70.000.000 baris squid log, ini sangat lambat. Jadi saya beralih ke kembali.

Silakan coba ini, jika Anda memiliki masalah kinerja dengan shlex.

import re

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

Karena pertanyaan ini ditandai dengan regex, saya memutuskan untuk mencoba pendekatan regex. Saya pertama-tama mengganti semua spasi di bagian kutipan dengan\x00, lalu pisah dengan spasi, lalu ganti\x00 kembali ke spasi di setiap bagian.

Kedua versi melakukan hal yang sama, tetapi splitter sedikit lebih mudah dibaca daripada 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)
8
gooli

Untuk menyimpan kutipan, gunakan fungsi ini:

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

Tes kecepatan untuk jawaban yang berbeda:

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

Tampaknya karena alasan kinerja re lebih cepat. Inilah solusi saya menggunakan operator serakah yang menjaga kutipan luar:

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

Hasil:

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

Ia meninggalkan konstruksi seperti aaa"bla blub"bbb bersamaan karena token ini tidak dipisahkan oleh spasi. Jika string berisi karakter yang lolos, Anda dapat mencocokkan seperti itu:

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

Harap dicatat bahwa ini juga cocok dengan string kosong "" melalui bagian \S dari pola.

2
hochl

Untuk mengatasi masalah unicode di beberapa versi Python 2, saya sarankan:

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

Masalah utama dengan pendekatan shlex yang diterima adalah bahwa ia tidak mengabaikan karakter pelarian di luar substring yang dikutip, dan memberikan hasil yang sedikit tidak terduga dalam beberapa kasus sudut.

Saya memiliki kasus penggunaan berikut, di mana saya memerlukan fungsi split yang membagi string input sedemikian rupa sehingga baik substring yang dikutip tunggal atau ganda dikutip dipertahankan, dengan kemampuan untuk lolos dari kutipan dalam substring tersebut. Kutipan dalam string yang tidak dikutip tidak boleh diperlakukan secara berbeda dari karakter lain. Beberapa contoh uji kasus dengan output yang diharapkan:

  masukan string | output yang diharapkan 
 ============================================= == 
 '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'] 
 '"" tes' | ['', 'uji']
 "'' test" | ['', 'uji']
 "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 $ '"]

Saya berakhir dengan fungsi berikut untuk memisahkan string sehingga hasil output yang diharapkan untuk semua string input:

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

Aplikasi tes berikut memeriksa hasil pendekatan lain (shlex dan csv untuk saat ini) dan implementasi pemisahan kustom:

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

Keluaran:

shlex 

 [OK] abc def -> ['abc', 'def'] 
 [FAIL] abc 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 -> pengecualian: Tidak ada kutipan penutup 
 [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 -> pengecualian: Tidak ada kutipan penutup 
 [FAIL] abc'def '-> [' abcdef '] 
 [FAIL] abc'def' ghi -> ['abcdef', 'ghi'] 
 [FAIL] abc'def'ghi -> ['abcdefghi'] 
 [FAIL] abc "def -> exception: Tidak ada kutipan penutup 
 [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\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] def def abc -> [' 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\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 per iterasi 
 csv: 0,030ms per iteration
re: 0,049ms per iterasi

Jadi kinerjanya jauh lebih baik daripada shlex, dan dapat ditingkatkan lebih lanjut dengan mengkompilasi ekspresi reguler, dalam hal ini akan mengungguli pendekatan csv.

1

Masalah unicode dengan shlex yang dibahas di atas (jawaban atas) tampaknya diselesaikan (tidak langsung) dalam 2.7.2+ sesuai per http://bugs.python.org/issue6988#msg146200

(pisahkan jawaban karena saya tidak bisa berkomentar)

1
Tyris

Hmm, sepertinya tidak dapat menemukan tombol "Balas" ... lagi pula, jawaban ini didasarkan pada pendekatan oleh Kate, tetapi dengan benar membagi string dengan substring yang berisi tanda kutip yang lolos dan juga menghilangkan tanda kutip mulai dan akhir substring:

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

Ini bekerja pada string seperti 'This is " a \\\"test\\\"\\\'s substring"' (markup gila sayangnya diperlukan untuk menjaga Python dari menghapus lolos).

Jika hasil yang keluar dalam string dalam daftar yang dikembalikan tidak diinginkan, Anda dapat menggunakan versi fungsi yang sedikit diubah ini:

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

Saya menyarankan:

string uji:

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

untuk menangkap juga "" dan '':

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

hasil:

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

untuk mengabaikan "" dan '' kosong:

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

hasil:

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