shlex — 簡單詞法分析

原始碼: Lib/shlex.py


shlex 類使得為類似於 Unix shell 的簡單語法編寫詞法分析器變得容易。這通常對於編寫微型語言(例如,在 Python 應用程式的執行控制檔案中)或解析帶引號的字串很有用。

shlex 模組定義了以下函式

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

使用類似於 shell 的語法拆分字串 s。如果 commentsFalse (預設值),則將停用給定字串中註釋的解析 (將 commenters 屬性設定為 shlex 例項的空字串)。此函式預設在 POSIX 模式下執行,但如果 posix 引數為 false,則使用非 POSIX 模式。

在 3.12 版本中更改: s 引數傳遞 None 現在會引發異常,而不是讀取 sys.stdin

shlex.join(split_command)

連線列表 split_command 的標記並返回一個字串。此函式是 split() 的逆運算。

>>> from shlex import join
>>> print(join(['echo', '-n', 'Multiple words']))
echo -n 'Multiple words'

返回的值經過 shell 轉義,以防止注入漏洞(請參閱 quote())。

3.8 版本中新增。

shlex.quote(s)

返回字串 s 的 shell 轉義版本。返回的值是一個字串,在不能使用列表的情況下,可以安全地用作 shell 命令列中的一個標記。

警告

shlex 模組僅為 Unix shell 設計

不能保證 quote() 函式在非 POSIX 相容的 shell 或來自其他作業系統(如 Windows)的 shell 上是正確的。在此類 shell 上執行此模組引用的命令可能會開啟命令注入漏洞的可能性。

考慮使用帶有列表的傳遞命令引數的函式,例如 subprocess.run() 帶有 shell=False

此用法是不安全的

>>> 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 例項或子類例項是一個詞法分析器物件。初始化引數(如果存在)指定從哪裡讀取字元。它必須是一個具有 read()readline() 方法的檔案/類流物件,或一個字串。如果沒有給出引數,則將從 sys.stdin 獲取輸入。第二個可選引數是檔名字串,它設定 infile 屬性的初始值。如果省略 instream 引數或等於 sys.stdin,則此第二個引數預設為“stdin”。posix 引數定義了操作模式:當 posix 不為 true(預設)時,shlex 例項將在相容模式下執行。當在 POSIX 模式下執行時,shlex 將嘗試儘可能接近 POSIX shell 解析規則。punctuation_chars 引數提供了一種使行為更接近於真實 shell 解析的方式。這可以採用多個值:預設值 False 保留了 Python 3.5 及更早版本下的行為。如果設定為 True,則會更改字元 ();<>|& 的解析:這些字元(被認為是標點字元)的任何執行都將作為單個標記返回。如果設定為非空字串,則這些字元將用作標點字元。出現在 punctuation_chars 中的 wordchars 屬性中的任何字元都將從 wordchars 中刪除。有關更多資訊,請參閱 改進了與 Shell 的相容性punctuation_chars 只能在 shlex 例項建立時設定,以後不能修改。

在 3.6 版本中更改: 添加了 punctuation_chars 引數。

另請參閱

模組 configparser

用於解析類似於 Windows .ini 檔案的配置檔案的解析器。

shlex 物件

shlex 例項具有以下方法

shlex.get_token()

返回一個 token。如果已經使用 push_token() 堆疊了 token,則從堆疊中彈出一個 token。否則,從輸入流中讀取一個。如果讀取時遇到立即檔案結束,則返回 eof (在非 POSIX 模式下為空字串 (''),在 POSIX 模式下為 None)。

shlex.push_token(str)

將引數壓入 token 堆疊。

shlex.read_token()

讀取一個原始 token。忽略回推堆疊,並且不解釋源請求。(這通常不是一個有用的入口點,這裡記錄它只是為了完整性。)

shlex.sourcehook(filename)

shlex 檢測到源請求時(參見下面的 source),此方法會接收到以下 token 作為引數,並期望返回一個由檔名和一個開啟的檔案類物件組成的元組。

