15.7. shlex — 解析 Shell 风格语法

未匹配的标注

目的:对Shell样式语法进行词法分析。

shlex模块实现了一个类,用于解析简单的类似于shell的语法。它可用于编写特定于域的语言或解析带引号的字符串(比表面上看起来复杂的任务)。

解析带引号的字符串

使用输入文本时,一个常见的问题是将一系列带引号的单词标识为单个实体。分隔引号上的文本并不总是能按预期方式工作,尤其是在存在嵌套的引号级别时。以以下文本为例。

This string has embedded "double quotes" and
'single quotes' in it, and even "a 'nested example'".

天真的方法是构造一个正则表达式,以找到引号外的文本部分,以将其与引号内的文本分开,反之亦然。这将不必要地变得复杂,并且容易出现由撇号或错别字等边缘情况导致的错误。更好的解决方案是使用真正的解析器,例如shlex模块提供的解析器。这是一个简单的示例,该示例使用shlex类打印在输入文件中标识的令牌。

shlex_example.py

import shlex
import sys

if len(sys.argv) != 2:
    print('Please specify one filename on the command line.')
    sys.exit(1)

filename = sys.argv[1]
with open(filename, 'r') as f:
    body = f.read()
print('ORIGINAL: {!r}'.format(body))
print()

print('TOKENS:')
lexer = shlex.shlex(body)
for token in lexer:
    print('{!r}'.format(token))

当对带有嵌入式引号的数据运行时,解析器将生成预期令牌的列表。

$ python3 shlex_example.py quotes.txt

ORIGINAL: 'This string has embedded "double quotes" and..singl
e quotes. in it, and even "a .nested example."..'

TOKENS:
'This'
'string'
'has'
'embedded'
'"double quotes"'
'and'
"'single quotes'"
'in'
'it'
','
'and'
'even'
'"a .nested example."'
'.'

还处理单引号,例如撇号。考虑这个输入文件。

This string has an embedded apostrophe, doesn't it?

带有嵌入式撇号的令牌没有问题。

$ python3 shlex_example.py apostrophe.txt

ORIGINAL: "This string has an embedded apostrophe, doesn't it?"

TOKENS:
'This'
'string'
'has'
'an'
'embedded'
'apostrophe'
','
"doesn't"
'it'
'?'

为Shell制作安全的字符串

quote()函数执行相反的操作,转义现有的引号并为字符串添加缺少的引号,以使它们在shell命令中可以安全使用。

shlex_quote.py

import shlex

examples = [
    "Embedded'SingleQuote",
    'Embedded"DoubleQuote',
    'Embedded Space',
    '~SpecialCharacter',
    r'Back.lash',
]

for s in examples:
    print('ORIGINAL : {}'.format(s))
    print('QUOTED   : {}'.format(shlex.quote(s)))
    print()

使用subprocess.Popen时通常使用参数列表通常更安全,但是在不可能的情况下quote()通过确保特殊字符和白色来提供某种保护空格正确引用。

$ python3 shlex_quote.py

ORIGINAL : Embedded'SingleQuote
QUOTED   : 'Embedded'"'"'SingleQuote'

ORIGINAL : Embedded"DoubleQuote
QUOTED   : 'Embedded"DoubleQuote'

ORIGINAL : Embedded Space
QUOTED   : 'Embedded Space'

ORIGINAL : ~SpecialCharacter
QUOTED   : '~SpecialCharacter'

ORIGINAL : Back.lash
QUOTED   : 'Back.lash'

嵌入式注释

由于解析器旨在与命令语言一起使用,因此它需要处理注释。默认情况下,之后的任何文本均被视为注释的一部分并被忽略。由于解析器的性质,仅支持单字符注释前缀。可以通过注释器属性来配置所使用的注释字符集。

$ python3 shlex_example.py comments.txt

ORIGINAL: 'This line is recognized..# But this line is ignored.
.And this line is processed.'

TOKENS:
'This'
'line'
'is'
'recognized'
'.'
'And'
'this'
'line'
'is'
'processed'
'.'

将字符串拆分为令牌

