ZLSH 是一个基本Shell,主要功能包括命令解析、管道操作以及后台运行等。这个Shell是由C语言编写而成的,利用Linux系统提供的API和库函数实现各个功能。
整体结构
该Shell程序包含了很多函数,每个函数都完成特定的任务。其中,主函数main()主要完成如下几个任务:
1.调用showPrompt()函数显示命令提示符;
2.使用fgets()函数读取用户输入的命令行字符串,并传递给parseCmd()函数进行解析;
3.跳转到ZLASH_START标签处,重新开始等待新的用户输入。
函数
showPrompt
showPrompt()函数主要完成获取当前登录用户的用户名、主机名、以及当前所在路径,并将它们拼接成一个命令提示符并输出给用户。具体实现过程如下:
1.使用getuid()获取当前登录用户的UID;
2.使用getpwuid()函数从用户ID获取passwd结构体信息,其中包括用户名;
3.使用gethostname()函数获取主机名;
4.使用getcwd()函数获取当前工作目录,并根据情况对其进行处理;
5.将上述信息拼接成一个命令提示符,并输出给用户。
intHandler
intHandler()函数主要是用于处理进程接收到中断信号时的情况,即用户按下Ctrl+C键的情况。在这种情况下,程序将输出空行并重新显示命令提示符。
parseCmd
parseCmd()函数是整个shell程序的核心,它主要完成对用户输入的命令进行解析,并根据解析结果分别调用不同的函数实现相应的功能。
在该函数中,首先通过strtok_r()函数按照管道符“|”对命令字符串进行分割,得到多个子命令,并使用char ***pipeline指针数组保存每个子命令的参数列表。然后,对每个子命令再次使用strtok_r()函数按照空格和制表符进行分割,得到各个参数,并根据参数类型(如输入重定向、输出重定向等)将参数信息存储在span类型的结构体中。
最后,根据pipeline数组的元素个数是否为1来判断是否需要使用管道操作,如果只有一个子命令,则直接调用execute_cmd()函数执行该命令;否则,调用execute_pipeline()函数执行管道操作。
execute_cmd
execute_cmd()函数主要用于执行一个单独命令,并根据需要设置是否使用后台运行模式。其中,fork()系统调用用于创建一个新的进程,execvp()系统调用用于在新进程中运行指定的命令。
execute_pipeline
execute_pipeline()函数主要用于执行多个子命令,并利用pipe()系统调用建立管道连接。在该函数内部,先使用for循环依次创建每个子进程,并使用dup2()系统调用建立管道连接。最后,使用wait()函数等待所有子进程都执行完毕。
#include <linux/limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void intHandler();
void showPrompt();
void parseCmd(char* cli);
typedef struct {
enum {
NORM, PSTART, PMID, PEND } cmd_t;
// char env[NAME_MAX];
char cmd[NAME_MAX][NAME_MAX];
char in[NAME_MAX];
enum {
ROUT, RADD } out_t;
char out[NAME_MAX];
} span;
int main(void) {
signal(SIGINT, intHandler);
ZLSH_START:
showPrompt();
char cli[PATH_MAX];
if (!fgets(cli, PATH_MAX, stdin))
// when read Ctrl-D(EOF)
exit(0);
cli[strlen(cli) - 1] = '\0';
parseCmd(cli);
goto ZLSH_START;
}
void intHandler() {
printf("\n");
showPrompt();
fflush(stdout);
}
void showPrompt() {
// username
uid_t uid = getuid();
struct passwd* user = getpwuid(uid);
// hostname
char host[NAME_MAX];
gethostname(host, NAME_MAX);
// current working directory
char rawdir[PATH_MAX], *dir = rawdir;
getcwd(dir, PATH_MAX);
if (!strcmp(dir, user->pw_dir))
strcpy(dir, "~");
else if (dir[1])
dir = 1 + strrchr(dir, '/');
// output
printf("[%s@%s %s]%c ", user->pw_name, host, dir, uid ? '$' : '#');
}
void execute_cmd(char** cmd, int bg) {
pid_t pid = fork();
if (pid == -1) {
fprintf(stderr, "fork error\n");
exit(1);
} else if (pid == 0) {
execvp(cmd[0], cmd);
perror("exec error");
exit(1);
} else {
if (!bg) {
int status;
waitpid(pid, &status, 0);
}
}
}
void execute_pipeline(char*** pipeline, int n, int bg) {
int pipefd[n - 1][2];
for (int i = 0; i < n - 1; ++i) {
if (pipe(pipefd[i]) == -1) {
fprintf(stderr, "pipe error\n");
exit(1);
}
}
for (int i = 0; i < n; ++i) {
pid_t pid = fork();
if (pid == -1) {
fprintf(stderr, "fork error\n");
exit(1);
} else if (pid == 0) {
if (i != 0) {
dup2(pipefd[i - 1][0], STDIN_FILENO);
close(pipefd[i - 1][0]);
close(pipefd[i - 1][1]);
}
if (i != n - 1) {
dup2(pipefd[i][1], STDOUT_FILENO);
close(pipefd[i][0]);
close(pipefd[i][1]);
}
execvp(pipeline[i][0], pipeline[i]);
perror("exec error");
exit(1);
}
}
for (int i = 0; i < n - 1; ++i) {
close(pipefd[i][0]);
close(pipefd[i][1]);
}
if (!bg) {
int status;
for (int i = 0; i < n; ++i) {
wait(&status);
}
}
}
void parseCmd(char* cli) {
// parse cli str
span* cmd = malloc(sizeof(span[NAME_MAX]));
int bg = 0;
char *saveptr1, *saveptr2;
char* token = strtok_r(cli, "|", &saveptr1);
char*** pipeline = malloc(sizeof(char** [NAME_MAX]));
int n = 0;
while (token) {
char** args = malloc(sizeof(char* [NAME_MAX]));
int i = 0;
char* arg = strtok_r(token, " \t", &saveptr2);
while (arg) {
if (*arg == '<') {
arg = strtok_r(NULL, " \t", &saveptr2);
strcpy(cmd->in, arg);
} else if (*arg == '>') {
arg = strtok_r(NULL, " \t", &saveptr2);
if (*(arg + 1) == '\0' && *arg == '>') {
cmd->out_t = RADD;
} else {
cmd->out_t = ROUT;
}
strcpy(cmd->out, arg);
} else if (*arg == '&') {
bg = 1;
} else {
args[i++] = arg;
}
arg = strtok_r(NULL, " \t", &saveptr2);
}
args[i] = NULL;
pipeline[n++] = args;
token = strtok_r(NULL, "|", &saveptr1);
}
pipeline[n] = NULL;
if (n == 1) {
execute_cmd(pipeline[0], bg);
} else {
execute_pipeline(pipeline, n, bg);
}
free(cmd);
for (int i = 0; i < n; ++i) {
free(pipeline[i]);
}
free(pipeline);
}
总之,这个基本Shell实现了解析命令、管道操作以及后台运行等功能。在该程序的基础上,我们可以进一步扩展并添加更多的功能,如变量替换、文件描述符操作以及信号量处理等。