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

205

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

this is "a test"

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

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

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

  • 0
    Спасибо, что задали этот вопрос. Это именно то, что мне нужно для исправления модуля сборки Pypar.
Теги:

13 ответов

310
Лучший ответ

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

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

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

  • 6
    Ох, боже, в python версии 2.5.1 и выше shlex.split() не работает для юникода. Например, shlex.split(u"test test") создает дерьмо, такое как 't\x00e\x00s\x00t\x00', '\x00t\x00e\x00s\x00t\x00' , подробности об ошибках см. В следующем обсуждении проблемы . python.org/issue6988
  • 7
    Нет проблем в Python 3.
Показать ещё 7 комментариев
50

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

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

Я вижу подходы 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, вероятно, предоставляет больше возможностей.

  • 0
    Я думал примерно так же, но вместо этого предложил бы [t.strip ('"') для t в re.findall (r '[^ \ s"] + | "[^"] * "', 'this is" тест"')]
  • 0
    Что делает этот раскол, когда в двойных кавычках есть апострофы: Он сказал: «Не делай этого!» Я думаю, что это будет относиться к <"Дон"> как к одному целому, не так ли?
Показать ещё 12 комментариев
18

В зависимости от вашего варианта использования вы также можете проверить модуль 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']
  • 1
    полезно, когда шлекс удаляет некоторые необходимые символы
  • 1
    обратите внимание на возможные проблемы как shlex: python2: модуль csv не поддерживает ввод Unicode
8

Поскольку этот вопрос помечен регулярным выражением, я решил попробовать подход с регулярным выражением. Сначала я заменил все пробелы в частях кавычек на \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)
  • 0
    Вы должны были использовать re.Scanner. Это более надежно (и я на самом деле реализовал shlex-like с помощью re.Scanner).
  • 0
    +1 Хм, это довольно умная идея, разбить проблему на несколько шагов, чтобы ответ не был слишком сложным. Шлекс не делал то, что мне было нужно, даже пытаясь настроить его. И решения regex за один проход становились действительно странными и сложными.
6

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

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

import re

def line_split(line):
    return re.findall(r'[^"\s]\S*|".+?"', line)
  • 0
    Проверено - работает с юникодом - спасибо!
2

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

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
2

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

from shlex import split as _split
split = lambda a: [b.decode('utf-8') for b in _split(a.encode('utf-8'))]
  • 0
    Для python 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)
1

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

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

1

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

  [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()]
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']
  • 0
    Может быть записан как re.findall("(?:\".*?\"|'.*?'|[^\s'\"]+)", s) .
0

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

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

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

  • 10
    Вы, кажется, упустили всю суть вопроса. В строке есть разделы в кавычках, которые не нужно разбивать.
-1

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

  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'"]
  • 1
    Это не будет работать с: «Это« тест »»
  • 0
    Пожалуйста, укажите repr строки, которая, по вашему мнению, потерпит неудачу.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню