shlex — 简单的词法分析 — Python 文档

来自菜鸟教程
Python/docs/3.9/library/shlex
跳转至:导航、​搜索

shlex — 简单的词法分析

源代码: :source:`Lib/shlex.py`



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

shlex 模块定义了以下函数:

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

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

笔记

由于 split() 函数实例化了一个 shlex 实例,因此将 None 传递给 s 将从标准输入中读取要拆分的字符串。

自 3.9 版起已弃用:s 传递 None 将在未来的 Python 版本中引发异常。

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 命令行中的一个标记,用于无法使用列表的情况。

这个习语是不安全的:

>>> 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,则字符 ();<>|& 的解析会发生变化:这些字符的任何运行(视为标点字符)都作为单个标记返回。 如果设置为非空字符串,则这些字符将用作标点字符。 wordchars 属性中出现在 punctuation_chars 中的任何字符都将从 wordchars 中删除。 有关详细信息,请参阅 改进与壳的兼容性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 不为空,则可以出现在文件名规范和命令行参数中的字符 ~-./*?= 也将包含在该属性中,任何出现在 [ X223X] 将从 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 值表示;
  • 允许引用空字符串 ()。


改进了与壳的兼容性

3.6 版中的新功能。


shlex 类提供与 bashdashsh 等常见 Unix shell 执行的解析的兼容性。 要利用这种兼容性,请在构造函数中指定 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_chars 应与 posix=True 一起设置。 (注意 posix=Falseshlex 的默认值。)