<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中国”