要将现有字符串拆分为组件标记,便利功能split()是解析器的简单包装器。

shlex_split.py

import shlex

text = """This text has "quoted parts" inside it."""
print('ORIGINAL: {!r}'.format(text))
print()

print('TOKENS:')
print(shlex.split(text))

结果是一个列表。

$ python3 shlex_split.py

ORIGINAL: 'This text has "quoted parts" inside it.'

TOKENS:
['This', 'text', 'has', 'quoted parts', 'inside', 'it.']

包括其他代币来源

shlex类包含几个控制其行为的配置属性。 属性通过允许一个令牌流包含另一个令牌流来启用代码(或配置)重用功能。这类似于Bourne shell source运算符,因此得名。

shlex_source.py

import shlex

text = "This text says to source quotes.txt before continuing."
print('ORIGINAL: {!r}'.format(text))
print()

lexer = shlex.shlex(text)
lexer.wordchars += '.'
lexer.source = 'source'

print('TOKENS:')
for token in lexer:
    print('{!r}'.format(token))

原始文本中的字符串“ sourcequotes.txt”受到特殊处理。由于词法分析器的source属性设置为“ source”,因此当遇到关键字时,将自动包括出现在下一行的文件名。为了使文件名显示为单个令牌,需要将字符添加到单词中包含的字符列表中(否则,`` quotes.txt''将变为三个标记,“ 引号”,“ ”,“ txt”。这就是输出的样子。

$ python3 shlex_source.py

ORIGINAL: 'This text says to source quotes.txt before
continuing.'

TOKENS:
'This'
'text'
'says'
'to'
'This'
'string'
'has'
'embedded'
'"double quotes"'
'and'
"'single quotes'"
'in'
'it'
','
'and'
'even'
'"a .nested example."'
'.'
'before'
'continuing.'

源功能使用一种名为sourcehook()的方法来加载其他输入源,因此shlex的子类可以提供从文件以外的位置加载数据的替代实现。

控制解析器

先前的示例演示了更改wordchars值以控制单词中包含哪些字符。也可以将 quotes  字符设置为使用附加或替代引号。每个引号必须是单个字符,因此不可能有不同的开和闭引号(例如,不对括号进行解析)。

shlex_table.py

import shlex

text = """|Col 1||Col 2||Col 3|"""
print('ORIGINAL: {!r}'.format(text))
print()

lexer = shlex.shlex(text)
lexer.quotes = '|'

print('TOKENS:')
for token in lexer:
    print('{!r}'.format(token))

在此示例中,每个表格单元格都包裹在竖线中。

$ python3 shlex_table.py

ORIGINAL: '|Col 1||Col 2||Col 3|'

TOKENS:
'|Col 1|'
'|Col 2|'
'|Col 3|'

也可以控制用于分割单词的空白字符。
shlex_whitespace.py

import shlex
import sys

if len(sys.argv) != 2:
    print('Please specify one filename on the command line.')
    sys.exit(1)

filename = sys.argv[1]
with open(filename, 'r') as f:
    body = f.read()
print('ORIGINAL: {!r}'.format(body))
print()

print('TOKENS:')
lexer = shlex.shlex(body)
lexer.whitespace += '.,'
for token in lexer:
    print('{!r}'.format(token))

如果将shlex_example.py中的示例修改为包含句点和逗号,则结果将更改。

$ python3 shlex_whitespace.py quotes.txt

ORIGINAL: 'This string has embedded "double quotes" and..singl
e quotes. in it, and even "a .nested example."..'

TOKENS:
'This'
'string'
'has'
'embedded'
'"double quotes"'
'and'
"'single quotes'"
'in'
'it'
'and'
'even'
'"a .nested example."'

错误处理

当解析器在关闭所有带引号的字符串之前遇到其输入的结尾时,它将引发ValueError。发生这种情况时,检查解析器在处理输入时维护的某些属性会很有用。例如,infile是指正在处理的文件的名称(如果一个文件源于另一个文件,则它可能与原始文件不同)。 lineno报告发现错误时的行。 lineno通常是文件的末尾,可能与第一个引号相距很远。 令牌属性包含有效令牌中尚未包含的文本缓冲区。 error_leader()方法以类似于Unix编译器的样式生成消息前缀,这使诸如emacs之类的编辑器能够解析错误并将用户直接带到无效行。

shlex_errors.py

import shlex

text = """This line is ok.
This line has an "unfinished quote.
This line is ok, too.
"""

print('ORIGINAL: {!r}'.format(text))
print()

lexer = shlex.shlex(text)

print('TOKENS:')
try:
    for token in lexer:
        print('{!r}'.format(token))
except ValueError as err:
    first_line_of_error = lexer.token.splitlines()[0]
    print('ERROR: {} {}'.format(lexer.error_leader(), err))
    print('following {!r}'.format(first_line_of_error))

该示例产生此输出。

$ python3 shlex_errors.py

ORIGINAL: 'This line is ok..This line has an "unfinished quote.
.This line is ok, too..'

TOKENS:
'This'
'line'
'is'
'ok'
'.'
'This'
'line'
'has'
'an'
ERROR: "None", line 4:  No closing quotation
following '"unfinished quote.'

POSIX与非POSIX解析

解析器的默认行为是使用不兼容POSIX的向后兼容样式。对于 POSIX 行为,在构造解析器时设置posix参数。

shlex_posix.py

import shlex

examples = [
    'Do"Not"Separate',
    '"Do"Separate',
    'Escaped . Character not in quotes',
    'Escaped "." Character in double quotes',
    "Escaped '.' Character in single quotes",
    r"Escaped '.' ... single quote",
    r'Escaped "." ... double quote',
    ".'Strip extra layer of quotes'.",
]

for s in examples:
    print('ORIGINAL : {!r}'.format(s))
    print('non-POSIX: ', end='')

    non_posix_lexer = shlex.shlex(s, posix=False)
    try:
        print('{!r}'.format(list(non_posix_lexer)))
    except ValueError as err:
        print('error({})'.format(err))

    print('POSIX    : ', end='')
    posix_lexer = shlex.shlex(s, posix=True)
    try:
        print('{!r}'.format(list(posix_lexer)))
    except ValueError as err:
        print('error({})'.format(err))

    print()

这是解析行为差异的一些示例。

$ python3 shlex_posix.py

ORIGINAL : 'Do"Not"Separate'
non-POSIX: ['Do"Not"Separate']
POSIX    : ['DoNotSeparate']

ORIGINAL : '"Do"Separate'
non-POSIX: ['"Do"', 'Separate']
POSIX    : ['DoSeparate']

ORIGINAL : 'Escaped .e Character not in quotes'
non-POSIX: ['Escaped', '.', 'e', 'Character', 'not', 'in',
'quotes']
POSIX    : ['Escaped', 'e', 'Character', 'not', 'in', 'quotes']

ORIGINAL : 'Escaped ".e" Character in double quotes'
non-POSIX: ['Escaped', '".e"', 'Character', 'in', 'double',
'quotes']
POSIX    : ['Escaped', '.e', 'Character', 'in', 'double',
'quotes']

ORIGINAL : "Escaped '.e' Character in single quotes"
non-POSIX: ['Escaped', "'.e'", 'Character', 'in', 'single',
'quotes']
POSIX    : ['Escaped', '.e', 'Character', 'in', 'single',
'quotes']

ORIGINAL : 'Escaped .... ."..." single quote'
non-POSIX: error(No closing quotation)
POSIX    : ['Escaped', '. ."."', 'single', 'quote']

ORIGINAL : 'Escaped "."" ...".. double quote'
non-POSIX: error(No closing quotation)
POSIX    : ['Escaped', '"', '.".', 'double', 'quote']

ORIGINAL : '".Strip extra layer of quotes."'
non-POSIX: ['".Strip extra layer of quotes."']
POSIX    : ["'Strip extra layer of quotes'"]

另请参见

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/shlex-pa...

译文地址:https://learnku.com/docs/pymotw/shlex-pa...

上一篇 下一篇
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~