In the previous article, we have created a shell main loop, split the command input, and executed the command throughfork
andexec
. In this part, we will address the remaining issues. First of all, thecd 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 thecd
command.
We first create abuiltins
directory. Every built-in command will be placed in this directory.
yosh_project |-- yosh |-- builtins | |-- __init__.py | |-- cd.py |-- __init__.py |-- shell.py
Incd.py
, we implement our owncd
command by using the system callos.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 toyosh/constants.py
.
yosh_project |-- yosh |-- builtins | |-- __init__.py | |-- cd.py |-- __init__.py |-- constants.py |-- shell.py
Inconstants.py
, we put all the state constants here.
SHELL_STATUS_STOP = 0 SHELL_STATUS_RUN = 1
Now, our built-incd
is ready. Let's modifyshell.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 variablebuilt_in_cmds
as a hash map hash map to store our built-in functions. We extract the name and parameters of the command in theexecute
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-incd
function. The final step is to add thecd
function to thebuilt_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 theregister_command
function to add a built-in function to our built-in command hash map. Next, we define theinit
function and register the built-incd
function here.
Pay attention to this lineregister_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 parametercd
to refer to thecd
function reference inyosh/builtins/cd.py
, we must put the following line of code inyosh/builtins/__init__.py
file.
from yosh.builtins.cd import *
So, inyosh/shell.py
, when we import*
fromyosh.builtins
, we can get theyosh.builtins
Importedcd
function reference.
We have the code ready. Let us try to run our shell as a module in the same directory asyosh
,python -m yosh.shell
.
Now, thecd
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 toSHELL_STATUS_STOP
. In this way, the shell loop can end naturally and the shell will reach the end and exit.
Same ascd
, if we fork and execute theexit
function in the child process, it will have no effect on the parent process. Therefore, theexit
function needs to be a shell built-in function.
Let’s start here: Create a new file calledexit.py
in thebuiltins
directory.
yosh_project |-- yosh |-- builtins | |-- __init__.py | |-- cd.py | |-- exit.py |-- __init__.py |-- constants.py |-- shell.py
exit.py
defines aexit
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 theexit
function reference located in theyosh/builtins/__init__.py
file.
from yosh.builtins.cd import * from yosh.builtins.exit import *
Finally, we register theexit
command in theinit()
function inshell.py
.
... ### 在此注册所有的内建命令 def init(): register_command("cd", cd) register_command("exit", exit) ...
That’s it!
Try to executepython -m yosh.shell
. Now you can typeexit
to exit the program gracefully.
I hope you enjoy creatingyosh
(yourown ## as much as I did) #shell) process. But my version ofyoshis 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!