In the previous article, we have created a shell main loop, split the command input, and executed the command through fork
and exec
. In this part, we will address the remaining issues. First of all, the cd test_dir2
command cannot modify our current directory. Second, we still can't exit gracefully from the shell.
"cd test_dir2
cannot modify our current directory" This sentence is correct, but it is also wrong in a sense. It's correct in the sense that we are still in the same directory after executing the command. However, the directory has actually been modified, but it has been modified in the child process.
Remember that we forked a child process and then executed the command. The process of executing the command did not occur on the parent process. The result is that we only change the current directory of the child process, not the directory of the parent process.
Then the child process exits, and the parent process continues to run in the intact directory.
Therefore, such commands related to the shell itself must be built-in commands. It must be executed in the shell process and not in forking.
Let’s start with the cd
command.
We first create a builtins
directory. Every built-in command will be placed in this directory.
yosh_project |-- yosh |-- builtins | |-- __init__.py | |-- cd.py |-- __init__.py |-- shell.py
In cd.py
, we implement our own cd
command by using the system call os.chdir
.
import os from yosh.constants import * def cd(args): os.chdir(args[0]) return SHELL_STATUS_RUN
Note that we will return the running status of the shell from the built-in function. So, to be able to continue using constants in the project, we moved them to yosh/constants.py
.
yosh_project |-- yosh |-- builtins | |-- __init__.py | |-- cd.py |-- __init__.py |-- constants.py |-- shell.py
In constants.py
, we put all the state constants here.
SHELL_STATUS_STOP = 0 SHELL_STATUS_RUN = 1
Now, our built-in cd
is ready. Let's modify shell.py
to handle these built-in functions.
... ### 导入常量 from yosh.constants import * ### 使用哈希映射来存储内建的函数名及其引用 built_in_cmds = {} def tokenize(string): return shlex.split(string) def execute(cmd_tokens): ### 从元组中分拆命令名称与参数 cmd_name = cmd_tokens[0] cmd_args = cmd_tokens[1:] ### 如果该命令是一个内建命令,使用参数调用该函数 if cmd_name in built_in_cmds: return built_in_cmds[cmd_name](cmd_args) ...
We use a python dictionary variable built_in_cmds
as a hash map hash map to store our built-in functions. We extract the name and parameters of the command in the execute
function. If the command is in our hash map, the corresponding built-in function is called.
(Tip: built_in_cmds[cmd_name]
returns a function reference that can be called directly with arguments.)
We are almost ready to use the built-in cd
function. The final step is to add the cd
function to the built_in_cmds
mapping.
... ### 导入所有内建函数引用 from yosh.builtins import * ... ### 注册内建函数到内建命令的哈希映射中 def register_command(name, func): built_in_cmds[name] = func ### 在此注册所有的内建命令 def init(): register_command("cd", cd) def main(): ###在开始主循环之前初始化 shell init() shell_loop()
We define the register_command
function to add a built-in function to our built-in command hash map. Next, we define the init
function and register the built-in cd
function here.
Pay attention to this line register_command("cd", cd)
. The first parameter is the name of the command. The second parameter is a function reference. In order to allow the second parameter cd
to refer to the cd
function reference in yosh/builtins/cd.py
, we must put the following line of code in yosh/builtins/__init__.py
file.
from yosh.builtins.cd import *
So, in yosh/shell.py
, when we import *
from yosh.builtins
, we can get the yosh.builtins
Imported cd
function reference.
We have the code ready. Let us try to run our shell as a module in the same directory as yosh
, python -m yosh.shell
.
Now, the cd
command can correctly modify our shell directory, and non-built-in commands can still work. very good!
The final piece is finally here: exit gracefully.
We need a function that can modify the shell status to SHELL_STATUS_STOP
. In this way, the shell loop can end naturally and the shell will reach the end and exit.
Same as cd
, if we fork and execute the exit
function in the child process, it will have no effect on the parent process. Therefore, the exit
function needs to be a shell built-in function.
Let’s start here: Create a new file called exit.py
in the builtins
directory.
yosh_project |-- yosh |-- builtins | |-- __init__.py | |-- cd.py | |-- exit.py |-- __init__.py |-- constants.py |-- shell.py
exit.py
defines a exit
function, which only returns a status that can exit the main loop.
from yosh.constants import * def exit(args): return SHELL_STATUS_STOP
Then, we import the exit
function reference located in the yosh/builtins/__init__.py
file.
from yosh.builtins.cd import * from yosh.builtins.exit import *
Finally, we register the exit
command in the init()
function in shell.py
.
... ### 在此注册所有的内建命令 def init(): register_command("cd", cd) register_command("exit", exit) ...
That’s it!
Try to execute python -m yosh.shell
. Now you can type exit
to exit the program gracefully.
I hope you enjoy creating yosh
(your own ## as much as I did) #shell) process. But my version of yosh is still in its early stages. I didn't deal with some corner cases that would crash the shell. There are a lot of built-in commands that I didn't cover. To improve performance, some non-builtin commands can also be implemented as built-in commands (to avoid new process creation time). At the same time, a large number of features have not yet been implemented.
The above is the detailed content of How to create your own Shell with Python (Part 2). For more information, please follow other related articles on the PHP Chinese website!