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

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

shlex.push_token(str)

将参数压入 token 堆栈。

shlex.read_token()

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

shlex.sourcehook(filename)

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

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

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

公开此钩子是为了方便您使用它来实现目录搜索路径、添加文件扩展名和其他命名空间 hacks。没有相应的“关闭”钩子,但是当 shlex 实例返回 EOF 时,它会调用源输入流的 close() 方法。

为了更明确地控制源堆叠,请使用 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

将累积到多字符 token 中的字符字符串。默认情况下,包括所有 ASCII 字母数字字符和下划线。在 POSIX 模式下,还包括 Latin-1 集中带有重音符号的字符。如果 punctuation_chars 不为空,则可能出现在文件名规范和命令行参数中的字符 ~-./*?= 也将包含在此属性中,并且 punctuation_chars 中出现的任何字符都将从 wordchars 中删除(如果它们存在于那里)。如果 whitespace_split 设置为 True,这将不起作用。

shlex.whitespace

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

shlex.escape

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

shlex.quotes

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

shlex.escapedquotes

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

shlex.whitespace_split

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

在 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 的默认值。)