自主编写shell

前言:

​ 我们在上一章学习的进程的程序替换,这对于我们来说是一件重要的事,因为可以实现创建子进程执行别的程序进而实现进程程序的替换,那么现在我们就可以实现我们自主编写的shell,一个命令解释器了!

1、头文件以及宏还有内置参数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/errno.h>

#define SIZE 520
#define NUM 64

// 利用宏定义保留最后一个“地址”
#define SkipPath(p) do{ p += (strlen(p) - 1); while(*p != '/') p--; }while(0) 

char cwd[SIZE * 2]; // 记录绝对路径
char* aArgv[NUM]; // 记录用户输入命令,类似于main函数参数里的char* argv[]
int exit_code = 0; // 记录退出码

2、构建简单命令行

const char* GetUserName()
{
    const char* username = getenv("USER");
    if(username == NULL) return "None";
    return username;
}

const char* GetHostName()
{
    const char* hostname = getenv("HOSTNAME");
    if(hostname == NULL) return "None";
    return hostname;
}

const char* GetPwd()
{
    const char* pwd = getenv("PWD");
    if(pwd == NULL) return "None";
    return pwd;
}

void CreateCommandLine() 
{
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* pwd = GetPwd();

    char line[SIZE]; 
    SkipPath(pwd);
    snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, ((strlen(pwd) == 1) ? "/" : pwd));
    printf("%s", line);
    fflush(stdout);
}

3、获取用户命令字符串

char usercommand[SIZE];
int n = GetCommandLine(usercommand, sizeof(usercommand));
if(n <= 0) return 1;
int GetCommandLine(char command[], int n)
{
    char* s = fgets(command, n, stdin); // 用fgets可以自动省略空格
    if(s == NULL) return -1;
    command[strlen(command) - 1] = '\0';
    return strlen(command);
}

4、命令行字符串分割

void SplitCommandLine(char command[])
{
    aArgv[0] = strtok(command, " "); // 利用strtok截取空格之前的字符串
    int index = 1;
    while((aArgv[index++] = strtok(NULL, " "))) ; // 第二次用strtok直接传递NULL参数即可自动截取
}

5、检查是否为内建命令

if(CheckBuildInCommand() == 1) continue;
int CheckBuildInCommand()
{
    int flag = 0;
    if(strcmp(aArgv[0], "cd") == 0) 
    {
        flag = 1;
        Cd();
    }
    else if(strcmp(aArgv[0], "echo") == 0 && strcmp(aArgv[1], "$?") == 0)
    {
        flag = 1;
        printf("%d\n", exit_code);
        exit_code = 0;
    }
    return flag;
}

因为我们一些系统调用的接口还不清楚,所以我们暂时用这些朴素的方法来判断是否为内建命令,像cd还有echo $?就是典型的内建命令。最难理解还是cd命令,因为cd是父进程在执行,所以你要是不判断内建命令的话是不会有反应的。

但是实现这个内建命令稍微有点复杂,首先你得找出来你要去往的路径,比如cd …
那么首先需要保存这个 …路径,再通过系统调用chdir直接将父进程的路劲改变。

以上是地址的改变,接下来难以理解的是环境变量的改变》
因为当前已经改变了路径,所以利用getcwd可以得到新地址,再将新地址以环境变量的方式进行保存,例如PWD = cwd这样,所以我们需要snprintf函数:

void Cd()
{
    const char* path = aArgv[1];
    if(path == NULL) path = GetHome();
    chdir(path);
    
    // change enviroment (honestly this part is not easy to understand) 
    char temp[SIZE * 2];
    getcwd(temp, sizeof(temp));
    snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
    putenv(cwd);

}

6、执行命令

void ExecuteCommandLine()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        execvp(aArgv[0], aArgv);
        exit(errno);
    }

    //father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
         exit_code = WEXITSTATUS(status);
         if(exit_code != 0) printf("%s:%s:%d\n", aArgv[0], strerror(exit_code), exit_code);
    }
}

if(rid > 0)
{
exit_code = WEXITSTATUS(status);
if(exit_code != 0) printf(“%s:%s:%d\n”, aArgv[0], strerror(exit_code), exit_code);
}
}


执行命令就还好,因为这和我们上文讲过的进程程序替换类似,在这里我就不过多赘述。
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无双@

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

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

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

打赏作者

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

抵扣说明:

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

余额充值