打造一个绝无伦比的 xxx-super-shell (xxx 是你的名字),它能实现下面这些功能:
-
实现 管道 (也就是 |)
-
实现 输入输出重定向(也就是 < > >>)
-
实现 后台运行(也就是 & )
-
实现 cd,要求支持能切换到绝对路径,相对路径和支持 cd -
-
屏蔽一些信号(如 ctrl + c 不能终止)
-
界面美观
-
开发过程记录、总结、发布在个人博客中
要求: -
不得出现内存泄漏,内存越界等错误
-
学会如何使用 gdb 进行调试,使用 valgrind 等工具进行检测
思路
通过frok函数fork出子进程进行对解析后命令的实现,并通过execvp函数对解析完成的命令进行完成,将返回的结果打印到终端上,并通过while进行无限循环。
难点在于对命令行的分析处理,我先将命令行进行对空格的分割,同时标记命令中管道和重定向的下标,再对分割完成的命令进行对应的操作。cd-的实现应先保存上一层的路径,在进行切换。最后由父进程回收对应的子进程。
在实现的时候要注意管道pipe的使用和重定向dup2的使用。
代码实现
1.打印提示
static void prompt()
{
printf("550w-super-shell$\n");
}
2.分析命令行
// 获取命令
if (getline(&linebuff, &linebuff_size, stdin) < 0)
break;
// 截取字符串
tok = strtok(linebuff, DE);
while (tok != NULL)
{
strcpy(cmd[cmdsize], tok);
cmdsize++;
tok = strtok(NULL, DE);
}
// 遍历分析命令
for (int j = 0; j < cmdsize; j++)
{
if (strcmp(cmd[j], ">") == 0) // 有>
{
mode[0] = 1;
xiabiao[0] = j;
}
if (strcmp(cmd[j], ">>") == 0) // 有>>
{
mode[1] = 1;
xiabiao[1] = j;
}
if (strcmp(cmd[j], "<") == 0) // 有<
{
mode[2] = 1;
xiabiao[2] = j;
}
if (strcmp(cmd[j], "&") == 0) // 有&
mode[3] = 1;
if (strcmp(cmd[j], "|") == 0) // 有|
{
mode[4] = 1;
guandao[guandaonum++] = j;
}
if (strcmp(cmd[j], "cd") == 0) // 有cd
{
mode[5] = 1;
}
}
3.管道实现
if (mode[4] == 1) // 有|
{
// 将每个管道前的命令放入三为数组的一个二维数组
char *pipecmd[10][10] = {
0};
for (int i = 0; i < guandaonum; i++) // i代表第几个管道
{
for (int n = 0; n < guandao[i]; n++)
{
pipecmd[i][n] = cmd[n];
//printf("%s\n", pipecmd[i][n]);
}
pipecmd[i][guandao[i]] = NULL;
}
// 将最后一个命令放入
int tmp = 0;
for (int j = guandao[guandaonum] + 1; j <= cmdsize; j++)
{
pipecmd[guandaonum][tmp] = cmd[j];
//printf("%s\n", pipecmd[guandaonum][tmp]);
tmp++;
}
pipecmd[guandaonum][tmp + 1] = NULL;
// printf("%s %s",pipecmd[0][0],pipecmd[1][0]);
// 循环创建管道
int fd[guandaonum][2]; // 存放管道的描述符
for (int i = 0; i < guandaonum; i++) // 循环创建多个管道
{
pipe(fd[i]);
}
int i = 0;
for (i; i < cmdsize; i++) // 父进程循环创建多个并列子进程
{
pid = fork();
if (pid == 0) // 子进程直接退出循环,不参与进程的创建
break;
}
if (pid == 0) // 子进程
{
if (i == 0) // 第一个子进程
{
dup2(fd[0][1], 1); // 绑定写端
close(fd[0][0]); // 关闭读端
// 其他进程读写端全部关闭
for (int j = 1; j < guandaonum; j++)
{
close(fd[j][1]);
close(fd[j][0]);
}
}
else if (i == guandaonum) // 最后一个进程
{
dup2(fd[i - 1][0], 0); // 打开读端
close(fd[i - 1][1]); // 关闭写端
// 其他进程读写端全部关闭
for (int j = 0; j < guandaonum - 1; j++)
{
close(fd[j][1]);
close(fd[j][0]);
}
}
else // 中间进程
{
dup2(fd[i - 1][0], 0); // 前一个管道的读端打开
close(fd[i - 1][1]); // 前一个写端关闭
dup2(fd[i][1], 1); // 后一个管道的写端打开
close(fd[i][0]); // 后一个读端关闭
// 其他的全部关闭
for (int j = 0; j < guandaonum; j++)
{
if (j != i && j != (i - 1))
{
close(fd[j][0]);
close(fd[j][1]);
}
}
}
printf("%s %s", pipecmd[0][0], pipecmd[1][1]);
execvp(pipecmd[i][0], pipecmd[i]); // 执行命令
perror("execvp");
exit(1);
}
// 父进程关闭管道
for (i = 0; i < guandaonum; i++)
{
close(fd[i][0]);
close(fd[i][1]); // 父进程端口全部关掉
}
for (int j = 0; j < cmdsize; j++) // 父进程等待子进程
wait(NULL);
}
4.重定向部分
if (mode[0] == 1 && mode[4] != 1) // 有> 无管道
{
// printf("%d",xiabiao[0]);
char *command[100];
for (int n = 0; n < 100; n++)
{
command[n] = malloc(100 * sizeof(char));
}
int n = guandao[guandaonum - 1]; // 最后一个管道的下标开始遍历
for (n; n < xiabiao[0]; n++)
{
strcpy(command[n], cmd[n]);
// printf("%s\n", command[n]);
}
command[n] = NULL;
int fd;
fd = open(cmd[xiabiao[0] + 1], O_RDWR);
if (fd < 0)
{
perror("open");
exit(1);
}
dup2(fd, STDOUT_FILENO);
execvp(command[0], command);
perror("execvp:");
exit(1);
}
if (mode[1] == 1 && mode[4] != 1) // 有>> 无管道
{
// printf("%d",xiabiao[0]);
char *command[100];
for (int n = 0; n < 100; n++)
{
command[n] = malloc(100 * sizeof(char));
}
int n = guandao[guandaonum - 1]; // 最后一个管道的下标开始遍历
for (n; n < xiabiao[1]; n++)
{
strcpy(command[n], cmd[n]);
// printf("%s\n", command[n]);
}
command[n] = NULL;
int fd;
// printf("%s",cmd[xiabiao[1] + 1]);
fd = open(cmd[xiabiao[1] + 1], O_RDWR | O_APPEND);
if (fd < 0)
{
perror("open");
exit(1);
}
dup2(fd, STDOUT_FILENO);
execvp(command[0], command);
perror("execvp:");
exit(1);
}
if (mode[2] == 1) // 有<
{
char *command[100];
for (int n = 0; n < 100; n++)
{
command[n] = malloc(100 * sizeof(char));
}
int n = 0;
for (n; n < xiabiao[2]; n++)
{
strcpy(command[n], cmd[n]);
// printf("%s\n", command[n]);
}
command[n] = NULL;
int fd;
// printf("%s",cmd[xiabiao[1] + 1]);
fd = open(cmd[xiabiao[2] + 1], O_RDWR);
if (fd < 0)
{
perror("open");
exit(1);
}
dup2(fd, STDIN_FILENO);
execvp(command[0], command);
perror("execvp:");
exit(1);
}
5.cd部分
char strpwd[1024];
void mycd(char *argv[])
{
if (argv[1] == NULL)
{
getcwd(strpwd, sizeof(strpwd));
chdir("/home");
}
else if (strcmp(argv[1], "-") == 0)
{
char strpwd1[1024];
getcwd(strpwd1, sizeof(strpwd));
chdir(strpwd);
printf("%s\n", strpwd);
strcpy(strpwd, strpwd1);
}
else if (strcmp(argv[1], "~") == 0)
{
getcwd(strpwd, sizeof(strpwd));
chdir("/home/abljiu");
}
else
{
getcwd(strpwd, sizeof(strpwd));
chdir(argv[1]);
}
}
完整代码:https://github.com/abljiu/linux/tree/main/myshell