1.题目要求
实现shell程序,要求在第1版的基础上,添加如下功能:
- 实现文件重定向
# 执行sh2
$ ./sh2
# 执行命令echo,并将输出保存到文件log中
> echo hello >log
# 打印cat命令的输出结果
> cat log
hello
2.解决思路
在sh1.c
的基础上,在创建子进程之后,需要判断当前键入的命令是否需要文件重定向,如果是需要实现文件重定向,反之按照之前的系统调用execvp
装入程序执行。因此在结构体中增加一个char *
型变量output
用于存储输出重定向的文件路径,初始化为NULL
。在分割命令字符串子函数while
循环中增加判断,调用库函数strchr
判断是否含有字符>
,若否返回NULL
,若是返回自>
开始的余下所有字符子串,将返回值res
指针加1存储到output
,这样只需要判断output
是否为NULL
来判断是否需要重定向。若需要则调用handle_redirect
函数,先打开文件,再使用dup2
系统调用,文件描述符1是标准输出,fd
指向文件实现标准输出重定向到文件。
3.代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SIZE 256
#define MAX_ARGC 16
struct Command {
int argc;
char *argv[MAX_ARGC];
char *input; //用于重定向输入
char *output; //用于重定向输出
} commands;
void panic(char *message) {
perror(message);
exit(EXIT_FAILURE);
}
// 将 command 以空格作为分割符分割
void parse_command(char *command) {
// char line[SIZE]; // 注意不能这样写
char *line = (char *)malloc(sizeof(command));
strcpy(line, command);
// 清理内存
commands.argc = 0;
for(int i = 0; i < MAX_ARGC; i++)
commands.argv[i] = NULL;
commands.input = NULL;
commands.output = NULL;
char *word;
char *delim = " \n";
word = strtok(line, delim);
while(word != NULL) {
char *output = strchr(word, '>');
char *input = strchr(word, '<');
if(output)
commands.output = output + 1;
else if(input)
commands.input = input + 1;
else
commands.argv[commands.argc++] = word;
word = strtok(NULL, delim);
}
}
// 处理内置命令cd、pwd、exit
void handle_built_in_command() {
if(strcmp(commands.argv[0], "cd") == 0) {
int error = chdir(commands.argv[1]);
if(error < 0)
perror("cd");
else {
char *path = getcwd(NULL, 0);
printf("current working directory: %s\n", path);
free(path);
}
}
else if(strcmp(commands.argv[0], "pwd") == 0) {
char *path = getcwd(NULL, 0);
printf("current working directory: %s\n", path);
free(path);
}
else if(strcmp(commands.argv[0], "exit") == 0) {
exit(0);
}
}
// 处理文件重定向
void handle_redirect() {
if(commands.output) {
int fd = open(commands.output, O_CREAT | O_RDWR);
if(fd < 0)
panic("open");
dup2(fd, 1);
close(fd);
}
if(commands.input) {
int fd = open(commands.input, O_CREAT | O_RDWR);
if(fd < 0)
panic("open");
dup2(fd, 0);
close(fd);
}
}
void mysys(char *command) {
parse_command(command);
// 单独处理内置命令cd、pwd、exit
if(strcmp(commands.argv[0], "cd") == 0 || strcmp(commands.argv[0], "pwd") == 0 || strcmp(commands.argv[0], "exit") == 0) {
handle_built_in_command();
return ;
}
pid_t pid;
pid = fork();
if(pid == 0) {
// 处理需要文件重定向的情况
if(commands.input || commands.output) {
handle_redirect();
}
int error = execvp(commands.argv[0], commands.argv);
if(error < 0)
perror("execvp");
}
wait(NULL);
}
int main() {
char command[SIZE];
int count;
while(1) {
write(1, ">", sizeof(">"));
count = read(0, command, sizeof(command));
command[count] = 0;
mysys(command);
}
return 0;
}
4.运行结果
$ gcc sh2.c -o sh2
$ ./sh2
>echo hello >log
>cat log
cat: log: 权限不够
>sudo cat log
[sudo] (用户名XXX) 的密码:
hello
>exit