shlex — 简单词法分析

源代码: Lib/shlex.py


shlex 类使得为类似于 Unix shell 的简单语法编写词法分析器变得容易。这通常对于编写小型语言(例如,用于 Python 应用程序的运行控制文件)或解析带引号的字符串非常有用。

shlex 模块定义了以下函数

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

使用类似 shell 的语法拆分字符串 s。如果 commentsFalse(默认值),则将禁用对给定字符串中注释的解析(将 commenters 实例的 shlex 属性设置为字符串)。此函数默认情况下以 POSIX 模式运行,但如果 posix 参数为 false,则使用非 POSIX 模式。

在 3.12 版更改: s 参数传递 None 现在会引发异常,而不是读取 sys.stdin

shlex.join(split_command)

连接列表 split_command 的标记并返回一个字符串。此函数是 split() 的逆函数。

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

返回值经过 shell 转义,以防止注入漏洞(请参阅 quote())。

3.8 版新加入.

shlex.quote(s)

返回字符串 s 的 shell 转义版本。返回值是一个字符串,可以在 shell 命令行中安全地用作一个标记,适用于不能使用列表的情况。

警告

shlex 模块仅设计用于 Unix shell

quote() 函数不能保证在非 POSIX 兼容的 shell 或来自其他操作系统(如 Windows)的 shell 上正确执行。在此类 shell 上执行由此模块引用的命令可能会导致命令注入漏洞。

请考虑使用使用列表传递命令参数的函数,例如 subprocess.run(),并使用 shell=False

这种习惯用法是不安全的

>>> filename = 'somefile; rm -rf ~'
>>> command = 'ls -l {}'.format(filename)
>>> print(command)  # executed by a shell: boom!
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 shell 和 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 不为 true(默认值)时,shlex 实例将以兼容模式运行。当以 POSIX 模式运行时,shlex 将尝试尽可能接近 POSIX shell 解析规则。punctuation_chars 参数提供了一种使行为更接近于真实 shell 解析方式的方法。它可以采用多个值:默认值 False 保留了 Python 3.5 及更早版本中的行为。如果设置为 True,则字符 ();<>|& 的解析将发生变化:这些字符(被视为标点字符)的任何连续出现都将作为单个标记返回。如果设置为一个非空的字符字符串,则这些字符将用作标点字符。出现在 punctuation_chars 中的 wordchars 属性中的任何字符都将从 wordchars 中删除。有关更多信息,请参阅 改进的 Shell 兼容性punctuation_chars 只能在创建 shlex 实例时设置,之后不能修改。

在 3.6 版更改: 添加了 punctuation_chars 参数。

参见

模块 configparser

用于解析类似于 Windows .ini 文件的配置文件的解析器。

shlex 对象

shlex 实例具有以下方法

shlex.get_token()

返回一个标记。如果使用 push_token() 堆叠了标记,则从堆栈中弹出一个标记。否则,从输入流中读取一个标记。如果读取时遇到立即的文件结尾,则返回 eof(在非 POSIX 模式下为空字符串 (''),在 POSIX 模式下为 None)。

shlex.push_token(str)

将参数推送到标记堆栈。

shlex.read_token()

读取原始标记。忽略回推堆栈,并且不解释源请求。(这通常不是一个有用的入口点,这里记录它只是为了完整性。)

shlex.sourcehook(filename)

shlex 检测到源请求时(请参阅下面的 source),此方法将接收下一个标记作为参数,并期望返回一个由文件名和打开的类文件对象组成的元组。

通常,此方法首先会去除参数中的任何引号。如果结果是绝对路径名,或者之前没有生效的源请求,或者之前的源是流(例如 sys.stdin),则结果将保持不变。否则,如果结果是相对路径名,则会将源包含堆栈中紧邻其之前的文件名的目录部分添加到其前面(此行为类似于 C 预处理器处理 #include "file.h" 的方式)。

操作结果将被视为文件名,并作为元组的第一个组件返回,并调用 open() 来生成第二个组件。(注意:这与实例初始化中参数的顺序相反!)

之所以公开此钩子,是为了让您可以使用它来实现目录搜索路径、文件扩展名的添加以及其他命名空间技巧。没有相应的“关闭”钩子,但是当 shlex 实例在堆叠的输入流上到达 EOF 时,它将调用源输入流的 close() 方法。

要更明确地控制源堆栈,请使用 push_source()pop_source() 方法。

shlex.push_source(newstream, newfile=None)

将输入源流推送到输入堆栈。如果指定了 filename 参数,则稍后可以在错误消息中使用它。这与 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 中)都将从 wordchars 中删除。如果 whitespace_split 设置为 True,则这将不起作用。

shlex.whitespace

将被视为空格并被跳过的字符。空格界定标记。默认情况下,包括空格、制表符、换行符和回车符。

