shlex — 簡單的詞法分析

原始碼: Lib/shlex.py


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

shlex 模組定義了以下函式

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

使用類 shell 語法拆分字串 s。如果 commentsFalse (預設值),則將停用給定字串中註釋的解析(將 shlex 例項的 commenters 屬性設定為空字串)。此函式預設以 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 不為真(預設)時,shlex 例項將以相容模式執行。在 POSIX 模式下執行時,shlex 將盡量接近 POSIX shell 解析規則。punctuation_chars 引數提供了一種使行為更接近實際 shell 解析方式的方法。它可以採用多個值:預設值 False 保留了 Python 3.5 及更早版本中的行為。如果設定為 True,則字元 ();<>|& 的解析會改變:這些字元(被視為標點字元)的任何連續執行都將作為單個令牌返回。如果設定為非空字元字串,則這些字元將用作標點字元。wordchars 屬性中出現在 punctuation_chars 中的任何字元都將從 wordchars 中移除。有關更多資訊,請參閱 改進與 Shell 的相容性punctuation_chars 只能在 shlex 例項建立時設定,之後不能修改。

3.6 版本中的變化:添加了 punctuation_chars 引數。

參見

模組 configparser

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

shlex 物件

一個 shlex 例項具有以下方法

shlex.get_token()

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

shlex.push_token(str)

將引數壓入令牌堆疊。

shlex.read_token()

讀取一個原始令牌。忽略回壓堆疊,不解釋源請求。(這通常不是一個有用的入口點,在此處僅為完整性而文件化。)

shlex.sourcehook(filename)

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

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

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

公開此鉤子是為了讓您可以使用它來實現目錄搜尋路徑、新增副檔名和其他名稱空間技巧。沒有相應的“關閉”鉤子,但是當它返回 EOF 時,shlex 例項將呼叫源輸入流的 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

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

shlex.whitespace

將被視為空白並跳過的字元。空白分隔令牌。預設情況下,包括空格、製表符、換行符和回車符。

shlex.escape

將被視為跳脫字元的字元。這隻在 POSIX 模式下使用,預設只包含 '\'

shlex.quotes

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

shlex.escapedquotes

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

shlex.whitespace_split

如果為 True,則令牌將僅在空白處拆分。這對於例如使用 shlex 解析命令列,以類似於 shell 引數的方式獲取令牌很有用。當與 punctuation_chars 結合使用時,令牌將在空白處以及這些字元處拆分。

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_chars 應與 posix=True 結合設定。(請注意,posix=Falseshlex 的預設值。)