Linux从0到1——自定义shell2.0【添加重定向功能】

0. 前言


学习本篇之前,需要大家掌握简单的自定义shell


1. 宏+全局变量的设计


// redir
#define NoneRedir 0
#define OutPutRedir 1
#define AppendRedir 2
#define InputRedir 3

int redir = NoneRedir;  // 默认是没有重定向 
char *filename = NULL;  // 重定向的文件名
  • 定义了四个宏,表示不同的重定向类型;
  • 变量redir用来记录当前进程的重定向类型;
  • filename用来记录重定向符号后面跟的文件名。

2. 主函数设计


int main()
{
    while(1)
    {
        // 每次循环对这两个重定向指标赋初始值
        redir = NoneRedir;
        filename = NULL;

        char usercommand[NUM];      // 用户输入的命令字符串
        char *argv[SIZE];           // 命令行参数表
        // 1. 打印提示符+获取用户命令字符串
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if (n <= 0) continue;   // 如果getUserCommand函数出错了或者得到的字符串是空串,就不要往后走了
        // 1.1 检查是否发生重定向
        // "ls -a -l > log.txt" -> "ls -a -l" redir_type "log.txt" 
        checkRedir(usercommand, strlen(usercommand));

        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);

        // 3. check build-in command,先检查是否是内建命令
        n = doBuildin(argv);
        if(n) continue;

        // 4. 创建子进程,执行对应的命令
        execute(argv);
    }    

    return 0;
}
  • 在分割字符串之前,先进行是否发生重定向的检查。

3. checkRedir函数的设计


#define SkipSpace(filename) do{ while(isspace(*filename)) filename++; }while(0)

void checkRedir(char usercommand[], int len)
{
    char *end = usercommand + len - 1;
    char *start = usercommand;
    while(end > start)
    {
        if (*end == '>')
        {
            if (*(end - 1) == '>')
            {
                redir = AppendRedir;
                *(end - 1) = '\0';
                filename = end + 1;
                SkipSpace(filename);
                break;
            }
            else
            {
                redir = OutPutRedir;
                *end = '\0';
                filename = end + 1;
                SkipSpace(filename);
                break;
            }
        }
        else if (*end == '<')
        {
            *end = '\0';
            filename = end + 1;
            SkipSpace(filename);    // 如果有空格就跳过
            redir = InputRedir;
            break;
        }
        else
        {
            end--;
        }
    }
}
  • 从后向前查找有没有重定向符号。如果是<输入重定向,直接执行<的逻辑;如果是>,还要判断是否是追加重定向>>,向前再看一步。
  • 这个函数的功能就是将,命令,重定向符号,文件名,三个元素拆开。基本的思路就是:
    • \0将命令和文件名分隔开,这样进行字符串分割时,就只需要分割命令的部分了;
    • filename将文件名存起来,用redir记录当前的重定向类别;
    • 获取文件名时还设计了一个宏SkipSpace,用来跳过空格。

4. 改造execute函数


// 执行一个命令
int execute(char *argv[])
{
    pid_t id = fork();
    if (id < 0) return -1;
    else if (id == 0)
    {
        // child
        int fd = 0;
        if (redir == InputRedir)
        {
            fd = open(filename, O_RDONLY);    // 差错处理暂时不做
            dup2(fd, 0);
        }
        else if (redir == OutPutRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if (redir == AppendRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        }

        // 程序替换不会影响重定向
        execvp(argv[0], argv);
        // 走到下面说明替换出错了
        exit(1);
    }
    else
    {
        // father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
        }
    }

    return 0;
}

5. 完整代码


#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<ctype.h>
#include<fcntl.h>

// redir
#define NoneRedir 0
#define OutPutRedir 1
#define AppendRedir 2
#define InputRedir 3

int redir = NoneRedir;  // 默认是没有重定向 
char *filename = NULL;  // 重定向的文件名

#define NUM 1024    // 用户在命令行能最大输入的字符串长度
#define SIZE 64     // 命令行参数表的大小
#define SEP " "     // 分割符
//#define Debug 1
char cwd[1024];    // 当前路径必须全局有效
char enval[1024];   // 全局环境变量
int lastcode = 0;   // 最近一次进程退出码

// 获取用户名
const char *getUsername()
{
    const char *name = getenv("USER");
    if (name) return name;
    else return "none";
}

// 获取主机名
const char *getHostname()
{
    const char *name = getenv("HOSTNAME");
    if (name) return name;
    else return "none";
}

// 获取当前路径
const char *getCwd()
{
    const char *cwd = getenv("PWD");
    if (cwd) return cwd;
    else return "none";
}

