📅  最后修改于: 2023-12-03 15:38:13.306000             🧑  作者: Mango
在计算机科学中,shell 是一种命令行解释器,它是用户与系统内核之间的接口。在本文章中,我们将通过 C 语言为初学者介绍如何编写自己的简单 shell。
在开始编写 shell 之前,我们需要了解一些基本概念:
|
用于将一个命令的输出连接到另一个命令的输入中,以便在命令之间传递数据。>
和 <
用于将命令的输出发送到文件或从文件中读取输入。现在我们已经了解了 Shell 的基本概念,我们可以开始编写基本的 Shell 代码了。
首先,我们需要包含一些头文件(headers):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
接下来,我们可以定义几个常量和变量:
#define MAX_CMD_LENGTH 100 // 命令的最大长度
#define MAX_ARGS 20 // 命令的最大参数数目
char command[MAX_CMD_LENGTH]; // 用户输入的命令
char *args[MAX_ARGS]; // 用户输入的参数
int num_args; // 用户输入的参数数目
现在,我们可以编写一个循环,每次等待用户输入命令:
while (1) {
printf("$ "); // 打印提示符
fgets(command, MAX_CMD_LENGTH, stdin); // 从标准输入读取命令
command[strcspn(command, "\n")] = 0; // 移除命令字符串中的换行符
parse_command(); // 解析命令
execute_command(); // 执行命令
}
在这个循环中,我们首先打印一个提示符 $
,然后使用 fgets
从标准输入(stdin)中读取命令。之后,我们调用 parse_command
函数解析命令,并调用 execute_command
函数执行命令。
下面,我们来看看 parse_command
函数的代码:
void parse_command() {
num_args = 0; // 重置参数计数器
char *token = strtok(command, " "); // 使用空格分隔符分割命令字符串为多个字符串
while (token != NULL && num_args < MAX_ARGS - 1) {
args[num_args] = token;
num_args++;
token = strtok(NULL, " ");
}
args[num_args] = NULL; // 以 NULL 结尾的参数数组是 execvp() 函数的要求
// 打印用户输入的参数
for (int i = 0; i < num_args; i++) {
printf("arg[%d]: '%s'\n", i, args[i]);
}
}
这个函数的作用是将用户输入的命令字符串解析为多个参数字符串(参考 man strtok
)。我们使用空格分隔符将命令字符串分割为多个字符串,并将每个字符串保存在 args
数组中。我们还使用一个计数器 num_args
来计算参数的数量。最后,我们将 args
数组以 NULL 结尾,这是 execvp
函数的要求。
最后,我们来看看 execute_command
函数的代码:
void execute_command() {
pid_t pid = fork(); // 创建一个子进程
if (pid < 0) { // 如果出现错误
printf("Error: Failed to fork.\n");
exit(1);
} else if (pid == 0) { // 如果是子进程
if (execvp(args[0], args) < 0) { // 执行命令
printf("Error: Failed to execute command.\n");
exit(1); // 退出子进程
}
} else { // 如果是父进程
wait(NULL); // 等待子进程执行完毕
}
}
execute_command
函数的作用是创建一个子进程,并在子进程中执行用户输入的命令。我们使用 fork
函数创建一个子进程,然后在子进程中使用 execvp
函数执行命令。父进程等待子进程完成执行并继续循环。
完整的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_CMD_LENGTH 100
#define MAX_ARGS 20
char command[MAX_CMD_LENGTH];
char *args[MAX_ARGS];
int num_args;
void parse_command() {
num_args = 0;
char *token = strtok(command, " ");
while (token != NULL && num_args < MAX_ARGS - 1) {
args[num_args] = token;
num_args++;
token = strtok(NULL, " ");
}
args[num_args] = NULL;
for (int i = 0; i < num_args; i++) {
printf("arg[%d]: '%s'\n", i, args[i]);
}
}
void execute_command() {
pid_t pid = fork();
if (pid < 0) {
printf("Error: Failed to fork.\n");
exit(1);
} else if (pid == 0) {
if (execvp(args[0], args) < 0) {
printf("Error: Failed to execute command.\n");
exit(1);
}
} else {
wait(NULL);
}
}
int main() {
while (1) {
printf("$ ");
fgets(command, MAX_CMD_LENGTH, stdin);
command[strcspn(command, "\n")] = 0;
parse_command();
execute_command();
}
return 0;
}
现在我们的 shell 可以解析并执行用户输入的命令,但是它还没有实现很多其他常见的 shell 功能,例如重定向、管道等。
要实现这些功能,我们需要在 parse_command
函数中添加一些逻辑来检测命令是否包含特殊符号,并相应地处理输入和输出。我们还可以创建额外的子进程来实现管道等操作。
例如,如果用户输入 ls > output.txt
,我们需要将 ls
的输出重定向到一个名为 output.txt
的文件中。我们可以在 parse_command
函数中添加以下逻辑:
// 检查是否存在重定向符号 '>'
for (int i = 0; i < num_args; i++) {
if (strcmp(args[i], ">") == 0) {
if (i == num_args - 2) { // 检查 '>' 是否在倒数第二个参数位置
char *output_file = args[num_args - 1];
freopen(output_file, "w", stdout); // 将标准输出重定向到文件
args[num_args - 2] = NULL; // 移除 '>' 右边的命令参数
num_args -= 2; // 更新参数数目
break;
} else {
printf("Error: Invalid command syntax.\n");
return;
}
}
}
在这个示例中,我们检查参数数组中是否存在重定向符号 >
,如果是,则打开一个文件作为标准输出,并删除参数数组中的文件名和符号。我们还在循环的末尾使用 break
退出循环。
我们还可以使用类似的逻辑来实现管道、重定向输入等功能。
在本文中,我们介绍了如何使用 C 语言编写一个简单的 shell 程序。我们了解了 shell 的基本概念、解析和执行命令的实现方法,并扩展了 shell 的功能,使其可以支持重定向、管道等操作。
对初学者来说,编写一个自己的 shell 程序是一次很好的练习,它可以帮助我们深入了解操作系统、编译器和 Linux 应用程序的工作方式。希望本篇文章能够对你有所帮助。