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