// 获取家目录
char *homepath()
{
    char *home = getenv("HOME");
    if (home) return home;
    else return (char*)".";
}

// 获取用户命令字符串
int getUserCommand(char *command, int num)
{

    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin); // 无论你输入了什么,最终我们还是会传入\n,为usercommand的最后一个有效字符
    if (r == NULL) return -1;    // 出错了

    command[strlen(command) - 1] = '\0';    // 去掉最终输入的\n(不会越界,因为usercommand中至少有一个\n)
    return strlen(command);
}

// 打散字符串
void commandSplit(char *in, char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while(out[argc++] = strtok(NULL, SEP));

    // 测试
#ifdef Debug
    for(int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

// 执行一个命令
int execute(char *argv[])
{
    pid_t id = fork();
    if (id < 0) return -1;
    else if (id == 0)
    {
        // child
        int fd = 0;
        if (redir == InputRedir)
        {
            fd = open(filename, O_RDONLY);    // 差错处理暂时不做
            dup2(fd, 0);
        }
        else if (redir == OutPutRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if (redir == AppendRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        }

        // 程序替换不会影响重定向
        execvp(argv[0], argv);
        // 走到下面说明替换出错了
        exit(1);
    }
    else
    {
        // father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
        }
    }

    return 0;
}

void cd(const char *path)
{
    chdir(path);

    char tmp[1024];
    getcwd(tmp, sizeof(tmp));
    sprintf(cwd, "PWD=%s", tmp);
    putenv(cwd);    // 由于cwd中的字符串开头是PWD,所以会直接覆盖环境变量PWD中的内容
}

// 什么是内建命令?内建命令就是必须由bash自己执行的命令,类似于自己内部的一个函数
// 检查是否是内建命令,是内建命令就顺便执行一下,返回1,不是就返回0
int doBuildin(char *argv[])
{
    if (strcmp(argv[0], "cd") == 0)
    {
        char *path = NULL;
        if (argv[1] == NULL) path = homepath();    // 如果cd后内容为空,默认切换至家目录
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if (strcmp(argv[0], "export") == 0)
    {
        if (argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);
        putenv(enval);
        return 1;
    }
    else if (strcmp(argv[0], "echo") == 0)
    {
        if (argv[1] == NULL)
        {
            printf("\n");
            return 1;
        }
        else if (*(argv[1]) == '$' && strlen(argv[1]) > 1)
        {
            char *val = argv[1] + 1;    // 跳过$,指向$后的字符串
            if (strcmp(val, "?") == 0)
            {
                printf("%d\n", lastcode);
                lastcode = 0;
            }
            else
            {
                const char *enval = getenv(val);
                if (enval) printf("%s\n", getenv(val));
                else printf("\n");
            }
            return 1;
        }
        else
        {
            printf("%s\n", argv[1]);
        }
        return 1;
    }
    else if (0)
    {
        // 穷举其他内建命令,不再写了
    }

    return 0;
}

#define SkipSpace(filename) do{ while(isspace(*filename)) filename++; }while(0)

void checkRedir(char usercommand[], int len)
{
    char *end = usercommand + len - 1;
    char *start = usercommand;
    while(end > start)
    {
        if (*end == '>')
        {
            if (*(end - 1) == '>')
            {
                redir = AppendRedir;
                *(end - 1) = '\0';
                filename = end + 1;
                SkipSpace(filename);
                break;
            }
            else
            {
                redir = OutPutRedir;
                *end = '\0';
                filename = end + 1;
                SkipSpace(filename);
                break;
            }
        }
        else if (*end == '<')
        {
            *end = '\0';
            filename = end + 1;
            SkipSpace(filename);    // 如果有空格就跳过
            redir = InputRedir;
            break;
        }
        else
        {
            end--;
        }
    }
}

int main()
{
    while(1)
    {
        // 每次循环对这两个重定向指标赋初始值
        redir = NoneRedir;
        filename = NULL;

        char usercommand[NUM];      // 用户输入的命令字符串
        char *argv[SIZE];           // 命令行参数表
        // 1. 打印提示符+获取用户命令字符串
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if (n <= 0) continue;   // 如果getUserCommand函数出错了或者得到的字符串是空串,就不要往后走了
        // 1.1 检查是否发生重定向
        // "ls -a -l > log.txt" -> "ls -a -l" redir_type "log.txt" 
        checkRedir(usercommand, strlen(usercommand));

        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);

        // 3. check build-in command,先检查是否是内建命令
        n = doBuildin(argv);
        if(n) continue;

        // 4. 创建子进程,执行对应的命令
        execute(argv);
    }    

    return 0;
}

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-指短琴长-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值