实验要求:
- 实现 管道 (也就是?
| ) - 实现 输入输出重定向(也就是?
< ?> ?>> ) - 实现 后台运行(也就是?
& ?) - 实现?
cd ,要求支持能切换到绝对路径,相对路径和支持?cd - - 屏蔽一些信号(如?
ctrl + c ?不能终止) - 界面美观
- 开发过程记录、总结、发布在个人博客中
这次实验于13日结束,最近太混了,今天才发博客
思路:
-
主体框架 -
void type_prompt(void); //终端提示符 -
read_command()//读取参数 -
split_command()//解析参数 -
执行command -
内建命令
shell实现的基本逻辑简单分为三部:读取,分析,执行
我们可以写一个loop函数,也可以放在main函数里
?
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <cstring>
#include <pwd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#define LSH_TOK_DELIM " \t\r\n\a"
#define MAXPIDTABLE 1024
#define NONE 0
#define BACKGROUND 1
#define IN_REDIRECT 2
#define OUT_REDIRECT 4
#define OUT_REDIRECT_CL 16
#define IN_REDIRECT_CL 8
#define PIPE 32
void type_prompt(void); //终端提示符,如果当前路径在用户路径下,那么用户路径就用~代替,否则会显示完整路径。
char *read_line(void); //从标准输入读取一行
char **split_line(char *line); //将该行解析为参数列表
char execute(char **args); //
int shell_cd(char **args); //实现cd
int num;
pid_t BPTable[MAXPIDTABLE];
int main(int argc, char *argv[])
{
char *line;
char **args;
int status;
do
{
type_prompt();
line = read_line();
args = split_line(line);
status = execute(args);
} while (status);
return 0;
}
?
?实现一个简易的type_prompt
顾名思义,这个要提供一个终端上的提示符
void type_prompt(void)
{
struct passwd *name;
name = getpwuid(getuid());
char path[255] = {0};
getcwd(path, 255); // getcwd() 会将当前的工作目录绝对路径复制到参数buf 所指的内存空间
printf("%s@PC:", name->pw_name);
if (strlen(path) < strlen(name->pw_dir) || strncmp(path, name->pw_dir, strlen(name->pw_dir)) != 0)
printf("%s ", path);
else
printf("~%s ", path + strlen(name->pw_dir)); //缩进
if (geteuid() == 0)
printf("#");
else
printf("$");
return;
}
事先不提前知道用户将在其 shell 中输入多少文本。不能简单地分配一个块并希望它们不会超过它。相反,需要从一个块开始,如果它们超过它,重新分配更多空间。
我么可以使用getline函数,完成我们刚刚实现的大部分工作。
getline()函数:
?https://blog.csdn.net/qq_26093511/article/details/73350912
char *read_line(void)
{
/* char *line = NULL;
size_t bufsize = 0;
if (getline(&line, &bufsize, stdin) == -1) //失败:返回-1。
{
if (feof(stdin)) // C 库函数 int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。
exit(0); // EOF
else
{
perror("readline");
exit(-1);
}
}
return line;
*/
size_t bufsize = 1024;
int position = 0;
char *buffer = (char *)malloc(sizeof(char) * bufsize);
int c;
if (!buffer)
{
fprintf(stderr, "allocation error\n");
exit(EXIT_FAILURE);
}
while (1)
{
// Read a character
c = getchar();
if (c == EOF)
{
exit(EXIT_SUCCESS);
}
else if (c == '\n')
{
buffer[position] = '\0';
return buffer;
}
else
{
buffer[position] = c;
}
position++;
if (position >= bufsize)
{
bufsize += 1024;
buffer = (char *)realloc(buffer, bufsize);
if (!buffer)
{
perror("allocation error\n");
exit(EXIT_FAILURE);
}
}
}
}
现在,我们需要将该行解析为参数列表。
此时,我们使用strtok函数
C 库函数char *strtok(c??har *str, const char *delim)使用分隔符delim将字符串str分解为一系列标记。
宣言
以下是 strtok() 函数的声明。
<span style="color:rgba(0, 0, 0, 0.87)"><span style="color:#000088">char</span> <span style="color:#666600">*</span><span style="color:#000000">strtok</span><span style="color:#666600">(</span><span style="color:#000088">char</span> <span style="color:#666600">*</span><span style="color:#000000">str</span><span style="color:#666600">,</span> <span style="color:#000088">const</span> <span style="color:#000088">char</span> <span style="color:#666600">*</span><span style="color:#000000">delim</span><span style="color:#666600">)</span></span>
参数
?在函数开始时,我们通过调用来开始标记化strtok 。它返回一个指向第一个标记的指针。实际上所做的是返回指向您strtok() 给它的字符串中的指针,并将\0 字节放在每个标记的末尾。我们将每个指针存储在字符指针数组(缓冲区)中。
char **split_line(char *line)
{
size_t bufsize = 1024;
int position = 0;
char **tokens = (char **)malloc(bufsize * sizeof(char *));
memset(tokens, 0, 1024);
char *token;
if (!tokens)
{
puts("allocation error\n");
exit(EXIT_FAILURE);
}
token = strtok(line, LSH_TOK_DELIM);
while (token != NULL)
{
tokens[position] = token;
position++;
if (position >= bufsize)
{
bufsize += 1024;
tokens = (char **)realloc(tokens, bufsize * sizeof(char *));
if (!tokens)
{
puts("allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
num = position;
tokens[position] = NULL;
return tokens;
}
?在execute中,对">""|"等进行判断。
char execute(char **args)
{
if (args[0] == NULL)
return 1;
if (strcmp(args[0], "cd") == 0)
return shell_cd(args);
int flag = 0, i, fd;
char *command = NULL;
pid_t childpid, childpid2;
int status;
for (i = 0; i < num; i++)
{
if (strcmp(args[i], "|") == 0)
{
flag += PIPE;
args[i] = NULL;
if (args[i + 1] != NULL)
command = args[i + 1];
else
perror("输入错误/n");
}
else if (strcmp(args[i], ">") == 0)
{
flag += OUT_REDIRECT_CL;
args[i] = NULL;
if (args[i + 1] != NULL)
command = args[i + 1];
else
perror("输入错误/n");
}
else if (strcmp(args[i], ">>") == 0)
{
flag += OUT_REDIRECT;
args[i] = NULL;
if (args[i + 1] != NULL)
command = args[i + 1];
else
perror("输入错误/n");
}
else if (strcmp(args[i], "<") == 0)
{
flag += IN_REDIRECT_CL;
args[i] = NULL;
if (args[i + 1] != NULL)
command = args[i + 1];
else
perror("输入错误/n");
}
else if (strcmp(args[i], "<<") == 0)
{
flag += IN_REDIRECT;
args[i] = NULL;
if (args[i + 1] != NULL)
command = args[i + 1];
else
perror("输入错误/n");
}
else if (strcmp(args[i], "&") == 0)
{
flag += BACKGROUND;
args[i] = NULL;
if (args[i + 1] != NULL)
command = args[i + 1];
else
perror("输入错误/n");
}
}
childpid = fork();
switch (flag)
{
case NONE:
{
if (execvp(args[0], args) == -1)
{
perror("execute");
exit(EXIT_FAILURE);
}
break;
}
case PIPE:
{
int pipe_fd[2], in_fd, out_fd;
if (pipe(pipe_fd) < 0)
{
printf("shell error:pipe failed.\n");
exit(0);
}
if ((childpid2 = fork()) == 0)
{
close(pipe_fd[1]);
close(fileno(stdin));
dup2(pipe_fd[0], fileno(stdin));
close(pipe_fd[0]);
execvp(args[0], args);
exit(EXIT_SUCCESS);
}
else
{
close(pipe_fd[0]);
close(pipe_fd[1]);
waitpid(childpid2, &status, 0);
}
break;
}
case OUT_REDIRECT_CL:
{
fd = open(command, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU);
dup2(fd, 1);
execvp(args[0], args);
exit(0);
break;
}
case OUT_REDIRECT:
{
fd = open(command, O_RDWR | O_CREAT | O_APPEND | S_IRWXU);
dup2(fd, 1);
execvp(args[0], args);
exit(0);
break;
}
case IN_REDIRECT:
{
fd = open(command, O_CREAT | O_RDONLY);
dup2(fd, 0);
execvp(args[0], args);
exit(0);
break;
}
case IN_REDIRECT_CL:
{
fd = open(command, O_CREAT | O_RDONLY);
dup2(fd, 0);
execvp(args[0], args);
exit(0);
break;
}
case BACKGROUND:
{
printf("Child pid:%u\n", childpid);
for (i = 0; i < MAXPIDTABLE; i++)
if (BPTable[i] == 0)
BPTable[i] = childpid;
if (i == MAXPIDTABLE)
perror("Too much background processes/n");
break;
}
default:
perror("输入错误!\n");
break;
}
if (childpid < 0)
perror("execute2");
else
{
do
{
waitpid(childpid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
free(args);
return 1;
}
实现cd
?
int shell_cd(char **args)
{
if (args[1] == NULL || args[1] == "~")
{
struct passwd *name;
name = getpwuid(getuid());
char *path = (char *)calloc(255, sizeof(char));
char *ar = (char *)calloc(255, sizeof(char));
strcpy(ar, name->pw_name);
strcpy(path, "/home/");
strcat(path, ar);
strcat(path, "/");
chdir(path);
}
else
{
if (chdir(args[1]) != 0)
{
perror("cd error");
}
}
return 1;
}
?
?使用valgrind检测:
?实验不足:
1.代码仍有Bug未解决。
2.个别要求未满足。
3.第一次写完代码后使用valgrind出现大量段错误未解决,于是重构代码。
参考资料:?
shell 的实现:https://www.cnblogs.com/wuyuegb2312/p/3399566.html
? ? ? ? ? ? ? ? ? ? ??https://brennan.io/2015/01/16/write-a-shell-in-c/
valgrind:Valgrind详细教程(1) Memcheck_tissar的博客-CSDN博客_valgrind教程?
|