Skip to main content

24.3. shlex —简单的词法分析

源代码: Lib/shlex.py


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

shlex 模块定义以下功能:

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

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

注解

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

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() 允许您插入安全孔:

>>> 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() 兼容:

>>> 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 参数提供了一种使行为更接近实际shell解析的方式。这可以采取一些值:默认值,False,保留在Python 3.5和更早版本中看到的行为。如果设置为 True,则更改字符 ();<>|& 的解析:将这些字符(被视为标点符号)的任何运行作为单个令牌返回。如果设置为非空字符串,那些字符将用作标点符号。在 punctuation_chars 中出现的 wordchars 属性中的任何字符将从 wordchars 中删除。有关详细信息,请参阅 改进与Shell的兼容性

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

参见

模块 configparser

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

24.3.1. 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() 调用它来生成第二个组件。 (注意:这是与实例初始化中的参数顺序相反的!)

这个钩子是暴露的,所以你可以使用它来实现目录搜索路径,添加文件扩展名和其他命名空间黑客。没有相应的“关闭”钩子,但是当它返回EOF时,shlex实例将调用源输入流的 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模式下,拉丁文-1集中的重音字符也包括在内。如果 punctuation_chars 不为空,那么可能出现在文件名规范和命令行参数中的字符 ~-./*?= 也将包括在此属性中,并且出现在 punctuation_chars 中的任何字符将从 wordchars 中删除(如果它们存在)。

shlex.whitespace

将被视为空格并被跳过的字符。空白限制令牌。默认情况下,包括空格,制表符,换行符和回车。

shlex.escape

将被视为逃逸的字符。这将仅用于POSIX模式,并且只包括 '\' 默认情况下。

shlex.quotes

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

shlex.escapedquotes

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

shlex.whitespace_split

如果 True,令牌将只分裂在空格。这是有用的,例如,用 shlex 解析命令行,以类似于shell参数的方式获取令牌。如果此属性是 True,则 punctuation_chars 将不起作用,并且拆分将仅在空格上发生。当使用 punctuation_chars (旨在提供更接近shell实现的解析)时,建议将 whitespace_split 保留为 False (默认值)。

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 新版功能.

24.3.2. 解析规则

当在非POSIX模式下操作时,shlex 将尝试遵守以下规则。

  • 报价字符不能在字中识别(Do"Not"Separate 被解析为单字 Do"Not"Separate);

  • 无法识别转义字符;

  • 在引号中包含字符会保留引号内所有字符的文字值;

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

  • 如果 whitespace_splitFalse,则任何未声明为字字符,空格或引号的字符将作为单字符令牌返回。如果是 Trueshlex 只会在空格中分割单词;

  • EOF用空字符串('')用信号通知;

  • 它不可能解析空字符串,即使引号。

当在POSIX模式下操作时,shlex 将尝试服从以下解析规则。

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

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

  • 将不是 escapedquotes (例如 "'")一部分的引号中的字符保留引号内的所有字符的文字值;

  • 作为 escapedquotes (例如 '"')一部分的引号中包含的字符保留引号内所有字符的文字值,但 escape 中提到的字符除外。转义字符只有在使用中引用引号或转义字符本身时才保留其特殊含义。否则,转义字符将被视为正常字符。

  • EOF用 None 值用信号通知;

  • 允许引用空字符串('')。

24.3.3. 改进与Shell的兼容性

3.6 新版功能.

shlex 类提供与通用Unix shell(如 bashdashsh)执行的解析的兼容性。要利用此兼容性,请在构造函数中指定 punctuation_chars 参数。默认为 False,它保留3.6之前的行为。然而,如果设置为 True,则字符 ();<>|& 的解析被改变:这些字符的任何运行作为单个令牌返回。虽然这不是一个完整的解析器的shell(这将超出标准库的范围,给定的多个shell在那里),它允许您更容易地执行命令行的处理比你可以否则。为了说明,您可以看到以下代码段的区别:

import shlex

for punct in (False, True):
    if punct:
        message = 'Old'
    else:
        message = 'New'
    text = "a && b; c && d || e; f >'abc'; (def \"ghi\")"
    s = shlex.shlex(text, punctuation_chars=punct)
    print('%s: %s' % (message, list(s)))

打印出来:

Old: ['a', '&', '&', 'b', ';', 'c', '&', '&', 'd', '|', '|', 'e', ';', 'f', '>', "'abc'", ';', '(', 'def', '"ghi"', ')']
New: ['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?']