shlex — Простой лексический анализ


Класс shlex упрощает написание лексических анализаторов для простых синтаксисов, напоминающих синтаксис оболочки Unix. Это часто бывает полезно для написания мини-языков (например, в файлах управления запуском для Python приложений) или для парсинга строк в кавычках.

Модуль shlex определяет следующие функции:

shlex.split(s, comments=False, posix=True)

Разделяет строку s, используя синтаксис оболочки. Если comments равен False (по умолчанию), анализ комментариев в данной строке будет отключён (установка атрибута commenters экземпляра shlex на пустую строку). Данная функция работает в режиме POSIX по умолчанию, но использует отличный от POSIX режим, если у аргумента posix ложное значение.

Примечание

Поскольку функция split() создаёт экземпляр shlex, передача None вместо s приведёт к чтению строки, которая будет отделена от стандартного ввода.

shlex.join(split_command)

Объединяет токены списка split_command и возвращает строку. Данная функция обратна split().

>>> from shlex import join
>>> print(join(['echo', '-n', 'Multiple words']))
echo -n 'Multiple words'

Возвращаемое значение экранируется оболочкой для защиты от уязвимостей внедрения (см. quote()).

Добавлено в версии 3.8.

shlex.quote(s)

Возвращает экранированную версию строки s. Возвращаемое значение представляет собой строку, которую можно безопасно использовать как один токен в командной строке оболочки в случаях, когда вы не можете использовать список.

Данная идиома была бы небезопасной:

>>> filename = 'somefile; rm -rf ~'
>>> command = 'ls -l {}'.format(filename)
>>> print(command)  # выполнен оболочкой: бум!
ls -l somefile; rm -rf ~

quote() позволяет закрыть дыру в безопасности:

>>> from shlex import quote
>>> command = 'ls -l {}'.format(quote(filename))
>>> print(command)
ls -l 'somefile; rm -rf ~'
>>> remote_command = 'ssh home {}'.format(quote(command))
>>> print(remote_command)
ssh home 'ls -l '"'"'somefile; rm -rf ~'"'"''

Закавычивание совместимо с оболочками UNIX и split():

>>> from shlex import split
>>> remote_command = split(remote_command)
>>> remote_command
['ssh', 'home', "ls -l 'somefile; rm -rf ~'"]
>>> command = split(remote_command[-1])
>>> command
['ls', '-l', 'somefile; rm -rf ~']

Добавлено в версии 3.3.

Модуль shlex определяет следующий класс:

class shlex.shlex(instream=None, infile=None, posix=False, punctuation_chars=False)

Экземпляр shlex или экземпляр подкласса является объектом лексического анализатора. Аргумент инициализации указывает, если он присутствует, откуда считывать символы. Это должен быть объект, подобный файлу/потоку, с методами read() и readline() или строка. Если аргумент не указан, ввод будет браться из sys.stdin. Второй необязательный аргумент — это строка имени файла, которая устанавливает начальное значение атрибута infile. Если аргумент instream пропущен или равен sys.stdin, данный второй аргумент по умолчанию имеет значение «stdin». Аргумент posix определяет режим работы: если posix не равно истина (по умолчанию), экземпляр shlex будет работать в режиме совместимости. При работе в режиме POSIX shlex попытается максимально приблизиться к правилам парсинга оболочки POSIX. Аргумент punctuation_chars позволяет сделать поведение ещё ближе к тому, как разбираются настоящие оболочки. Это может принимать несколько значений: значение по умолчанию, False, сохраняет поведение, наблюдаемое в Python 3.5 и более ранних версиях. Если установлено значение True, то парсинг символов ();<>|& изменяется: любая серия данных символов (считающихся знаками препинания) возвращается как один токен. Если задана непустая строка символов, данные символы будут использоваться в качестве знаков препинания. Любые символы в атрибуте wordchars, которые появляются в punctuation_chars, будут удалены из wordchars. См. Улучшена совместимость с оболочками для получения дополнительной информации. punctuation_chars может быть установлен только при создании экземпляра shlex и не может быть изменён позже.

Изменено в версии 3.6: Добавлен параметр punctuation_chars.

См.также

Модуль configparser
Парсер для файлов конфигурации, похожих на .ini файлы Windows.

Объекты shlex

У экземпляра shlex есть следующие методы:

shlex.get_token()

Возвращает токен. Если токены были сложены с помощью push_token(), извлекает токен из стека. В противном случае читает один из входного потока. Если при чтении обнаруживается немедленный конец файла, возвращается eof (пустая строка ('') в режиме, отличном от POSIX, и None в режиме POSIX).

shlex.push_token(str)

Поместить аргумент в стек токенов.

shlex.read_token()

Чтение необработанного токена. Игнорировать стек pushback и не интерпретирует исходные запросы. (Обычно это не очень полезная точка входа, и она задокументирована здесь только для полноты картины.)

shlex.sourcehook(filename)

Когда shlex обнаруживает исходный запрос (см. source ниже), этому методу в качестве аргумента передаётся следующий токен, и ожидается, что он вернёт кортеж, состоящий из имени файла и открытого файлового объекта.

