Bootstrap

我与Linux的爱恋:自主Shell


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🔥个人主页guoguoqiang. 🔥专栏Linux的学习

Alt

自主Shell实现

根据之前所学的内容,模拟实现一个Shell实现,一个shell程序需要循环做一下事情:
1.获取命令行
2.解析命令行
3.创建子程序
4.替换子程序
5.父进程等待子进程退出

获取基本变量

在这里插入图片描述

我们可以在env查看我们需要的变量,以及使用getenv()获取我们需要的变量

//获取用户名
const char* GetUserName(){
  const char* name = getenv("USER");
  if(name == NULL) return "None";
  return name;
}
 
const char* GetHome(){
  const char* home = getenv("HOME");
  if(home == NULL) return "/";
  return home;
}
 
const char* GetHostName(){
  const char* hostname = getenv("HOSTNAME");
  if(hostname == NULL) return "None";
  return hostname;
}
 
const char* GetCwd(){
  const char* cwd = getenv("PWD");
  if(cwd == NULL) return "None";
  return cwd;
}

实现命令行

我们要把env获取的内容拼接成一个字符串

#define SIZE 512
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)
 
void MakeCommandLineAndPrint(){
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();
 
  //只保留相对路径
  SkipPath(cwd);
  //写入指定缓冲区
  snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);
  //打印命令行
  printf("%s",line);
  fflush(stdout);
}

获取用户命令字符串

#define SIZE 512
#define ZERO '\0'
 
//获取用户指令
int GetUserCommand(char command[], size_t n){
  char* s = fgets(command, n, stdin);
  if(s == NULL) return -1;
 
  //由于fgets也会获取我们输入的回车换行符
  //为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'
  command[strlen(command)-1] = ZERO;
  return strlen(command);
}

命令行字符串分割

认识字符串分割函数
在这里插入图片描述

#define SEP " "
#define NUM 32
#define SkipSpace(cmd, pos) do{\
  while(1){\
    if(isspace(cmd[pos]))\
      pos++;\
    else break;\
  }\
}while(0)
 
char *gArgv[NUM];
 
//分割命令
void SplitCommand(char command[], size_t n){
 (void)n; 
  // "ls -a -l -n" -> "ls" "-a" "-l" "-n"
  gArgv[0] = strtok(command, SEP);
  int index = 1;
  //故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL
  //刚好让gArgv最后一个元素是NULL, 并且while判断结束
  while((gArgv[index++] = strtok(NULL,SEP)));
}

检测是否为内建命令

char *gArgv[NUM];
int lastcode = 0;
 
int CheckBuildin()
{
    int yes = 0;
    const char *enter_cmd = gArgv[0];
    if(strcmp(enter_cmd, "cd") == 0)
    {
        yes = 1;
        Cd();
    }
    else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        yes = 1;
        printf("%d\n", lastcode);
        lastcode = 0;
    }
    return yes;
}

检查是否为重定向

void CheckRedir(char cmd[])
{
    // > >> <
    // "ls -a -l -n >  myfile.txt"
    int pos = 0;
    int end = strlen(cmd);
 
    while(pos < end)
    {
        if(cmd[pos] == '>')
        {
            if(cmd[pos+1] == '>')
            {
                cmd[pos++] = 0;
                pos++;
                redir_type = App_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
            else
            {
                cmd[pos++] = 0;
                redir_type = Out_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
        }
        else if(cmd[pos] == '<')
        {
            cmd[pos++] = 0;
            redir_type = In_Redir;
            SkipSpace(cmd, pos);
            filename = cmd + pos;
        }
        else
        {
            pos++;
        }
    }
}

执行命令

void ExecuteCommand()
{
    pid_t id = fork();
    if(id < 0) Die();
    else if(id == 0)
    {
        //重定向设置
        if(filename != NULL){
            if(redir_type == In_Redir)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0);
            }
            else if(redir_type == Out_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd, 1);
            }
            else if(redir_type == App_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                dup2(fd, 1);
            }
            else
            {}
        }
 
        // child
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        // fahter
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
            if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
        }
    }
}

主函数设置

int main(){
  int quit = 0;
  while(!quit){
  //1.我们需要自己输出一个命令行
  MakeCommandLineAndPrint(); 
  
  //2.获取用户命令字符串
  char usercommand[SIZE];
  int n = GetUserCommand(usercommand, sizeof(usercommand));
  if(n <= 0) return 1;
 
  //检测是否为重定向
  CheckRedir(usercommand);
 
  // 3. 命令行字符串分割. 
  SplitCommand(usercommand, sizeof(usercommand));
  
  //4.检测是否为内建命令
  n = CheckBuildin();
  if(n) continue;
  // 5. 执行命令
  ExecuteCommand();
  }
  return 0;
}

