Linux C编程一站式学习P585编程练习:
实现简单的Shell
用讲过的各种C函数实现一个简单的交互式Shell,要求:
- 给出提示符,让用户输入一行命令,识别程序名和参数并调用适当的exec函数执行程序,待执行完成后再次给出提示符。
- 识别和处理以下符号:
· 简单的标准输入输出重定向(<和>):仿照例30.5 “wrapper”,先dup2然后exec。
· 管道(|):Shell进程先调用pipe创建一对管道描述符,然后fork出两个子进程,一个子进程关闭读端,调用dup2把写端赋给标准输出,另一个子进程关闭写端,调用dup2把读端赋给标准输入,两个子进程分别调用exec执行程序,而Shell进程把管道的两端都关闭,调用wait等待两个子进程终止。
你的程序应该可以处理以下命令:
○ls△-l△-R○>○file1○
○cat○<○file1○|○wc△-c○>○file1○
○表示零个或多个空格,△表示一个或多个空格
思路:
main函数(主进程)以一个循环从标准输入stdin不断读取指令,直到ctrl+c退出。读取的指令首先作为字符串存放在input_str中,然后调用split函数对其进行拆分,根据管道(|)切割成多条指令,存放在cmds中。程序假设拆分后得到的指令最多10条。
按顺序处理cmds中的每一条指令:对每条指令,fork一个子进程并调用exec函数处理它。exec函数中使用take_token函数对指令根据空格和重定向符进行拆分,拆分后得到记录执行文件和参数的数组token(token[0]为指令的可执行文件名,作为execvp函数的第一个参数;整个token数组作为execvp函数的第二个参数)。若有重定向符">“或”<"存在,则读取紧跟重定向符的字符串作为文件名,根据符号重定向输入或输出到指定文件。
程序中使用管道fd[10][2]作为多条指令输入输出之间的通信手段:假设当前执行的为第n条指令
- 若当前指令有上一条指令,从管道fd[n-1][0]读取上一条指令的输出作为输入,并关闭管道写端fd[n-1][1](57~61行);
- 若当前指令有下一条指令,重定向其输出到管道fd[n][1]被下一条指令读取,并关闭管道读端fd[n][0](63~65行);
- 若当前指令是最后一条指令,恢复输出到标准输出stdout;
- 以上步骤中子进程都由父进程fork而来,因此属于子进程之间通过管道相互通讯,父进程需要关闭管道的读写两段。
在程序中我的逻辑是,管道fd[n]沟通第n和第n+1个指令,因此fork出第n+1个子进程时,父进程可以关闭第n个(即前一个)管道的读写端。
(这里每次没关干净管道,下此循环开始又调用pipe,不知道会不会有泄露之类的。。实在不太懂,如果有人能帮我指出就非常感谢_(:3」∠)_)
我的程序源代码如下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#define BUFF 512
int split(char input_str[], char *cmds[]);
void take_token(char cmd[], char *token[], char *file[]);
void exec(char cmd[]);
/* test:
cat < file1|wc -c > file1
cat<file1 | wc -c > file1
cat < file1| wc -c>file1
cat < file1 | wc -c > file1
ls -l -R > file1
ls -l -R> file1
ls -l -R >file1
*/
int main()
{
char input_str[BUFF]; // 输入指令字符串
char *cmds[10]; // 分割完的指令字符串
int fd[10][2]; // 管道
int i, pid, cmd_num;
int save_stdin, save_stdout;
save_stdin = dup(STDIN_FILENO);
save_stdout = dup(STDOUT_FILENO);
while (1) {
printf("username:path$ ");
fgets(input_str, BUFF, stdin);
cmd_num = split(input_str, cmds);
if (cmd_num > 1) {
if (pipe(fd[0]) < 0) { // 当前pipe
perror("pipe");
exit(1);
}
}
i = 0;
while (cmds[i] != NULL) {
/* printf("%s\n", cmds[i]);
i++; */
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
} else if (pid == 0) { // 当前指令子进程
/* printf("1\n"); */
if (i > 0) { // 若有上一条,读上一pipe并关闭写端
/* printf("2\n"); */
close(fd[i-1][1]);
dup2(fd[i-1][0], STDIN_FILENO);
}
if (cmds[i+1] != NULL) { // 若有下一条,写当前pipe并关闭读端
close(fd[i][0]);
dup2(fd[i][1], STDOUT_FILENO);
} else { // 当前指令是最后一条,恢复标准输出
dup2(save_stdout, STDOUT_FILENO);
}
exec(cmds[i]);
} else { // 当前指令父进程
if (cmds[i+1] != NULL && cmds[i+2] != NULL) {
if (pipe(fd[i+1]) < 0) { // 当前pipe
perror("pipe");
exit(1);
}
}
if (i > 0) { // 两次fork后关闭上一条指令的父进程pipe读写
close(fd[i-1][0]); // 顺序:fork子1,fork子2,关闭父读写
close(fd[i-1][1]); // 这个if块写在i++后面会阻塞子进程。。
}
waitpid(pid, NULL, 0); // 等待指令执行结束
i++;
}
}
}
return 0;
}
int split(char input_str[], char *cmds[])
{
int i = 0; // 指令个数
char *str = NULL, *saveptr = NULL;
char *enter = NULL;
for (i=0, str=input_str; ; i++, str=NULL){
cmds[i] = strtok_r(str, "|", &saveptr);
if (cmds[i] == NULL) {
enter = strrchr(cmds[i-1], '\n');
*enter = ' '; // 替换最末尾换行符
break;
}
}
return i;
}
void take_token(char cmd[], char *token[], char *file[]) {
int i;
char *op;
char *str = NULL, *saveptr = NULL;
int fd, std_fileno, file_mode;
if ((op = strrchr(cmd, '<')) != NULL) {
std_fileno = STDIN_FILENO;
file_mode = O_RDONLY;
}
else if ((op = strrchr(cmd, '>')) != NULL) {
std_fileno = STDOUT_FILENO;
file_mode = O_WRONLY | O_CREAT | O_TRUNC;
}
if (op) {
*op = '\0';
*file = strtok_r((op+1), " ", &saveptr);
fd = open(*file, file_mode, 0666);
if (fd < 0) {
perror("open");
exit(1);
}
dup2(fd, std_fileno);
//printf("[[%s]]", *file);
}
for (i=0, str=cmd, saveptr = NULL; ; i++, str=NULL) {
token[i] = strtok_r(str, " ", &saveptr);
if (token[i] == NULL)
break;
}
return ;
}
void exec(char cmd[])
{
char *tokens[100];
char *str, *saveptr, *file;
int i, mode;
take_token(cmd, tokens, &file);
execvp(tokens[0], tokens);
perror(tokens[0]);
return ;
}
执行结果示例:
qxy@qxy:/mnt/c/Users/saltyfish/Desktop/test$ ./test1
username:path$ ls
1.jpg clean file1 file2 img Makefile pytest test test1 test1.c test1.o test.cpp tmp tmp.c
username:path$ ls -l
total 72
-rwxrwxrwx 1 qxy qxy 7448 Dec 11 2017 1.jpg
drwxrwxrwx 1 qxy qxy 4096 Oct 3 14:59 clean
-rwxrwxrwx 1 qxy qxy 10216 Oct 17 12:45 file1
-rwxrwxrwx 1 qxy qxy 4 Oct 3 14:26 file2
drwxrwxrwx 1 qxy qxy 4096 Oct 3 15:21 img
-rwxrwxrwx 1 qxy qxy 93 Jun 23 2017 Makefile
drwxrwxrwx 1 qxy qxy 4096 Oct 3 15:21 pytest
drwxrwxrwx 1 qxy qxy 4096 Oct 3 15:21 test
-rwxrwxrwx 1 qxy qxy 13560 Oct 17 12:26 test1
-rwxrwxrwx 1 qxy qxy 4420 Oct 17 12:26 test1.c
-rwxrwxrwx 1 qxy qxy 4784 Oct 17 12:26 test1.o
-rwxrwxrwx 1 qxy qxy 974 Mar 25 2018 test.cpp
-rwxrwxrwx 1 qxy qxy 9936 Sep 23 10:06 tmp
-rwxrwxrwx 1 qxy qxy 779 Oct 16 22:35 tmp.c
username:path$ ls -l -R > file1
username:path$ cat <file1 | wc -c >file2
username:path$ cat file2
10216
username:path$