shlex —— 簡單的詞法分析?

源代碼: Lib/shlex.py


shlex 類可用于編寫類似 Unix shell 的簡單詞法分析程序。通常可用于編寫“迷你語言”(如 Python 應(yīng)用程序的運行控制文件)或解析帶引號的字符串。

shlex 模塊中定義了以下函數(shù):

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

用類似 shell 的語法拆分字符串 s。如果 commentsFalse (默認值),則不會解析給定字符串中的注釋 (commenters 屬性的 shlex 實例設(shè)為空字符串)。 本函數(shù)默認工作于 POSIX 模式下,但若 posix 參數(shù)為 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)起來,返回一個字符串。本函數(shù)是 split() 的逆運算。

>>>
>>> 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ù)必須是具備 read()readline() 方法的文件/流對象,或者是一個字符串。 如果沒有給出初始化參數(shù),則會從 sys.stdin 獲取輸入。 第二個可選參數(shù)是個文件名字符串,用于設(shè)置 infile 屬性的初始值。 如果 instream 參數(shù)被省略或等于 sys.stdin,則第二個參數(shù)默認為 "stdin"。 posix 參數(shù)定義了操作的模式:若 posix 不為真值(默認),則 shlex 實例將工作于兼容模式。 若運行于 POSIX 模式下,則 shlex 會盡可能地應(yīng)用 POSIX shell 解析規(guī)則。 punctuation_chars 參數(shù)提供了一種使行為更接近于真正的 shell 解析的方式。 該參數(shù)可接受多種值:默認值、False、保持 Python 3.5 及更早版本的行為。 如果設(shè)為 True,則會改變對字符 ();<>|& 的解析方式:這些字符將作為獨立的詞法單元被返回(視作標點符號)。 如果設(shè)為非空字符串,則這些字符將被用作標點符號。 出現(xiàn)在 punctuation_chars 中的 wordchars 屬性中的任何字符都會從 wordchars 中被刪除。 請參閱 改進的 shell 兼容性 了解詳情。 punctuation_chars 只能在創(chuàng)建 shlex 實例時設(shè)置,以后不能再作修改。

在 3.6 版更改: 加入 punctuation_chars 參數(shù)。

參見

configparser 模塊

配置文件解析器,類似于 Windows 的 .ini 文件。

shlex 對象?

shlex 實例具備以下方法:

shlex.get_token()?

返回一個詞法單元。如果所有單詞已用 push_token() 堆疊在一起了,則從堆棧中彈出一個詞法單元。否則就從輸入流中讀取一個。如果讀取時遇到文件結(jié)束符,則會返回 eof`(在非 POSIX 模式下為空字符串 `'',在 POSIX 模式下為 ``None)。

shlex.push_token(str)?

將參數(shù)值壓入詞法單元堆棧。

shlex.read_token()?

讀取一個原始詞法單元。忽略堆棧,且不解釋源請求。(通常沒什么用,只是為了完整起見。)

shlex.sourcehook(filename)?

當(dāng) shlex 檢測到源請求(見下面的 source),以下詞法單元可作為參數(shù),并應(yīng)返回一個由文件名和打開的文件對象組成的元組。