测试

在这里插入图片描述
代码

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

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p)           \
    do                        \
    {                         \
        p += (strlen(p) - 1); \
        while (*p != '/')     \
            p--;              \
    } while (0)
#define SkipSpace(cmd, pos)        \
    do                             \
    {                              \
        while (1)                  \
        {                          \
            if (isspace(cmd[pos])) \
                pos++;             \
            else                   \
                break;             \
        }                          \
    } while (0)

// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define App_Redir 3

int redir_type = None_Redir;
char *filename = NULL;

// 为了方便,我就直接定义了
char cwd[SIZE * 2];
char *gArgv[NUM];
int lastcode = 0;

void Die()
{
    exit(1);
}

// 获取用户名
const char *GetUserName()
{
    const char *name = getenv("USER");
    if (name == NULL)
        return "None";
    return name;
}

const char *GetHome()
{
    const char *home = getenv("HOME");
    if (home == NULL)
        return "/";
    return home;
}

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

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

void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char *username = GetUserName();
    const char *hostname = GetHostName();
    const char *cwd = GetCwd();

    // 只保留相对路径
    SkipPath(cwd);
    // 写入指定缓冲区
    snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
    // 打印命令行
    printf("%s", line);
    fflush(stdout);
}

// 获取用户指令
int GetUserCommand(char command[], size_t n)
{
    char *s = fgets(command, n, stdin);
    if (s == NULL)
        return -1;

    // 由于fgets也会获取我们输入的回车换行符
    // 为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'
    command[strlen(command) - 1] = ZERO;
    return strlen(command);
}

// 分割命令
void SplitCommand(char command[], size_t n)
{
    (void)n;
    // "ls -a -l -n" -> "ls" "-a" "-l" "-n"
    gArgv[0] = strtok(command, SEP);
    int index = 1;
    // 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL
    // 刚好让gArgv最后一个元素是NULL, 并且while判断结束
    while ((gArgv[index++] = strtok(NULL, SEP)))
        ;
}

void ExecuteCommand()
{
    pid_t id = fork();
    if (id < 0)
        Die();
    else if (id == 0)
    {
        // 重定向设置
        if (filename != NULL)
        {
            if (redir_type == In_Redir)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0);
            }
        }
        else if (redir_type == Out_Redir)
        {
            int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 0);
        }
        else if (redir_type == App_Redir)
        {
            int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
        }
        // child
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        // father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            // wait success
        }
    }
}

void Cd(){
  const char* path = gArgv[1];
  if(path == NULL) path = GetHome();
  //path 一定存在
  chdir(path);
 
        //刷新环境变量
        char temp[SIZE*2];
        getcwd(temp, sizeof(temp));
        snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
        putenv(cwd); // OK 
}

int CheckBuildin()
{
    int yes = 0;
    const char *enter_cmd = gArgv[0];
    if (strcmp(enter_cmd, "cd") == 0)
    {
        yes = 1;
        Cd();
    }
    else if (strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        yes = 1;
        printf("%d\n", lastcode);
        lastcode = 0;
    }
    return yes;
}

void CheckRedir(char cmd[])
{
    // > >> <
    // "ls -a -l -n >  myfile.txt"
    int pos = 0;
    int end = strlen(cmd);

    while (pos < end)
    {
        if (cmd[pos] == '>')
        {
            // 是否为追加重定向
            if (cmd[pos + 1] == '>')
            {
                // 是追加重定向
                cmd[pos++] = 0;
                pos++;
                redir_type = App_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
            else
            {
                // 是输出重定向
                cmd[pos++] = 0;
                redir_type = Out_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
        }
        else if (cmd[pos] == '<') // 是输入重定向
        {
            cmd[pos++] = 0;
            redir_type = In_Redir;
            SkipSpace(cmd, pos);
            filename = cmd + pos;
        }
        else
        {
            pos++;
        }
    }
}

int main()
{
    int quit = 0;
    while (!quit)
    {
        // 1.我们需要自己输出一个命令行
        MakeCommandLineAndPrint();

        // 2.获取用户命令字符串
        char usercommand[SIZE];
        int n = GetUserCommand(usercommand, sizeof(usercommand));
        if (n <= 0)
            return 1;

        // 检测是否为重定向
        CheckRedir(usercommand);

        // 3. 命令行字符串分割.
        SplitCommand(usercommand, sizeof(usercommand));

        // 4.检测是否为内建命令
        n = CheckBuildin();
        if (n)
            continue;
        // 5. 执行命令
        ExecuteCommand();
    }
    return 0;
}
;