Разделить строку пробелами - сохранить цитируемые подстроки - в Python

У меня есть строка, которая выглядит примерно так:

this is "a test"

Я пытаюсь написать что-то в Python, чтобы разделить его на пространство, игнорируя пробелы внутри кавычек. Результат, который я ищу, это:

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

PS. Я знаю, что вы спросите: "Что произойдет, если в кавычках есть кавычки, ну, в моем приложении это никогда не произойдет.

Ответ 1

Вы хотите разделить, из shlex.

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

Это должно делать именно то, что вы хотите.

Ответ 2

Посмотрите модуль shlex, особенно shlex.split.

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

Ответ 3

Я вижу подходы regex здесь, которые выглядят сложными и/или неправильными. Это меня удивляет, потому что синтаксис регулярных выражений может легко описать "пробельные или вещественные окружения за кавычками", и большинство движков регулярных выражений (включая 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, вероятно, предоставляет больше возможностей.

Ответ 4

В зависимости от вашего варианта использования вы также можете проверить модуль 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']

Ответ 5

Поскольку этот вопрос помечен регулярным выражением, я решил попробовать подход с регулярным выражением. Сначала я заменил все пробелы в частях кавычек на \x00, затем разделил пробелами, а затем заменил \x00 на пробелы в каждой части.

Обе версии делают то же самое, но сплиттер немного читаем, а затем 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)

Ответ 6

Я использую shlex.split для обработки 70 000 000 строк журнала кальмаров, это так медленно. Поэтому я переключился на re.

Попробуйте это, если у вас есть проблемы с производительностью с помощью shlex.

import re

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

Ответ 7

Чтобы обойти проблемы с unicode в некоторых версиях Python 2, я предлагаю:

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

Ответ 8

Чтобы сохранить кавычки, используйте эту функцию:

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

Ответ 9

Хмм, похоже, не может найти кнопку "Ответить"... в любом случае, этот ответ основан на подходе Кейт, но правильно разбивает строки с подстроками, содержащими экранированные кавычки, а также удаляет стартовые и конечные кавычки подстроки:

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

Это работает с строками типа 'This is " a \\\"test\\\"\\\ substring"' (к сожалению, безумная разметка необходима, чтобы Python не удалял экраны).

Если результирующие escape-последовательности в строках в возвращаемом списке не нужны, вы можете использовать эту слегка измененную версию функции:

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

Ответ 10

Проблемы с unicode с shlex, описанные выше (верхний ответ), кажутся разрешенными (косвенно) в 2.7.2+ в соответствии с http://bugs.python.org/issue6988#msg146200

(отдельный ответ, потому что я не могу комментировать)

Ответ 11

Я предлагаю:

тестовая строка:

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

Ответ 12

Если вам не нужны подстроки, чем простые

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

Производительность: модуль String работает лучше, чем строковые методы

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

Для очень длинных строк вы не должны загружать всю строку в память и вместо этого разделять строки или использовать итеративный цикл

Ответ 13

Попробуйте следующее:

  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'"]