cmd --- 以列為導向的命令直譯器支援

原始碼:Lib/cmd.py


Cmd 類別提供了一個簡單的架構,用於撰寫列導向的命令直譯器。這類直譯器常用於測試控制工具、管理工具以及日後將包裝於更高階介面的原型中。

class cmd.Cmd(completekey='tab', stdin=None, stdout=None)

Cmd 實例或其子類別實例是一種列導向的直譯器架構。通常沒有必要直接實例化 Cmd 本身;它更適合作為你自己定義的直譯器類別的父類別,讓你能繼承 Cmd 的方法,並封裝動作方法。

可選引數 completekeyreadline 模組中用於自動完成的按鍵名稱;預設為 Tab。若 completekey 不為 Nonereadline 可用,則會自動啟用命令自動完成功能。

預設值 'tab' 會被特殊處理,使其在所有的 readline.backend 中皆代表 Tab 鍵。具體來說,若 readline.backendeditline,則 Cmd 會改用 '^I' 取代 'tab'。請注意,其他值不會有此處理方式,且可能僅能在特定的後端中適用。

可選引數 stdinstdout 用來指定 Cmd 實例或其子類別實例所使用的輸入與輸出檔案物件。若未指定,預設為 sys.stdinsys.stdout

若你希望使用指定的 stdin,請務必將該實例的 use_rawinput 屬性設為 False,否則 stdin 會被忽略。

在 3.13 版的變更: 對於 editlinecompletekey='tab' 會被替換為 '^I'

Cmd 物件

Cmd 實例具有以下方法:

Cmd.cmdloop(intro=None)

重複顯示提示字元、接收輸入、剖析接收到的輸入字串前綴,並派發給動作方法,將其餘部分作為引數傳遞給它們

此可選引數為橫幅或導言字串,會在首次顯示提示字元前輸出(此值會覆寫 intro 類別屬性)。

如果已載入 readline 模組,輸入將自動繼承類似 bash 的歷史紀錄編輯功能(例如 Control-P 可向上捲動至上一個命令,Control-N 向下捲動至下一個命令,Control-F 非破壞性地將游標向右移動,Control-B 非破壞性地將游標向左移動等)。

當輸入為檔案結尾(EOF)時,會傳回字串 'EOF'

直譯器實例僅當存在 do_foo() 方法時,才會識別命令名稱 foo。作為特殊情況,以字元 '?' 開頭的列會被派發至 do_help() 方法;另一個特殊情況是,以字元 '!' 開頭的列會被派發至 do_shell() 方法(若該方法已定義)。

postcmd() 方法回傳真值時,此方法將會結束。傳遞給 postcmd()stop 引數是該命令對應的 do_*() 方法的回傳值。

如果啟用了自動完成,命令的自動完成將會自動執行,而命令引數的自動完成則是透過呼叫 complete_foo() 方法並傳入 textlinebegidxendidx 引數來處理。text 是要比對的字串前綴:所有回傳的符合項都必須以此字串開頭。line 是目前的輸入列(前置空白會被移除),begidxendidx 則分別是前綴字串的起始與結束索引,可用來根據引數所在的位置提供不同的自動完成結果。

Cmd.do_help(arg)

所有 Cmd 的子類別都會繼承預先定義的 do_help() 方法。當此方法接收到引數 'bar' 時,會呼叫對應的 help_bar() 方法;若該方法不存在,則會列印 do_bar() 的說明字串(若有的話)。若未提供任何引數,do_help() 會列出所有可用的說明主題(也就是所有具有對應 help_*() 方法或有說明字串的命令),並且也會列出所有尚未記錄的命令。

Cmd.onecmd(str)

將引數視為在回應提示字元時所輸入的內容來直譯。這個方法可以被覆寫,但通常不需要這麼做;參見 precmd()postcmd() 方法,它們提供實用的執行勾點(hook)。此方法的回傳值是一個旗標,用來指出是否應該停止直譯器對命令的直譯。若有對應 str 命令的 do_*() 方法,則會回傳該方法的回傳值;否則,回傳值將來自 default() 方法。

Cmd.emptyline()

在回應提示字元時輸入空白列,會呼叫此方法。若此方法未被覆寫,則會重復上一次輸入的非空命令。

Cmd.default(line)

當輸入列中的命令前綴無法辨識時,會呼叫此方法。若此方法未被覆寫,則會輸出並回傳錯誤訊息。

Cmd.completedefault(text, line, begidx, endidx)

當沒有對應特定命令的 complete_*() 方法時,會呼叫此方法以完成輸入列。預設會回傳空串列。

Cmd.columnize(list, displaywidth=80)

此方法用來將字串串列顯示為緊湊的欄集合。每一欄的寬度僅足以容納其內容,各欄之間以兩個空格分隔,以提高可讀性。

Cmd.precmd(line)

勾點方法會在直譯命令列 line 前執行,但會在提示字元產生並顯示後才觸發。這個方法在 Cmd 類別中為 stub,預期由子類別覆寫。回傳值會作為 onecmd() 方法所執行的命令;precmd() 的實作可以重寫該命令,或直接回傳未變更的 line

Cmd.postcmd(stop, line)

