天天看點

使用 Python 建立你自己的 Shell(下)

<a target="_blank"></a>

“<code>cd test_dir2</code> 無法修改我們的目前目錄” 這句話是對的,但在某種意義上也是錯的。在執行完該指令之後,我們仍然處在同一目錄,從這個意義上講,它是對的。然而,目錄實際上已經被修改,隻不過它是在子程序中被修改。

還記得我們分叉(fork)了一個子程序,然後執行指令,執行指令的過程沒有發生在父程序上。結果是我們隻是改變了子程序的目前目錄,而不是父程序的目錄。

然後子程序退出,而父程序在原封不動的目錄下繼續運作。

是以,這類與 shell 自己相關的指令必須是内置指令。它必須在 shell 程序中執行而不是在分叉中(forking)。

讓我們從 <code>cd</code> 指令開始。

我們首先建立一個 <code>builtins</code> 目錄。每一個内置指令都會被放進這個目錄中。

<code>yosh_project</code>

<code>|-- yosh</code>

<code>|-- builtins</code>

<code>| |-- __init__.py</code>

<code>| |-- cd.py</code>

<code>|-- __init__.py</code>

<code>|-- shell.py</code>

在 <code>cd.py</code> 中,我們通過使用系統調用 <code>os.chdir</code> 實作自己的 <code>cd</code> 指令。

<code>import os</code>

<code>from yosh.constants import *</code>

<code></code>

<code>def cd(args):</code>

<code>os.chdir(args[0])</code>

<code>return shell_status_run</code>

注意,我們會從内置函數傳回 shell 的運作狀态。是以,為了能夠在項目中繼續使用常量,我們将它們移至<code>yosh/constants.py</code>。

<code>|-- constants.py</code>

在 <code>constants.py</code> 中,我們将狀态常量都放在這裡。

<code>shell_status_stop = 0</code>

<code>shell_status_run = 1</code>

現在,我們的内置 <code>cd</code> 已經準備好了。讓我們修改 <code>shell.py</code> 來處理這些内置函數。

<code>...</code>

<code>### 導入常量</code>

<code>### 使用哈希映射來存儲内建的函數名及其引用</code>

<code>built_in_cmds = {}</code>

<code>def tokenize(string):</code>

<code>return shlex.split(string)</code>

<code>def execute(cmd_tokens):</code>

<code>### 從元組中分拆指令名稱與參數</code>

<code>cmd_name = cmd_tokens[0]</code>

<code>cmd_args = cmd_tokens[1:]</code>

<code>### 如果該指令是一個内建指令,使用參數調用該函數</code>

<code>if cmd_name in built_in_cmds:</code>

<code>return built_in_cmds[cmd_name](cmd_args)</code>

我們使用一個 python 字典變量 <code>built_in_cmds</code> 作為哈希映射hash map,以存儲我們的内置函數。我們在<code>execute</code> 函數中提取指令的名字和參數。如果該指令在我們的哈希映射中,則調用對應的内置函數。

(提示:<code>built_in_cmds[cmd_name]</code> 傳回能直接使用參數調用的函數引用。)

我們差不多準備好使用内置的 <code>cd</code> 函數了。最後一步是将 <code>cd</code> 函數添加到 <code>built_in_cmds</code> 映射中。

<code>### 導入所有内建函數引用</code>

<code>from yosh.builtins import *</code>

<code>### 注冊内建函數到内建指令的哈希映射中</code>

<code>def register_command(name, func):</code>

<code>built_in_cmds[name] = func</code>

<code>### 在此注冊所有的内建指令</code>

<code>def init():</code>

<code>register_command("cd", cd)</code>

<code>def main():</code>

<code>###在開始主循環之前初始化 shell</code>

<code>init()</code>

<code>shell_loop()</code>

我們定義了 <code>register_command</code> 函數,以添加一個内置函數到我們内置的指令哈希映射。接着,我們定義<code>init</code> 函數并且在這裡注冊内置的 <code>cd</code> 函數。

注意這行 <code>register_command("cd", cd)</code> 。第一個參數為指令的名字。第二個參數為一個函數引用。為了能夠讓第二個參數 <code>cd</code> 引用到 <code>yosh/builtins/cd.py</code> 中的 <code>cd</code> 函數引用,我們必須将以下這行代碼放在<code>yosh/builtins/__init__.py</code> 檔案中。

<code>from yosh.builtins.cd import *</code>

是以,在 <code>yosh/shell.py</code> 中,當我們從 <code>yosh.builtins</code> 導入 <code>*</code> 時,我們可以得到已經通過<code>yosh.builtins</code> 導入的 <code>cd</code> 函數引用。

我們已經準備好了代碼。讓我們嘗試在 <code>yosh</code> 同級目錄下以子產品形式運作我們的 shell,<code>python -m yosh.shell</code>。

現在,<code>cd</code> 指令可以正确修改我們的 shell 目錄了,同時非内置指令仍然可以工作。非常好!

最後一塊終于來了:優雅地退出。

我們需要一個可以修改 shell 狀态為 <code>shell_status_stop</code> 的函數。這樣,shell 循環可以自然地結束,shell 将到達終點而退出。

和 <code>cd</code> 一樣,如果我們在子程序中分叉并執行 <code>exit</code> 函數,其對父程序是不起作用的。是以,<code>exit</code> 函數需要成為一個 shell 内置函數。

讓我們從這開始:在 <code>builtins</code> 目錄下建立一個名為 <code>exit.py</code> 的新檔案。

<code>| |-- exit.py</code>

<code>exit.py</code> 定義了一個 <code>exit</code> 函數,該函數僅僅傳回一個可以退出主循環的狀态。

<code>def exit(args):</code>

<code>return shell_status_stop</code>

然後,我們導入位于 <code>yosh/builtins/__init__.py</code> 檔案的 <code>exit</code> 函數引用。

<code>from yosh.builtins.exit import *</code>

最後,我們在 <code>shell.py</code> 中的 <code>init()</code> 函數注冊 <code>exit</code> 指令。

<code>register_command("exit", exit)</code>

到此為止!

嘗試執行 <code>python -m yosh.shell</code>。現在你可以輸入 <code>exit</code> 優雅地退出程式了。

原文釋出時間為:2016-07-29

本文來自雲栖社群合作夥伴“linux中國”