Python 的 shlex 模块:简单词法分析的利器

Python 的 shlex 模块:简单词法分析的利器

本文将深入介绍 Python 的shlex模块,该模块用于编写类似 Unix shell 的简单词法分析程序,在解析带引号的字符串、编写 “迷你语言” 等场景中发挥重要作用。通过对shlex模块的函数、类、解析规则以及应用示例的详细讲解,帮助读者全面掌握其使用方法,同时对比类似模块,推荐学习资源,助力读者深入理解和运用shlex模块。

一、shlex 模块概述

shlex模块是 Python 标准库的一部分,其源代码位于Lib/shlex.py。它主要用于将字符串按照类似 Unix shell 的语法进行词法分析,把字符串拆分成一个个词法单元(token),也可以将词法单元列表重新组合成字符串。该模块通常应用于编写 “迷你语言”(如 Python 应用程序的运行控制文件)或解析带引号的字符串场景,为开发者处理命令行输入、配置文件解析等任务提供了便利。

二、shlex 模块的函数

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

该函数用于按照类似 shell 的语法拆分字符串scomments参数默认为False,表示不解析字符串中的注释(shlex实例的commenters属性设为空字符串)。posix参数默认为True,表示采用 POSIX 模式进行拆分;若设为False,则采用非 POSIX 模式。在 Python 3.12 版本中,传入None作为s参数会引发异常,而不再是读取sys.stdin。例如:

from shlex import split
s = "echo -n 'Hello, World!'"
result = split(s)
print(result)  

上述代码将字符串"echo -n 'Hello, World!'"按照 POSIX 模式进行拆分,输出结果为['echo', '-n', 'Hello, World!']

(二)shlex.join(split_command)

join函数与split函数相反,用于将列表split_command中的词法单元串联起来,返回一个字符串。为防止注入漏洞,返回值会经过 shell 转义(类似于quote函数的处理)。该函数在 Python 3.8 版本中添加。例如:

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

运行上述代码,输出结果为echo -n 'Multiple words',实现了将词法单元列表转换为字符串的功能。

(三)shlex.quote(s)

quote函数返回经过 shell 转义的字符串s,确保返回的字符串可以安全地用作 shell 命令行中的词法单元,适用于不能使用列表传递参数的场合。需要注意的是,shlex模块仅适用于 Unix shell,在不兼容 POSIX 的 shell 或其他操作系统(如 Windows)的 shell 上,quote函数可能无法正常使用,甚至存在命令注入漏洞。建议在这些场景下使用命令参数以列表形式给出的函数,如带shell=False参数的subprocess.run()。例如:

from shlex import quote
filename ='somefile; rm -rf ~'
command = 'ls -l {}'.format(quote(filename))
print(command)  

上述代码对可能存在安全风险的文件名进行了转义处理,避免了命令注入漏洞。

三、shlex 类详解

(一)构造函数shlex.shlex(instream=None, infile=None, posix=False, punctuation_chars=False)

shlex类的实例是词法分析器对象,构造函数用于初始化词法分析器。

  • instream:指定读取字符的输入源,可以是具有read()readline()方法的文件 / 流对象,也可以是一个字符串。若未指定,默认从sys.stdin获取输入。
  • infile:第二个可选参数,是一个文件名字符串,用于设置infile属性的初始值。当instream参数被省略或等于sys.stdin时,infile默认为"stdin"
  • posix:定义操作模式,默认值为False,表示工作于兼容模式;若设为True,则尽可能应用 POSIX shell 解析规则。
  • punctuation_chars:该参数在 Python 3.6 版本中添加,用于控制对特定字符的解析方式。默认值为False,保持 Python 3.5 及更早版本的行为。若设为True();<>|&这些字符将作为独立的词法单元被返回(视作标点符号);若设为非空字符串,则这些字符将被用作标点符号,同时出现在punctuation_chars中的wordchars属性中的字符会从wordchars中删除。punctuation_chars只能在创建shlex实例时设置,之后无法修改。

(二)常用方法

  1. get_token():返回一个词法单元。首先检查词法单元堆栈,若堆栈中有元素,则从堆栈中弹出一个词法单元;否则从输入流中读取一个。当读取到文件结束符时,在非 POSIX 模式下返回空字符串'',在 POSIX 模式下返回None
  2. push_token(str):将字符串str压入词法单元堆栈,后续调用get_token()时会优先从堆栈中获取该词法单元。
  3. read_token():读取一个原始词法单元,忽略堆栈且不解释源请求,通常较少使用。
  4. sourcehook(filename):当shlex检测到源请求(source属性)时调用,应返回一个由文件名和打开的文件对象组成的元组。该方法主要用于实现路径搜索、添加文件扩展名等功能。
  5. push_source(newstream, newfile=None):将输入源流压入输入堆栈,若指定了newfile参数,后续错误信息中会使用该文件名。sourcehook()方法内部会调用此方法。
  6. pop_source():从输入堆栈中弹出最后一条输入源,当遇到输入流的文件结束符时,内部也会调用此方法。
  7. error_leader(infile=None, lineno=None):生成一条错误信息的首部,格式为'"%s", line %d:',其中%s会被替换为当前源文件的名称,%d会被替换为当前输入行号(可通过可选参数覆盖),用于以标准的、可解析的格式生成错误信息。