勾點方法會在命令派發完成後執行。這個方法在 Cmd 類別中為 stub,預期由子類別覆寫。line 是剛剛執行的命令列,而 stop 是一個旗標,用來指出在呼叫 postcmd() 後是否應終止執行;該值即為 onecmd() 方法的回傳值。本方法的回傳值將會更新內部的 stop 旗標;若回傳 false,則會繼續進行直譯。

Cmd.preloop()

cmdloop() 被呼叫時,此勾點方法會執行一次。這個方法在 Cmd 類別中為 stub,預期由子類別覆寫。

Cmd.postloop()

cmdloop() 即將回傳時,此勾點方法會執行一次。這個方法在 Cmd 類別中為 stub,預期由子類別覆寫。

Cmd 子類別的實例包含一些公開的實例變數:

Cmd.prompt

用來請求輸入的提示字元。

Cmd.identchars

可作為命令前綴的字元字串

Cmd.lastcmd

最後一個遇到的非空命令前綴

Cmd.cmdqueue

排入佇列的輸入列串列。當 cmdloop() 需要新輸入時,會檢查 cmdqueue 串列;若不為空,其元素將依序處理,就如同它們是在提示字元中輸入的一樣。

Cmd.intro

作為簡介或橫幅的字串。可透過為 cmdloop() 方法提供引數來覆寫此內容。

Cmd.doc_header

若說明輸出包含已記錄命令的區段,則會顯示的標頭字串。

Cmd.misc_header

若說明輸出包含雜項說明主題的區段(也就是存在 help_*() 方法但沒有對應的 do_*() 方法),則會顯示的標頭字串。

Cmd.undoc_header

若說明輸出包含未記錄命令的區段(也就是存在 do_*() 方法但沒有對應的 help_*() 方法),則會顯示的標頭字串。

Cmd.ruler

用於在說明訊息的標頭下方繪製分隔線的字元。若為空,則不會繪製分隔線。預設為 '='

Cmd.use_rawinput

一個旗標,預設為 true。若為 true,cmdloop() 會使用 input() 來顯示提示字元並讀取下一個命令;若為 false,則會改用 sys.stdout.write()sys.stdin.readline()。(這表示在支援的系統中,透過 import readline module,直譯器將自動支援類似 Emacs 的列編輯與命令歷史快捷鍵。)

Cmd 範例

cmd module 主要用於建構自訂 shell,讓使用者能以互動方式操作程式。

本節將示範如何以 turtle module 中的幾個命令為基礎,建立一個簡單的 shell。

像是 forward() 這樣的基本 turtle 命令,可透過新增名為 do_forward() 的方法加入至 Cmd 子類別中。傳入的引數會轉換為數值,並傳送給 turtle 模組。該方法的說明字串會用於 shell 所提供的說明功能中。

此範例同時包含一個簡單的錄製與重播功能,其實作方式是透過 precmd() 方法,負責將輸入轉為小寫並寫入檔案。do_playback() 方法則會讀取該檔案,並將錄製的命令加入 cmdqueue 中以供立即重播:

import cmd, sys
from turtle import *

class TurtleShell(cmd.Cmd):
    intro = '歡迎來到 turtle shell。輸入 help 或 ? 來列出命令。\n'
    prompt = '(turtle) '
    file = None

    # ----- 基本烏龜命令 -----
    def do_forward(self, arg):
        '將烏龜向前移動指定的距離: FORWARD 10'
        forward(*parse(arg))
    def do_right(self, arg):
        '將烏龜右轉指定的角度: RIGHT 20'
        right(*parse(arg))
    def do_left(self, arg):
        '將烏龜左轉指定的角度: LEFT 90'
        left(*parse(arg))
    def do_goto(self, arg):
        '將烏龜移動到指定的絕對位置並改變方向。 GOTO 100 200'
        goto(*parse(arg))
    def do_home(self, arg):
        '將烏龜返回起始位置: HOME'
        home()
    def do_circle(self, arg):
        '畫出指定半徑、範圍和步數的圓: CIRCLE 50'
        circle(*parse(arg))
    def do_position(self, arg):
        '顯示目前烏龜位置: POSITION'
        print('目前位置是 %d %d\n' % position())
    def do_heading(self, arg):
        '顯示目前烏龜方向角度: HEADING'
        print('目前方向是 %d\n' % (heading(),))
    def do_color(self, arg):
        '設定顏色: COLOR BLUE'
        color(arg.lower())
    def do_undo(self, arg):
        '撤銷(重複)最後一次烏龜動作: UNDO'
    def do_reset(self, arg):
        '清除畫面並將烏龜返回到中心: RESET'
        reset()
    def do_bye(self, arg):
        '停止錄製、關閉烏龜視窗並退出: BYE'
        print('感謝使用 Turtle')
        self.close()
        bye()
        return True

    # ----- 錄製與重播 -----
    def do_record(self, arg):
        '將未來命令儲存至檔案: RECORD rose.cmd'
        self.file = open(arg, 'w')
    def do_playback(self, arg):
        '從檔案重播命令: PLAYBACK rose.cmd'
        self.close()
        with open(arg) as f:
            self.cmdqueue.extend(f.read().splitlines())
    def precmd(self, line):
        line = line.lower()
        if self.file and 'playback' not in line:
            print