Обычно данный метод сначала удаляет из аргумента все кавычки. Если результатом является абсолютный путь, или предыдущий запрос источника не действовал, или предыдущий источник был потоком (например, sys.stdin), результат остаётся без изменений. В противном случае, если результатом является относительный путь к имени файла, непосредственно предшествующему ему в стеке включения исходного кода, добавляется часть каталога (это поведение похоже на то, как препроцессор C обрабатывает #include "file.h").

Результат манипуляций обрабатывается как имя файла и возвращается как первый компонент кортежа с вызовом open() для получения второго компонента. (Примечание: это обратный порядок аргументов при инициализации экземпляра!)

Данный хук открыт, так что вы можете использовать его для реализации путей поиска каталогов, добавления расширений файлов и других хаков пространства имён. Соответствующего хука «закрыть» нет, но экземпляр shlex будет вызывать метод close() исходного входного потока, когда он возвращает EOF.

Для более явного управления стеком исходного кода используйте методы push_source() и pop_source().

shlex.push_source(newstream, newfile=None)

Поместить входной исходный поток во входной стек. Если указан аргумент имени файла, позже он будет доступен для использования в сообщениях об ошибках. Это тот же метод, который используется внутри метода sourcehook().

shlex.pop_source()

Извлечь последний источник ввода из входного стека. Это тот же метод, который используется внутри, когда лексер достигает EOF во входном потоке с накоплением.

shlex.error_leader(infile=None, lineno=None)

Данный метод создаёт заголовок сообщения об ошибке в формате метки ошибки компилятора Unix C; формат — '"%s", line %d: ', где %s заменяется именем текущего исходного файла, а %d — текущим номером входной строки (для их переопределения можно использовать необязательные аргументы).

Это удобство предоставляется для того, чтобы побудить пользователей shlex генерировать сообщения об ошибках в стандартном анализируемом формате, понятном Emacs и другим инструментам Unix.

Экземпляры подклассов shlex имеют некоторые общедоступные переменные экземпляра, которые либо управляют лексическим анализом, либо могут использоваться для отладки:

shlex.commenters

Строка символов, которые распознаются как начинающие комментарии. Все символы от начала комментария до конца строки игнорируются. По умолчанию включает только '#'.

shlex.wordchars

Строка символов, накапливаемая в многосимвольных токенах. По умолчанию включает все буквенно-цифровые символы ASCII и символы подчеркивания. В режиме POSIX также включаются символы с диакритическими знаками из набора Latin-1. Если punctuation_chars не пуст, символы ~-./*?=, которые могут появляться в спецификациях имени файла и параметрах командной строки, также будут включены в данный атрибут, а любые появляющиеся символы в punctuation_chars, будут удалены из wordchars, если они там присутствуют. Если для whitespace_split установлено значение True, это не будет иметь никакого эффекта.

shlex.whitespace

Символы, которые будут считаться пробелами и будут пропущены. Токены границ пробелов. По умолчанию включает пробел, табуляцию, перевод строки и возврат каретки.

shlex.escape

Символы, которые будут считаться экранированными. Будет использоваться только в режиме POSIX и по умолчанию включает только '\'.

shlex.quotes

Символы, которые будут считаться строковыми кавычками. Маркер накапливается до тех пор, пока та же кавычка не встретится снова (таким образом, разные типы кавычек защищают друг друга, как в оболочке). По умолчанию включает одинарные и двойные кавычки ASCII.

shlex.escapedquotes

Символы в quotes, которые будут интерпретировать escape-символы, определённые в escape. Он используется только в режиме POSIX и по умолчанию включает только '"'.

shlex.whitespace_split

Если True, токены будут разделены только пробелами. Это полезно, например, для разбора командных строк с shlex, получения токенов аналогично аргументам оболочки. При использовании в сочетании с punctuation_chars токены будут разделены на пробелы в дополнение к этим символам.

Изменено в версии 3.8: Атрибут punctuation_chars стал совместимым с атрибутом whitespace_split.

shlex.infile

Имя текущего входного файла, изначально установленное во время создания экземпляра класса или сложенное более поздними исходными запросами. Это может быть полезно изучить при создании сообщений об ошибках.

shlex.instream

Входной поток, из которого данный экземпляр shlex считывает символы.

shlex.source

По умолчанию данный атрибут имеет значение None. Если вы присвоите ему строку, трока будет распознана как запрос на включение на лексическом уровне, аналогичный ключевому слову source в различных оболочках. Т. е. сразу последующая лексема будет открыта как имя файла, и входные данные будут браться из этого потока до тех пор, пока не завершится EOF, после чего будет вызван метод close() этого потока, и источник входных данных снова станет исходным входным потоком. Исходные запросы могут располагаться на любом количестве уровней в глубину.

shlex.debug

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

shlex.lineno

Номер исходной строки (количество просмотренных новых строк плюс один).

shlex.token

Буфер токенов. Это может быть полезно изучить при перехвате исключений.

shlex.eof

Токен, используемый для определения конца файла. Это будет пустая строка ('') в режиме, отличном от POSIX, и None в режиме POSIX.

shlex.punctuation_chars

Свойство только для чтения. Символы, которые будут считаться знаками препинания. Ряды знаков препинания будут возвращены как один токен. Однако обратите внимание, что никакая проверка семантической достоверности не будет выполняться: например, «>>>» может быть возвращено как токен, даже если он может не распознаваться как таковой оболочками.

Добавлено в версии 3.6.

Правила парсинга

При работе в режиме, отличном от POSIX, shlex будет пытаться соблюдать следующие правила.

  • Символы кавычек не распознаются внутри слов (Do"Not"Separate анализируется как одно слово Do"Not"Separate);
  • Экранирующие символы не распознаются;
  • Символы, заключенные в кавычки, сохраняют буквальное значение всех символов в кавычках;
  • Закрытие кавычек отдельных слов ("Do"Separate анализируется как "Do" и Separate);
  • Если whitespace_split равен False, любой символ, не объявленный как символ слова, пробел или кавычка, будет возвращён как односимвольный токен. Если это True, shlex будет разделять слова только пробелами;
  • EOF сигнализируется пустой строкой ('');
  • Невозможно разобрать пустые строки, даже если они заключены в кавычки.

При работе в режиме POSIX shlex будет пытаться соблюдать следующие правила парсинга.

  • Кавычки удаляются и не разделяют слова ("Do"Not"Separate" анализируется как одно слово DoNotSeparate);
  • Экранирующие символы без кавычек (например, '\') сохраняют буквальное значение следующего символа;
  • Заключение символов в кавычки, которые не являются частью escapedquotes (например, "'"), сохраняет буквальное значение всех символов в кавычках;
  • Заключение символов в кавычки, которые являются частью escapedquotes (например, '"'), сохраняет буквальное значение всех символов в кавычках, за исключением символов, упомянутых в escape. Экранирующие символы сохраняют свое особое значение только тогда, когда за ними следует используемая кавычка или сам экранирующий символ. В противном случае escape-символ будет считаться обычным символом.
  • EOF сигнализируется значением None;
  • Допускаются пустые строки в кавычках ('').

Улучшена совместимость с оболочками

Добавлено в версии 3.6.

Класс shlex обеспечивает совместимость с парсингом, выполняемым распространенными оболочками Unix, такими как bash, dash и sh. Чтобы воспользоваться этой совместимостью, укажите в конструкторе аргумент punctuation_chars. По умолчанию это False, что сохраняет поведение до версии 3.6. Однако, если он установлен в True, то парсинг символов ();<>|& изменяется: любое выполнение данных символов возвращается как одиночный токен. Хотя этого недостаточно для полноценного парсинга оболочек (который выходит за рамки стандартной библиотеки, учитывая множество оболочек), он позволяет выполнять обработку командных строк проще, чем в противном случае. Чтобы проиллюстрировать, вы можете увидеть разницу в следующем фрагменте:

 >>> import shlex
 >>> text = "a && b; c && d || e; f >'abc'; (def \"ghi\")"
 >>> s = shlex.shlex(text, posix=True)
 >>> s.whitespace_split = True
 >>> list(s)
 ['a', '&&', 'b;', 'c', '&&', 'd', '||', 'e;', 'f', '>abc;', '(def', 'ghi)']
 >>> s = shlex.shlex(text, posix=True, punctuation_chars=True)
 >>> s.whitespace_split = True
 >>> list(s)
 ['a', '&&', 'b', ';', 'c', '&&', 'd', '||', 'e', ';', 'f', '>', 'abc', ';',
 '(', 'def', 'ghi', ')']

Конечно, будут возвращены токены, недопустимые для оболочек, и вам нужно будет реализовать собственные проверки ошибок для возвращаемых токенов.

Вместо передачи True в качестве значения параметра punctuation_chars вы можете передать строку с определенными символами, которые будут использоваться для определения того, какие символы составляют пунктуацию. Например:

>>> import shlex
>>> s = shlex.shlex("a && b || c", punctuation_chars="|")
>>> list(s)
['a', '&', '&', 'b', '||', 'c']

Примечание

Когда указан punctuation_chars, wordchars атрибут дополнен символами ~-./*?=. Именно потому, что данные символы могут появляться в именах файлов (включая подстановочные знаки) и в командной строке аргументы (например, --color=auto). Следовательно:

>>> import shlex
>>> s = shlex.shlex('~/a && b-c --color=auto || d *.py?',
...                 punctuation_chars=True)
>>> list(s)
['~/a', '&&', 'b-c', '--color=auto', '||', 'd', '*.py?']

Однако для максимально точного соответствия оболочке рекомендуется всегда использовать posix и whitespace_split при использовании punctuation_chars, что полностью отменяет wordchars.

Для лучшего эффекта punctuation_chars следует устанавливать вместе с posix=True. (Обратите внимание, что posix=False является значением по умолчанию для shlex.)