Python shlex 拆分带引号文本:得到词元之后,还没有执行任何命令

10-01 3阅读

一个小工具接受类似“动作加参数”的文本,名称里允许空格,也允许用引号表示空字符串。直接按空白 split 会把一个名称切成两段;自己反复替换引号,又容易破坏转义和错误提示。shlex 可以处理这种接近 Unix shell 的词法规则,但它并不是一套自动执行命令的系统。

先把用途限定为解析一行配置:第一项是动作名称,后面是供业务解释的参数。引号只控制词元边界,不会作为结果的一部分保留。得到列表以后,仍需自己检查动作是否存在、参数数量是否匹配,以及每个参数允许什么内容。

Python shlex 拆分带引号文本:得到词元之后,还没有执行任何命令

AI概念配图,非真实界面:以抽象物件说明本文主题,不代表运行结果。

先验证拆分结果,再处理后续业务

以下程序保存为 demo.py,用 Python 3.8 或更高版本运行。第一段保留带空格的名称和一个空参数,同时证明美元变量名、星号和管道符只是得到的文本。程序没有启动任何外部命令,所有输出都来自词元列表或断言。

接着用 join 把词元列表变成适合 POSIX 风格 shell 的展示字符串,再拆回来比较。这种往返验证关注的是参数边界,不保证恢复原来的空格数量、原来选择的单双引号或文字排版。若要做编辑器,应另外保留用户原文。

import shlex

line = 'deploy "blue team" "" $REGION *.txt | cat'
parts = shlex.split(line)
assert parts == ["deploy", "blue team", "", "$REGION", "*.txt", "|", "cat"]
print(parts)
shown = shlex.join(parts)
assert shlex.split(shown) == parts

assert shlex.split("run # note") == ["run", "#", "note"]
assert shlex.split("run # note", comments=True) == ["run"]
assert shlex.split('run "# note"', comments=True) == ["run", "# note"]
assert shlex.split("") == []
assert shlex.split('""') == [""]

lexer = shlex.shlex("left|right", posix=True, punctuation_chars=True)
lexer.whitespace_split = True
lexer.commenters = ""
assert list(lexer) == ["left", "|", "right"]
try:
    shlex.split('run "unfinished')
except ValueError:
    pass
else:
    raise AssertionError("unclosed quote accepted")
print("tokens, round trip, comments and malformed input passed")

注释与标点规则必须主动确定

默认 split 不把井号当注释,所以示例中的井号及后续文字仍在列表中。启用 comments 后,未被引号保护的井号会开始注释。两种方式都合理,取决于你的配置格式;不要让开发机和生产环境使用不同参数,再把差异归咎于某个特殊文件名。

较低层的 shlex 对象默认规则与便捷 split 函数不完全相同,因此示例明确指定 posix、whitespace_split 和 commenters。punctuation_chars 能把连在一起的管道符拆成独立词元,但这只是标点识别,不会验证整条文本是不是有效 shell 程序。

即使得到的列表中含有竖线,也没有建立进程间管道;美元符号没有读取环境变量,星号也没有扫描当前目录。把词元再拼接后交给 shell,会引入另一轮语法解释,风险与语义都发生变化。需要这种能力时,应重新设计并审查执行入口。

对无效输入保留清楚的失败方式

引号没有闭合时会抛出 ValueError。示例验证这一点,避免把残缺输入自动补齐后继续执行。面向用户的工具可以提示“引号未闭合”并让对方修改原文;日志里保留适当位置与错误类别即可,不必输出可能含私人内容的整行参数。

空文本拆分后是空列表,而两个引号表示一个空字符串参数。这两种情况对业务往往不同:前者可能表示没有命令,后者可能表示把某个字段清空。代码中要分别验收,不能在解析之后统一删除所有空词元,否则用户表达的信息会丢失。

shlex 面向 Unix shell 风格规则,不能直接当作 Windows 命令行或 PowerShell 的通用解析器。尤其是反斜杠、引号与路径混合时,应选择目标平台对应的语法,而不是尝试切换一个选项就假定与所有终端兼容。

实用的落地顺序是先写下微型语言允许的语法,再为带空格、空参数、井号、反斜杠和未闭合引号准备样本。若内容只是结构化配置且不需要 shell 风格输入,采用已有结构化格式通常更容易校验,也能减少用户对“这些字符会不会执行”的误解。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。