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。如果 commentsFalse (默認值),則不會(huì )解析給定字符串中的注釋 (commenters 屬性的 shlex 實(shí)例設為空字符串)。 本函數默認工作于 POSIX 模式下,但若 posix 參數為 False,則采用非 POSIX 模式。

備注

Since the split() function instantiates a shlex instance, passing None 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.escapedquotes?

quotes 中的字符將會(huì )解析 escape 定義的轉義字符。這只在 POSIX 模式下使用,默認只包含 '"'。

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.instream?

shlex 實(shí)例正從中讀取字符的輸入流。

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_splitFalse,則未聲明為單詞字符、空白或引號的字符將作為單字符的詞法單元返回。若為 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í)始終使用 posixwhitespace_split ,這將完全否定 wordchars 。

為了達到最佳效果,punctuation_chars 應與 posix=True 一起設置。(注意 posix=Falseshlex 的默認設置)。