shlex
—— 簡單的詞法分析?
源代碼: Lib/shlex.py
shlex
類可用于編寫類似 Unix shell 的簡單詞法分析程序。通??捎糜诰帉憽懊阅阏Z言”(如 Python 應(yīng)用程序的運(yùn)行控制文件)或解析帶引號的字符串。
shlex
模塊中定義了以下函數(shù):
- shlex.split(s, comments=False, posix=True)?
用類似 shell 的語法拆分字符串 s。如果 comments 為
False
(默認(rèn)值),則不會解析給定字符串中的注釋 (commenters
屬性的shlex
實(shí)例設(shè)為空字符串)。 本函數(shù)默認(rèn)工作于 POSIX 模式下,但若 posix 參數(shù)為 False,則采用非 POSIX 模式。備注
Since the
split()
function instantiates ashlex
instance, passingNone
for s will read the string to split from standard input.3.9 版后已移除: 在以后的 Python 版本中,給 s 傳入
None
將觸發(fā)異常。
- shlex.join(split_command)?
將列表 split_command 中的詞法單元(token)串聯(lián)起來,返回一個(gè)字符串。本函數(shù)是
split()
的逆運(yùn)算。>>> from shlex import join >>> print(join(['echo', '-n', 'Multiple words'])) echo -n 'Multiple words'
為防止注入漏洞,返回值是經(jīng)過 shell 轉(zhuǎn)義的(參見
quote()
)。3.8 新版功能.
- shlex.quote(s)?
返回經(jīng)過 shell 轉(zhuǎn)義的字符串 s 。返回值為字符串,可以安全地用作 shell 命令行中的詞法單元,可用于不能使用列表的場合。
警告
shlex
模塊 僅適用于 Unix shell。在不兼容 POSIX 的 shell 或其他操作系統(tǒng)(如Windows)的shell上,并不保證
quote()
函數(shù)能夠正常使用。在這種 shell 中執(zhí)行用本模塊包裝過的命令,有可能會存在命令注入漏洞。請考慮采用命令參數(shù)以列表形式給出的函數(shù),比如帶了
shell=False
參數(shù)的subprocess.run()
。以下用法是不安全的:
>>> 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
及其子類的實(shí)例是一種詞義分析器對象。 利用初始化參數(shù)可指定從哪里讀取字符。 初始化參數(shù)必須是具備read()
和readline()
方法的文件/流對象,或者是一個(gè)字符串。 如果沒有給出初始化參數(shù),則會從sys.stdin
獲取輸入。 第二個(gè)可選參數(shù)是個(gè)文件名字符串,用于設(shè)置infile
屬性的初始值。 如果 instream 參數(shù)被省略或等于sys.stdin
,則第二個(gè)參數(shù)默認(rèn)為 "stdin"。 posix 參數(shù)定義了操作的模式:若 posix 不為真值(默認(rèn)),則shlex
實(shí)例將工作于兼容模式。 若運(yùn)行于 POSIX 模式下,則shlex
會盡可能地應(yīng)用 POSIX shell 解析規(guī)則。 punctuation_chars 參數(shù)提供了一種使行為更接近于真正的 shell 解析的方式。 該參數(shù)可接受多種值:默認(rèn)值、False
、保持 Python 3.5 及更早版本的行為。 如果設(shè)為True
,則會改變對字符();<>|&
的解析方式:這些字符將作為獨(dú)立的詞法單元被返回(視作標(biāo)點(diǎn)符號)。 如果設(shè)為非空字符串,則這些字符將被用作標(biāo)點(diǎn)符號。 出現(xiàn)在 punctuation_chars 中的wordchars
屬性中的任何字符都會從wordchars
中被刪除。 請參閱 改進(jìn)的 shell 兼容性 了解詳情。 punctuation_chars 只能在創(chuàng)建shlex
實(shí)例時(shí)設(shè)置,以后不能再作修改。在 3.6 版更改: 加入 punctuation_chars 參數(shù)。
參見
configparser
模塊配置文件解析器,類似于 Windows 的
.ini
文件。
shlex 對象?
shlex
實(shí)例具備以下方法:
- shlex.get_token()?
返回一個(gè)詞法單元。如果所有單詞已用
push_token()
堆疊在一起了,則從堆棧中彈出一個(gè)詞法單元。否則就從輸入流中讀取一個(gè)。如果讀取時(shí)遇到文件結(jié)束符,則會返回eof`(在非 POSIX 模式下為空字符串 `
'',在 POSIX 模式下為 ``None
)。
- shlex.push_token(str)?
將參數(shù)值壓入詞法單元堆棧。
- shlex.read_token()?
讀取一個(gè)原始詞法單元。忽略堆棧,且不解釋源請求。(通常沒什么用,只是為了完整起見。)
- shlex.sourcehook(filename)?
當(dāng)
shlex
檢測到源請求(見下面的source
),以下詞法單元可作為參數(shù),并應(yīng)返回一個(gè)由文件名和打開的文件對象組成的元組。通常本方法會先移除參數(shù)中的引號。如果結(jié)果為絕對路徑名,或者之前沒有有效的源請求,或者之前的源請求是一個(gè)流對象(比如
sys.stdin
),那么結(jié)果將不做處理。否則,如果結(jié)果是相對路徑名,那么前面將會加上目錄部分,目錄名來自于源堆棧中前一個(gè)文件名(類似于 C 預(yù)處理器對#include "file.h"
的處理方式)。結(jié)果被視為一個(gè)文件名,并作為元組的第一部分返回,元組的第二部分以此為基礎(chǔ)調(diào)用
open()
獲得。(注意:這與實(shí)例初始化過程中的參數(shù)順序相反!)此鉤子函數(shù)是公開的,可用于實(shí)現(xiàn)路徑搜索、添加文件擴(kuò)展名或黑入其他命名空間。沒有對應(yīng)的“關(guān)閉”鉤子函數(shù),但 shlex 實(shí)例在返回 EOF 時(shí)會調(diào)用源輸入流的
close()
方法。若要更明確地控制源堆棧,請采用
push_source()
和pop_source()
方法。
- shlex.push_source(newstream, newfile=None)?
將輸入源流壓入輸入堆棧。如果指定了文件名參數(shù),以后錯(cuò)誤信息中將會用到。
sourcehook()
內(nèi)部同樣使用了本方法。
- shlex.pop_source()?
從輸入堆棧中彈出最后一條輸入源。當(dāng)遇到輸入流的 EOF 時(shí),內(nèi)部也使用同一方法。
- shlex.error_leader(infile=None, lineno=None)?
本方法生成一條錯(cuò)誤信息的首部,以 Unix C 編譯器錯(cuò)誤標(biāo)簽的形式;格式為``'"%s", line %d: '
,其中 ``%s
被替換為當(dāng)前源文件的名稱,%d
被替換為當(dāng)前輸入行號(可用可選參數(shù)覆蓋)。這是個(gè)快捷函數(shù),旨在鼓勵(lì)
shlex
用戶以標(biāo)準(zhǔn)的、可解析的格式生成錯(cuò)誤信息,以便 Emacs 和其他 Unix 工具理解。
shlex
子類的實(shí)例有一些公共實(shí)例變量,這些變量可以控制詞義分析,也可用于調(diào)試。
- shlex.commenters?
將被視為注釋起始字符串。從注釋起始字符串到行尾的所有字符都將被忽略。默認(rèn)情況下只包括
'#'
。
- shlex.wordchars?
可連成多字符詞法單元的字符串。默認(rèn)包含所有 ASCII 字母數(shù)字和下劃線。在 POSIX 模式下,Latin-1 字符集的重音字符也被包括在內(nèi)。如果
punctuation_chars
不為空,則可出現(xiàn)在文件名規(guī)范和命令行參數(shù)中的~-./*?=
字符也將包含在內(nèi),任何punctuation_chars
中的字符將從wordchars
中移除。如果whitespace_split
設(shè)為True
,則本規(guī)則無效。
- shlex.whitespace?
將被視為空白符并跳過的字符。空白符是詞法單元的邊界。默認(rèn)包含空格、制表符、換行符和回車符。
- shlex.escape?
將視為轉(zhuǎn)義字符。僅適用于 POSIX 模式,默認(rèn)只包含
'\'
。
- shlex.quotes?
將視為引號的字符。詞法單元中的字符將會累至再次遇到同樣的引號(因此,不同的引號會像在 shell 中一樣相互包含。)默認(rèn)包含 ASCII 單引號和雙引號。
- shlex.whitespace_split?
若為
True
,則只根據(jù)空白符拆分詞法單元。這很有用,比如用shlex
解析命令行,用類似 shell 參數(shù)的方式讀取各個(gè)詞法單元。當(dāng)與punctuation_chars
一起使用時(shí),將根據(jù)空白符和這些字符拆分詞法單元。在 3.8 版更改:
punctuation_chars
屬性已與whitespace_split
屬性兼容。
- shlex.infile?
當(dāng)前輸入的文件名,可能是在類實(shí)例化時(shí)設(shè)置的,或者是由后來的源請求堆棧生成的。在構(gòu)建錯(cuò)誤信息時(shí)可能會用到本屬性。
- shlex.source?
本屬性默認(rèn)值為
None
。 如果給定一個(gè)字符串,則會識別為包含請求,類似于各種 shell 中的source
關(guān)鍵字。 也就是說,緊隨其后的詞法單元將作為文件名打開,作為輸入流,直至遇到 EOF 后調(diào)用流的close()
方法,然后原輸入流仍變回輸入源。Source 請求可以在詞義堆棧中嵌套任意深度。
- shlex.debug?
如果本屬性為大于
1
的數(shù)字,則shlex
實(shí)例會把動(dòng)作進(jìn)度詳細(xì)地輸出出來。若需用到本屬性,可閱讀源代碼來了解細(xì)節(jié)。
- shlex.lineno?
源的行數(shù)(到目前為止讀到的換行符數(shù)量加 1)。
- shlex.token?
詞法單元的緩沖區(qū)。在捕獲異常時(shí)可能會用到。
- shlex.eof?
用于確定文件結(jié)束的詞法單元。在非 POSIX 模式下,將設(shè)為空字符串
''
,在 POSIX 模式下被設(shè)為None
。
- shlex.punctuation_chars?
只讀屬性。表示應(yīng)視作標(biāo)點(diǎn)符號的字符。標(biāo)點(diǎn)符號將作為單個(gè)詞法單元返回。然而,請注意不會進(jìn)行語義有效性檢查:比如 “>>>” 可能會作為一個(gè)詞法單元返回,雖然 shell 可能無法識別。
3.6 新版功能.
解析規(guī)則?
在非 POSIX 模式下時(shí),shlex
會試圖遵守以下規(guī)則:
不識別單詞中的引號(
Do"Not"Separate
解析為一個(gè)單詞Do"Not"Separate
);不識別轉(zhuǎn)義字符;
引號包裹的字符保留字面意思;
成對的引號會將單詞分離(
"Do"Separate
解析為"Do"
和Separate
);如果
whitespace_split
為False
,則未聲明為單詞字符、空白或引號的字符將作為單字符的詞法單元返回。若為True
, 則shlex
只根據(jù)空白符拆分單詞。EOF 用空字符串(
''
)表示;空字符串無法解析,即便是加了引號。
在 POSIX 模式時(shí),shlex
將嘗試遵守以下解析規(guī)則:
引號會被剔除,且不會拆分單詞(
"Do"Not"Separate"
將解析為單個(gè)單詞DoNotSeparate
);未加引號包裹的轉(zhuǎn)義字符(如
'\'
)保留后一個(gè)字符的字面意思;引號中的字符不屬于
escapedquotes
(例如,"'"
),則保留引號中所有字符的字面值;若引號包裹的字符屬于
escapedquotes
(例如'"'
),則保留引號中所有字符的字面意思,屬于escape
中的字符除外。僅當(dāng)后跟后半個(gè)引號或轉(zhuǎn)義字符本身時(shí),轉(zhuǎn)義字符才保留其特殊含義。否則,轉(zhuǎn)義字符將視作普通字符;EOF 用
None
表示;允許出現(xiàn)引號包裹的空字符串(
''
)。
改進(jìn)的 shell 兼容性?
3.6 新版功能.
shlex
類提供了與常見 Unix shell(如 bash
、dash
和``sh``)的解析兼容性。為了充分利用這種兼容性,請?jiān)跇?gòu)造函數(shù)中設(shè)定 punctuation_chars
參數(shù)。該參數(shù)默認(rèn)為 False
,維持 3.6 以下版本的行為。如果設(shè)為 True
,則會改變對 ();<>|&
字符的解析方式:這些字符都將視為單個(gè)的詞法單元返回。雖然不算是完整的 shell 解析程序(考慮到 shell 的多樣性,超出了標(biāo)準(zhǔn)庫的范圍),但確實(shí)能比其他方式更容易進(jìn)行命令行的處理。以下代碼段演示了兩者的差異:
>>> 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', ')']
當(dāng)然,返回的詞法單元對 shell 無效,需要對返回的詞法單元自行進(jìn)行錯(cuò)誤檢查。
punctuation_chars 參數(shù)可以不傳入 True
,而是傳入包含特定字符的字符串,用于確定由哪些字符構(gòu)成標(biāo)點(diǎn)符號。例如:
>>> import shlex
>>> s = shlex.shlex("a && b || c", punctuation_chars="|")
>>> list(s)
['a', '&', '&', 'b', '||', 'c']
備注
如果指定了 punctuation_chars
,則 wordchars
屬性的參數(shù)會是 ~-./*?=
。因?yàn)檫@些字符可以出現(xiàn)在文件名(包括通配符)和命令行參數(shù)中(如 --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
時(shí)始終使用 posix
和 whitespace_split
,這將完全否定 wordchars
。
為了達(dá)到最佳效果,punctuation_chars
應(yīng)與 posix=True
一起設(shè)置。(注意 posix=False
是 shlex
的默認(rèn)設(shè)置)。