(三)实例变量

  1. 控制解析行为的变量
    • commenters:被视为注释起始的字符串,从注释起始到行尾的字符都会被忽略,默认值为'#'
    • wordchars:可连成多字符词法单元的字符串。默认包含所有 ASCII 字母数字和下划线,在 POSIX 模式下,Latin - 1 字符集的重音字符也包含在内。若punctuation_chars不为空,可出现在文件名规范和命令行参数中的~-./*?=<字符也会包含在内,同时punctuation_chars中的字符会从wordchars中移除。若whitespace_split设为True,该规则无效。
    • whitespace:被视为空白符并跳过的字符,是词法单元的边界,默认包含空格、制表符、换行符和回车符。
    • escape:在 POSIX 模式下,被视为转义字符,默认值为'\'
    • quotes:被视为引号的字符,词法单元中的字符会累至再次遇到同样的引号,默认包含 ASCII 单引号和双引号。
    • escapedquotesquotes中的字符会解析escape定义的转义字符,仅在 POSIX 模式下使用,默认值为'"'
    • whitespace_split:若为True,则只根据空白符拆分词法单元;与punctuation_chars一起使用时,会根据空白符和指定的标点符号拆分词法单元。在 Python 3.8 版本中,punctuation_chars属性已与whitespace_split属性兼容。
  2. 记录状态的变量
    • infile:当前输入的文件名,可能在实例化时设置,也可能由源请求堆栈生成,用于构建错误信息。
    • instreamshlex实例正在从中读取字符的输入流。
    • source:默认值为None,若给定一个字符串,则会识别为包含请求,类似于 shell 中的source关键字,用于指定新的输入源。
    • debug:若该属性为大于1的数字,shlex实例会详细输出动作进度,可通过阅读源代码了解详细信息。
    • lineno:源的行数(到目前为止读到的换行符数量加 1)。
    • token:词法单元的缓冲区,在捕获异常时可能会用到。
    • eof:用于确定文件结束的词法单元,在非 POSIX 模式下为'',在 POSIX 模式下为None
    • punctuation_chars(只读):表示应视作标点符号的字符,标点符号将作为单个词法单元返回,但不会进行语义有效性检查。

四、解析规则对比

解析规则非 POSIX 模式POSIX 模式
引号处理不识别单词中的引号(如Do"Not"Separate解析为一个单词);成对的引号会分离单词(如"Do"Separate解析为"Do"Separate引号会被剔除,且不会拆分单词(如"Do"Not"Separate"解析为DoNotSeparate
转义字符不识别转义字符未加引号包裹的转义字符(如'\')保留后一个字符的字面意思
引号内字符处理引号包裹的字符保留字面意思若引号中的字符不属于escapedquotes(例如"'"),保留引号中所有字符的字面值;若属于escapedquotes(例如'"'),保留引号中所有字符的字面意思,属于escape中的字符除外。仅当后跟后半个引号或转义字符本身时,转义字符才保留其特殊含义,否则视作普通字符
文件结束标识EOF 用空字符串('')表示EOF 用None表示
空字符串处理空字符串无法解析,即便是加了引号允许出现引号包裹的空字符串(''

五、应用示例

(一)简单字符串拆分与重组

from shlex import split, join

# 拆分字符串
s = "echo -e 'Hello\nWorld'"
split_result = split(s)
print(split_result)  

# 重组字符串
join_result = join(split_result)
print(join_result)  

上述代码展示了splitjoin函数的基本用法,先将字符串拆分成词法单元列表,再将列表重组为字符串。

(二)使用 shlex 类解析字符串

import shlex

text = "a && b; c && d || e; f >'abc'; (def \" ghi\")"
s = shlex.shlex(text, posix=True)
s.whitespace_split = True
print(list(s))  

s = shlex.shlex(text, posix=True, punctuation_chars=True)
s.whitespace_split = True
print(list(s))  

这段代码通过创建shlex类的实例,展示了在不同punctuation_chars设置下对字符串的解析结果。

六、与类似模块对比

对比项shlex 模块re 模块argparse 模块
功能按照类似 Unix shell 的语法进行词法分析,将字符串拆分为词法单元,或重组词法单元为字符串基于正则表达式进行文本匹配、替换、分割等操作,功能更强大、灵活,但使用相对复杂用于解析命令行参数,生成帮助信息,处理命令行选项和参数的各种情况
适用场景适用于解析类似 Unix shell 命令行的字符串,编写 “迷你语言”,处理带引号字符串适用于各种文本处理场景,如文本清洗、数据提取、格式验证等主要用于开发命令行工具,处理命令行参数的解析和验证
特点简单易用,专注于类 Unix shell 语法的词法分析高度灵活,可定制匹配规则,但需要掌握正则表达式语法专门针对命令行参数处理,提供丰富的参数类型检查、默认值设置等功能

相关学习资源推荐

  1. Python 官方文档:https://docs.python.org/zh-cn/3.12/library/shlex.html,详细介绍了shlex模块的函数、类、方法、实例变量以及解析规则等内容,是学习shlex模块的权威资料。
  2. Tekin的Python编程秘籍库Python 实用知识与技巧分享,涵盖基础、爬虫、数据分析等干货 本 Python 专栏聚焦实用知识,深入剖析基础语法、数据结构。分享爬虫、数据分析等热门领域实战技巧,辅以代码示例。无论新手入门还是进阶提升,都能在此收获满满干货,快速掌握 Python 编程精髓。

总结

shlex模块为 Python 开发者提供了处理类似 Unix shell 语法字符串的便捷方式,通过splitjoin等函数以及shlex类的各种方法和实例变量,可以轻松实现字符串的词法分析和重组。在实际应用中,要根据具体需求选择合适的模块,同时注意shlex模块在不同模式下的解析规则差异。结合推荐的学习资源深入学习,有助于更好地掌握shlex模块的使用技巧,提升编程能力。

TAG:Python;shlex 模块;词法分析;Unix shell;字符串解析;命令行处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tekin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值