通常本方法會先移除參數(shù)中的引號。如果結(jié)果為絕對路徑名,或者之前沒有有效的源請求,或者之前的源請求是一個流對象(比如 sys.stdin),那么結(jié)果將不做處理。否則,如果結(jié)果是相對路徑名,那么前面將會加上目錄部分,目錄名來自于源堆棧中前一個文件名(類似于 C 預(yù)處理器對 #include "file.h" 的處理方式)。

結(jié)果被視為一個文件名,并作為元組的第一部分返回,元組的第二部分以此為基礎(chǔ)調(diào)用 open() 獲得。(注意:這與實例初始化過程中的參數(shù)順序相反!)

此鉤子函數(shù)是公開的,可用于實現(xiàn)路徑搜索、添加文件擴展名或黑入其他命名空間。沒有對應(yīng)的“關(guān)閉”鉤子函數(shù),但 shlex 實例在返回 EOF 時會調(diào)用源輸入流的 close() 方法。

若要更明確地控制源堆棧,請采用 push_source()pop_source() 方法。

shlex.push_source(newstream, newfile=None)?

將輸入源流壓入輸入堆棧。如果指定了文件名參數(shù),以后錯誤信息中將會用到。sourcehook() 內(nèi)部同樣使用了本方法。

shlex.pop_source()?

從輸入堆棧中彈出最后一條輸入源。當(dāng)遇到輸入流的 EOF 時,內(nèi)部也使用同一方法。

shlex.error_leader(infile=None, lineno=None)?

本方法生成一條錯誤信息的首部,以 Unix C 編譯器錯誤標簽的形式;格式為``'"%s", line %d: ',其中 ``%s 被替換為當(dāng)前源文件的名稱,%d 被替換為當(dāng)前輸入行號(可用可選參數(shù)覆蓋)。

這是個快捷函數(shù),旨在鼓勵 shlex 用戶以標準的、可解析的格式生成錯誤信息,以便 Emacs 和其他 Unix 工具理解。

shlex 子類的實例有一些公共實例變量,這些變量可以控制詞義分析,也可用于調(diào)試。

shlex.commenters?

將被視為注釋起始字符串。從注釋起始字符串到行尾的所有字符都將被忽略。默認情況下只包括 '#'

shlex.wordchars?

可連成多字符詞法單元的字符串。默認包含所有 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?

將被視為空白符并跳過的字符??瞻追窃~法單元的邊界。默認包含空格、制表符、換行符和回車符。

shlex.escape?

將視為轉(zhuǎn)義字符。僅適用于 POSIX 模式,默認只包含 '\'

shlex.quotes?

將視為引號的字符。詞法單元中的字符將會累至再次遇到同樣的引號(因此,不同的引號會像在 shell 中一樣相互包含。)默認包含 ASCII 單引號和雙引號。

shlex.escapedquotes?

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

shlex.whitespace_split?

若為 True,則只根據(jù)空白符拆分詞法單元。這很有用,比如用 shlex 解析命令行,用類似 shell 參數(shù)的方式讀取各個詞法單元。當(dāng)與 punctuation_chars 一起使用時,將根據(jù)空白符和這些字符拆分詞法單元。

在 3.8 版更改: punctuation_chars 屬性已與 whitespace_split 屬性兼容。

shlex.infile?

當(dāng)前輸入的文件名,可能是在類實例化時設(shè)置的,或者是由后來的源請求堆棧生成的。在構(gòu)建錯誤信息時可能會用到本屬性。

shlex.instream?

shlex 實例正從中讀取字符的輸入流。

shlex.source?

本屬性默認值為 None。 如果給定一個字符串,則會識別為包含請求,類似于各種 shell 中的 source 關(guān)鍵字。 也就是說,緊隨其后的詞法單元將作為文件名打開,作為輸入流,直至遇到 EOF 后調(diào)用流的 close() 方法,然后原輸入流仍變回輸入源。Source 請求可以在詞義堆棧中嵌套任意深度。

shlex.debug?

如果本屬性為大于 1 的數(shù)字,則 shlex 實例會把動作進度詳細地輸出出來。若需用到本屬性,可閱讀源代碼來了解細節(jié)。

shlex.lineno?

源的行數(shù)(到目前為止讀到的換行符數(shù)量加 1)。

shlex.token?

詞法單元的緩沖區(qū)。在捕獲異常時可能會用到。

shlex.eof?

用于確定文件結(jié)束的詞法單元。在非 POSIX 模式下,將設(shè)為空字符串 '',在 POSIX 模式下被設(shè)為 None。

shlex.punctuation_chars?

只讀屬性。表示應(yīng)視作標點符號的字符。標點符號將作為單個詞法單元返回。然而,請注意不會進行語義有效性檢查:比如 “>>>” 可能會作為一個詞法單元返回,雖然 shell 可能無法識別。

3.6 新版功能.

解析規(guī)則?

在非 POSIX 模式下時,shlex 會試圖遵守以下規(guī)則:

  • 不識別單詞中的引號(Do"Not"Separate 解析為一個單詞 Do"Not"Separate);

  • 不識別轉(zhuǎn)義字符;

  • 引號包裹的字符保留字面意思;

  • 成對的引號會將單詞分離("Do"Separate 解析為 "Do"Separate);

  • 如果 whitespace_splitFalse,則未聲明為單詞字符、空白或引號的字符將作為單字符的詞法單元返回。若為 True, 則 shlex 只根據(jù)空白符拆分單詞。

  • EOF 用空字符串('')表示;

  • 空字符串無法解析,即便是加了引號。

在 POSIX 模式時,shlex 將嘗試遵守以下解析規(guī)則:

  • 引號會被剔除,且不會拆分單詞( "Do"Not"Separate" 將解析為單個單詞 DoNotSeparate);

  • 未加引號包裹的轉(zhuǎn)義字符(如 '\' )保留后一個字符的字面意思;

  • 引號中的字符不屬于 escapedquotes (例如,"'"),則保留引號中所有字符的字面值;

  • 若引號包裹的字符屬于 escapedquotes (例如 '"'),則保留引號中所有字符的字面意思,屬于 escape 中的字符除外。僅當(dāng)后跟后半個引號或轉(zhuǎn)義字符本身時,轉(zhuǎn)義字符才保留其特殊含義。否則,轉(zhuǎn)義字符將視作普通字符;

  • EOF 用 None 表示;

  • 允許出現(xiàn)引號包裹的空字符串('')。

改進的 shell 兼容性?

3.6 新版功能.

shlex 類提供了與常見 Unix shell(如 bash、dash 和``sh``)的解析兼容性。為了充分利用這種兼容性,請在構(gòu)造函數(shù)中設(shè)定 punctuation_chars 參數(shù)。該參數(shù)默認為 False,維持 3.6 以下版本的行為。如果設(shè)為 True,則會改變對 ();<>|& 字符的解析方式:這些字符都將視為單個的詞法單元返回。雖然不算是完整的 shell 解析程序(考慮到 shell 的多樣性,超出了標準庫的范圍),但確實能比其他方式更容易進行命令行的處理。以下代碼段演示了兩者的差異:

 >>> 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 無效,需要對返回的詞法單元自行進行錯誤檢查。

punctuation_chars 參數(shù)可以不傳入 True ,而是傳入包含特定字符的字符串,用于確定由哪些字符構(gòu)成標點符號。例如:

>>>
>>> import shlex
>>> s = shlex.shlex("a && b || c", punctuation_chars="|")
>>> list(s)
['a', '&', '&', 'b', '||', 'c']

備注

如果指定了 punctuation_chars,則 wordchars 屬性的參數(shù)會是 ~-./*?=。因為這些字符可以出現(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 時始終使用 posixwhitespace_split ,這將完全否定 wordchars

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