通常,此方法首先會去除引數上的任何引號。如果結果是絕對路徑名,或者之前沒有生效的源請求,或者之前的源是流(例如 sys.stdin),則結果保持不變。否則,如果結果是相對路徑名,則會在源包含堆疊中它之前的檔案的名稱的目錄部分前面加上(此行為類似於 C 預處理器處理 #include "file.h" 的方式)。

操作的結果被視為檔名,並作為元組的第一個元件返回,並呼叫 open() 以生成第二個元件。(請注意:這與例項初始化中的引數順序相反!)

公開此鉤子是為了方便您使用它來實現目錄搜尋路徑、新增副檔名和其他名稱空間 hacks。沒有相應的“關閉”鉤子,但是當 shlex 例項返回 EOF 時,它會呼叫源輸入流的 close() 方法。

為了更明確地控制源堆疊,請使用 push_source()pop_source() 方法。

shlex.push_source(newstream, newfile=None)

將輸入源流壓入輸入堆疊。如果指定了檔名引數,則稍後它可用於錯誤訊息中。這是 sourcehook() 方法內部使用的相同方法。

shlex.pop_source()

從輸入堆疊中彈出最後壓入的輸入源。這是當詞法分析器在堆疊的輸入流上到達 EOF 時內部使用的相同方法。

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

此方法生成 Unix C 編譯器錯誤標籤格式的錯誤訊息頭;格式為 '"%s", line %d: ',其中 %s 替換為當前原始檔的名稱,%d 替換為當前輸入行號(可選引數可用於覆蓋這些)。

提供此便利是為了鼓勵 shlex 使用者以 Emacs 和其他 Unix 工具理解的標準可解析格式生成錯誤訊息。

shlex 子類的例項有一些公共例項變數,這些變數要麼控制詞法分析,要麼可以用於除錯。

shlex.commenters

被識別為註釋起始符的字元字串。從註釋起始符到行尾的所有字元都將被忽略。預設情況下僅包含 '#'

shlex.wordchars

將累積到多字元 token 中的字元字串。預設情況下,包括所有 ASCII 字母數字字元和下劃線。在 POSIX 模式下,還包括 Latin-1 集中帶有重音符號的字元。如果 punctuation_chars 不為空,則可能出現在檔名規範和命令列引數中的字元 ~-./*?= 也將包含在此屬性中,並且 punctuation_chars 中出現的任何字元都將從 wordchars 中刪除(如果它們存在於那裡)。如果 whitespace_split 設定為 True,這將不起作用。

shlex.whitespace

將被視為空格並跳過的字元。空格界定 token。預設情況下,包括空格、製表符、換行符和回車符。

shlex.escape

將被視為轉義符的字元。這僅在 POSIX 模式下使用,預設情況下僅包含 '\'

shlex.quotes

將被視為字串引號的字元。token 會一直累積,直到再次遇到相同的引號(因此,不同的引號型別會像在 shell 中一樣相互保護。)預設情況下,包括 ASCII 單引號和雙引號。

shlex.escapedquotes

quotes 中將解釋 escape 中定義的跳脫字元的字元。這僅在 POSIX 模式下使用,預設情況下僅包含 '"'

shlex.whitespace_split

如果為 True,則 token 將僅在空格處分割。例如,這對於使用 shlex 解析命令列,以類似於 shell 引數的方式獲取 token 非常有用。當與 punctuation_chars 結合使用時,除了這些字元之外,token 還將在空格處分割。

在 3.8 版本中變更: punctuation_chars 屬性與 whitespace_split 屬性相容。

shlex.infile

當前輸入檔案的名稱,最初在類例項化時設定或由以後的源請求堆疊。在構造錯誤訊息時,檢查它可能很有用。

shlex.instream

shlex 例項從中讀取字元的輸入流。

shlex.source

此屬性預設值為 None。如果將其賦值為一個字串,則該字串將被識別為類似於各種 shell 中的 source 關鍵字的詞法級包含請求。也就是說,緊隨其後的標記將作為檔名開啟,輸入將從該流中獲取,直到 EOF,此時將呼叫該流的 close() 方法,輸入源將再次變為原始輸入流。源請求可以堆疊任意深度。

shlex.debug

如果此屬性是數字且大於等於 1,則 shlex 例項將在其行為上列印詳細的進度輸出。如果您需要使用此功能,可以閱讀模組原始碼以瞭解詳細資訊。

shlex.lineno

源行號(目前為止看到的換行符數量加一)。

shlex.token

標記緩衝區。在捕獲異常時檢查此內容可能很有用。

shlex.eof

用於確定檔案結尾的標記。在非 POSIX 模式下,這將設定為空字串 (''),而在 POSIX 模式下,則設定為 None

shlex.punctuation_chars

一個只讀屬性。將被視為標點符號的字元。連續的標點符號字元將作為單個標記返回。但是,請注意,不會執行語義有效性檢查:例如,'>>>' 可以作為標記返回,即使 shell 可能不將其識別為標記。

3.6 版本新增。

解析規則

在非 POSIX 模式下操作時,shlex 將嘗試遵守以下規則。

  • 引號字元在單詞中不被識別(Do"Not"Separate 被解析為單個單詞 Do"Not"Separate);

  • 跳脫字元不被識別;

  • 將字元括在引號中會保留引號內所有字元的字面值;

  • 閉合引號分隔單詞("Do"Separate 被解析為 "Do"Separate);

  • 如果 whitespace_splitFalse,則任何未宣告為單詞字元、空格或引號的字元都將作為單字元標記返回。如果為 True,則 shlex 將僅在空格中拆分單詞;

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

  • 即使使用引號,也無法解析空字串。

在 POSIX 模式下操作時,shlex 將嘗試遵守以下解析規則。

  • 引號被剝離,並且不分隔單詞("Do"Not"Separate" 被解析為單個單詞 DoNotSeparate);

  • 未加引號的跳脫字元(例如,'\')保留緊隨其後的下一個字元的字面值;

  • 將字元括在不屬於 escapedquotes 的引號中(例如,"'")會保留引號內所有字元的字面值;

  • 將字元括在屬於 escapedquotes 的引號中(例如,'"')會保留引號內所有字元的字面值,但 escape 中提到的字元除外。僅當跳脫字元後跟正在使用的引號或跳脫字元本身時,跳脫字元才保留其特殊含義。否則,跳脫字元將被視為普通字元。

  • EOF 用 None 值表示;

  • 允許使用帶引號的空字串('')。

改進了與 Shell 的相容性

3.6 版本新增。

shlex 類提供了與常見 Unix shell(如 bashdashsh)執行的解析的相容性。要利用此相容性,請在建構函式中指定 punctuation_chars 引數。此引數預設為 False,這會保留 3.6 之前的行為。但是,如果將其設定為 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', ')']

當然,將返回對 shell 無效的標記,您需要在返回的標記上實現自己的錯誤檢查。

除了傳遞 True 作為 punctuation_chars 引數的值之外,您還可以傳遞一個帶有特定字元的字串,該字串將用於確定哪些字元構成標點符號。例如

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

注意

指定 punctuation_chars 時,wordchars 屬性會新增字元 ~-./*?=。這是因為這些字元可以出現在檔名(包括萬用字元)和命令列引數中(例如,--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_charsposix=True 結合設定。(請注意,posix=Falseshlex 的預設值。)