shlex.escape

将被视为转义符的字符。这仅在 POSIX 模式下使用,默认情况下仅包含 '\'

shlex.quotes

将被视为字符串引号的字符。标记会一直累积,直到再次遇到相同的引号(因此,不同的引号类型会像在 shell 中一样相互保护)。默认情况下,包括 ASCII 单引号和双引号。

shlex.escapedquotes

quotes 中的字符,将解释 escape 中定义的转义字符。这仅在 POSIX 模式下使用,默认情况下仅包含 '"'

shlex.whitespace_split

如果为 True,则标记将仅在空格处拆分。例如,这对于使用 shlex 解析命令行、以类似于 shell 参数的方式获取标记非常有用。与 punctuation_chars 结合使用时,除了这些字符之外,标记还将在空格处拆分。

3.8 版更改: punctuation_chars 属性已与 whitespace_split 属性兼容。

shlex.infile

当前输入文件的名称,在类实例化时初始设置或由后续源请求堆叠。在构造错误消息时检查它可能很有用。

shlex.instream

shlex 实例从中读取字符的输入流。

shlex.source

此属性默认为 None。如果为其分配一个字符串,则该字符串将被识别为类似于各种 shell 中的 source 关键字的词法级别包含请求。也就是说,紧随其后的标记将作为文件名打开,并且输入将从该流中获取,直到 EOF,此时将调用该流的 close() 方法,并且输入源将再次成为原始输入流。源请求可以堆叠任意数量的级别。

shlex.debug

如果此属性是数字且为 1 或更大,则 shlex 实例将打印有关其行为的详细进度输出。如果需要使用它,可以阅读模块源代码以了解详细信息。

shlex.lineno

源代码行号(到目前为止看到的换行符计数加一)。

shlex.token

标记缓冲区。在捕获异常时检查它可能很有用。

shlex.eof

用于确定文件结束的标记。在非 POSIX 模式下,这将设置为空字符串 (''),在 POSIX 模式下设置为 None

shlex.punctuation_chars

只读属性。将被视为标点的字符。标点字符的运行将作为单个标记返回。但是,请注意,不会执行语义有效性检查:例如,'>>>' 可以作为标记返回,即使 shell 可能无法识别它。

3.6 版新加入.

解析规则

在非 POSIX 模式下运行时,shlex 将尝试遵循以下规则。

  • 单词中不识别引号字符(Do"Not"Separate 被解析为单个单词 Do"Not"Separate);

  • 不识别转义字符;

  • 用引号括起来的字符保留引号内所有字符的字面值;

  • 结束引号分隔单词("Do"Separate 被解析为 "Do"Separate);

  • 如果 whitespace_splitFalse,则任何未声明为单词字符、空格或引号的字符都将作为单个字符标记返回。如果为 True,则 shlex 将仅在空格处拆分单词;

  • EOF 用空字符串 ('') 表示;

  • 无法解析空字符串,即使加引号也不行。

在 POSIX 模式下运行时,shlex 将尝试遵循以下解析规则。

  • 引号被去除,并且不分隔单词("Do"Not"Separate" 被解析为单个单词 DoNotSeparate);

  • 非引号转义字符(例如 '\')保留其后下一个字符的字面值;

  • 用不属于 escapedquotes 的引号(例如 "'")括起来的字符保留引号内所有字符的字面值;

  • 用属于 escapedquotes 的引号(例如 '"')括起来的字符保留引号内所有字符的字面值,但 escape 中提到的字符除外。转义字符仅在使用引号或转义字符本身后才保留其特殊含义。否则,转义字符将被视为普通字符。

  • EOF 用 None 值表示;

  • 允许使用带引号的空字符串 ('')。

改进与 Shell 的兼容性

3.6 版新加入.

shlex 类提供与常见 Unix shell(如 bashdashsh)执行的解析的兼容性。要利用此兼容性,请在构造函数中指定 punctuation_chars 参数。它默认为 False,这保留了 3.6 之前的行为。但是,如果将其设置为 True,则字符 ();<>|& 的解析会发生变化:这些字符的任何运行都将作为单个标记返回。虽然这对于 shell 的完整解析器来说还很短(考虑到 shell 的多样性,这超出了标准库的范围),但它确实允许您比其他方式更轻松地执行命令行处理。为了说明这一点,您可以看到以下代码段中的区别

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

当然,将返回对 shell 无效的标记,您需要对返回的标记实施自己的错误检查。

您可以传递一个包含特定字符的字符串,而不是传递 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?']

但是,为了尽可能地匹配 shell,建议在使用 punctuation_chars 时始终使用 posixwhitespace_split,这将完全否定 wordchars

为了获得最佳效果,应将 punctuation_charsposix=True 一起设置。(请注意,posix=Falseshlex 的默认值。)