如何用Python創建自己的 Shell(上)
我很想知道一個 shell (像 bash,csh 等)內部是如何運作的。於是為了滿足自己的好奇心,我使用 Python 實作了一個名為yosh (Your Own Shell)的 Shell。本文章所介紹的概念也可以應用在其他程式語言上。
(提示:你可以在這裡找到本博文使用的源代碼,代碼以MIT 許可證發布。在Mac OS X 10.11.5 上,我使用Python 2.7.10 和3.4.3 進行了測試。
步驟 0:項目結構
對於此項目,我使用了以下的項目結構。
yosh_project |-- yosh |-- __init__.py |-- shell.py
為專案根目錄(你也可以簡單地命名它為 yosh
)。
為套件目錄,且 __init__.py
可以使它成為與套件的目錄名字相同的套件(如果你不用Python 寫的話,可以忽略它。 步驟 1:Shell 循環
當啟動一個 shell,它會顯示一個命令提示字元並等待你的命令輸入。在接收了輸入的命令並執行它之後(稍後文章會進行詳細解釋),你的 shell 會重新回到這裡,並循環等待下一條指令。 在
中,我們會以一個簡單的main 函數開始,該函數呼叫了shell_loop() 函數,如下:
def shell_loop(): # Start the loop here def main(): shell_loop() if __name__ == "__main__": main()
接著,在
shell_loop() 中,為了指示循環是否繼續或停止,我們使用了一個狀態標誌。在循環的開始,我們的 shell 將顯示一個命令提示符,並等待讀取命令輸入。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>import sys
SHELL_STATUS_RUN = 1
SHELL_STATUS_STOP = 0
def shell_loop():
status = SHELL_STATUS_RUN
while status == SHELL_STATUS_RUN:
### 显示命令提示符
sys.stdout.write(&#39;> &#39;)
sys.stdout.flush()
### 读取命令输入
cmd = sys.stdin.readline()</pre><div class="contentsignin">登入後複製</div></div>
之後,我們切分指令(tokenize)輸入並執行(execute)(我們即將實作
和 execute
函數)。
因此,我們的 shell_loop() 會是如下這樣:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>import sys
SHELL_STATUS_RUN = 1
SHELL_STATUS_STOP = 0
def shell_loop():
status = SHELL_STATUS_RUN
while status == SHELL_STATUS_RUN:
### 显示命令提示符
sys.stdout.write(&#39;> &#39;)
sys.stdout.flush()
### 读取命令输入
cmd = sys.stdin.readline()
### 切分命令输入
cmd_tokens = tokenize(cmd)
### 执行该命令并获取新的状态
status = execute(cmd_tokens)</pre><div class="contentsignin">登入後複製</div></div>
這就是我們整個 shell 迴圈。如果我們使用 python shell.py
啟動我們的 shell,它會顯示命令提示字元。然而如果我們輸入指令並按回車,它會拋出錯誤,因為我們還沒定義
函數。
為了退出 shell,可以嘗試輸入 ctrl-c。稍後我將解釋如何以優雅的形式退出 shell。 步驟2:命令切分tokenize
當使用者在我們的shell 中輸入命令並按下回車鍵,該命令將會是一個包含命令名稱及其參數的長字符串。因此,我們必須切分該字串(分割一個字串為多個元組)。
咋一看似乎很簡單。我們或許可以使用
cmd.split(),以空格分割輸入。它對類似
ls -a my_folder的指令起作用,因為它能夠將指令分割為一個清單
['ls', '-a', 'my_folder'],這樣我們便能輕易處理它們了。 然而,也有一些類似
echo "
Hello World"
或
以單引號或雙引號引用參數的情況。如果我們使用cmd.spilt,我們將會得到一個存有3 個標記的列表 ['echo', '"Hello', 'World"'] 而不是2 個標記的列表 ['echo', 'Hello World']
。 幸運的是,Python 提供了一個名為
shlex
的函式庫,它能夠幫助我們如魔法般地分割指令。 (提示:我們也可以使用正規表示式,但它不是本文的重點。)
然後我們將這些元組傳送到執行進程。 import sys
import shlex
...
def tokenize(string):
return shlex.split(string)
...
步驟 3:執行這是 shell 中核心而有趣的一部分。當 shell 執行
時,到底發生了什麼事? (提示:
mkdir是一個帶有
test_dir 參數的執行程序,用於建立一個名為 test_dir
的目錄。)#execvp
是這一步驟的首先需要的函數。在我們解釋 execvp
所做的事之前,讓我們先看看它的實際效果。
import os ... def execute(cmd_tokens): ### 执行命令 os.execvp(cmd_tokens[0], cmd_tokens) ### 返回状态以告知在 shell_loop 中等待下一个命令 return SHELL_STATUS_RUN ...
再次嘗試執行我們的 shell,並輸入 mkdir test_dir
指令,接著按下回車鍵。 在我們敲下回車鍵之後,問題是我們的 shell 會直接退出而不是等待下一個指令。然而,目錄正確地創建了。
因此,execvp
實際上做了什麼?
execvp
是系统调用 exec
的一个变体。第一个参数是程序名字。v
表示第二个参数是一个程序参数列表(参数数量可变)。p
表示将会使用环境变量 PATH
搜索给定的程序名字。在我们上一次的尝试中,它将会基于我们的 PATH
环境变量查找mkdir
程序。
(还有其他 exec
变体,比如 execv、execvpe、execl、execlp、execlpe;你可以 google 它们获取更多的信息。)
exec
会用即将运行的新进程替换调用进程的当前内存。在我们的例子中,我们的 shell 进程内存会被替换为 mkdir
程序。接着,mkdir
成为主进程并创建 test_dir
目录。最后该进程退出。
这里的重点在于我们的 shell 进程已经被 mkdir
进程所替换。这就是我们的 shell 消失且不会等待下一条命令的原因。
因此,我们需要其他的系统调用来解决问题:fork
。
fork
会分配新的内存并拷贝当前进程到一个新的进程。我们称这个新的进程为子进程,调用者进程为父进程。然后,子进程内存会被替换为被执行的程序。因此,我们的 shell,也就是父进程,可以免受内存替换的危险。
让我们看看修改的代码。
... def execute(cmd_tokens): ### 分叉一个子 shell 进程 ### 如果当前进程是子进程,其 `pid` 被设置为 `0` ### 否则当前进程是父进程的话,`pid` 的值 ### 是其子进程的进程 ID。 pid = os.fork() if pid == 0: ### 子进程 ### 用被 exec 调用的程序替换该子进程 os.execvp(cmd_tokens[0], cmd_tokens) elif pid > 0: ### 父进程 while True: ### 等待其子进程的响应状态(以进程 ID 来查找) wpid, status = os.waitpid(pid, 0) ### 当其子进程正常退出时 ### 或者其被信号中断时,结束等待状态 if os.WIFEXITED(status) or os.WIFSIGNALED(status): break ### 返回状态以告知在 shell_loop 中等待下一个命令 return SHELL_STATUS_RUN ...
当我们的父进程调用 os.fork()
时,你可以想象所有的源代码被拷贝到了新的子进程。此时此刻,父进程和子进程看到的是相同的代码,且并行运行着。
如果运行的代码属于子进程,pid
将为 0
。否则,如果运行的代码属于父进程,pid
将会是子进程的进程 id。
当 os.execvp
在子进程中被调用时,你可以想象子进程的所有源代码被替换为正被调用程序的代码。然而父进程的代码不会被改变。
当父进程完成等待子进程退出或终止时,它会返回一个状态,指示继续 shell 循环。
运行
现在,你可以尝试运行我们的 shell 并输入 mkdir test_dir2
。它应该可以正确执行。我们的主 shell 进程仍然存在并等待下一条命令。尝试执行 ls
,你可以看到已创建的目录。
但是,这里仍有一些问题。
第一,尝试执行 cd test_dir2
,接着执行 ls
。它应该会进入到一个空的 test_dir2
目录。然而,你将会看到目录并没有变为 test_dir2
。
第二,我们仍然没有办法优雅地退出我们的 shell。
我们将会在下篇解决诸如此类的问题。
以上是如何用Python創建自己的 Shell(上)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

PHP和Python各有優劣,選擇取決於項目需求和個人偏好。 1.PHP適合快速開發和維護大型Web應用。 2.Python在數據科學和機器學習領域佔據主導地位。

在CentOS系統上高效訓練PyTorch模型,需要分步驟進行,本文將提供詳細指南。一、環境準備:Python及依賴項安裝:CentOS系統通常預裝Python,但版本可能較舊。建議使用yum或dnf安裝Python3併升級pip:sudoyumupdatepython3(或sudodnfupdatepython3),pip3install--upgradepip。 CUDA與cuDNN(GPU加速):如果使用NVIDIAGPU,需安裝CUDATool

在CentOS系統上啟用PyTorchGPU加速,需要安裝CUDA、cuDNN以及PyTorch的GPU版本。以下步驟將引導您完成這一過程:CUDA和cuDNN安裝確定CUDA版本兼容性:使用nvidia-smi命令查看您的NVIDIA顯卡支持的CUDA版本。例如,您的MX450顯卡可能支持CUDA11.1或更高版本。下載並安裝CUDAToolkit:訪問NVIDIACUDAToolkit官網,根據您顯卡支持的最高CUDA版本下載並安裝相應的版本。安裝cuDNN庫:前

Docker利用Linux內核特性,提供高效、隔離的應用運行環境。其工作原理如下:1. 鏡像作為只讀模板,包含運行應用所需的一切;2. 聯合文件系統(UnionFS)層疊多個文件系統,只存儲差異部分,節省空間並加快速度;3. 守護進程管理鏡像和容器,客戶端用於交互;4. Namespaces和cgroups實現容器隔離和資源限制;5. 多種網絡模式支持容器互聯。理解這些核心概念,才能更好地利用Docker。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

在CentOS下選擇PyTorch版本時,需要考慮以下幾個關鍵因素:1.CUDA版本兼容性GPU支持:如果你有NVIDIAGPU並且希望利用GPU加速,需要選擇支持相應CUDA版本的PyTorch。可以通過運行nvidia-smi命令查看你的顯卡支持的CUDA版本。 CPU版本:如果沒有GPU或不想使用GPU,可以選擇CPU版本的PyTorch。 2.Python版本PyTorch

MinIO對象存儲:CentOS系統下的高性能部署MinIO是一款基於Go語言開發的高性能、分佈式對象存儲系統,與AmazonS3兼容。它支持多種客戶端語言,包括Java、Python、JavaScript和Go。本文將簡要介紹MinIO在CentOS系統上的安裝和兼容性。 CentOS版本兼容性MinIO已在多個CentOS版本上得到驗證,包括但不限於:CentOS7.9:提供完整的安裝指南,涵蓋集群配置、環境準備、配置文件設置、磁盤分區以及MinI

在CentOS系統上進行PyTorch分佈式訓練,需要按照以下步驟操作:PyTorch安裝:前提是CentOS系統已安裝Python和pip。根據您的CUDA版本,從PyTorch官網獲取合適的安裝命令。對於僅需CPU的訓練,可以使用以下命令:pipinstalltorchtorchvisiontorchaudio如需GPU支持,請確保已安裝對應版本的CUDA和cuDNN,並使用相應的PyTorch版本進行安裝。分佈式環境配置:分佈式訓練通常需要多台機器或單機多GPU。所
