我与Linux的爱恋:自主Shell


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

文章目录

自主Shell实现

根据之前所学的内容,模拟实现一个Shell实现,一个shell程序需要循环做一下事情:

1.获取命令行

2.解析命令行

3.创建子程序

4.替换子程序

5.父进程等待子进程退出

获取基本变量

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

c 复制代码
//获取用户名
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获取的内容拼接成一个字符串

c 复制代码
#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);
}

获取用户命令字符串

c 复制代码
#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);
}

命令行字符串分割

认识字符串分割函数

c 复制代码
#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)));
}

检测是否为内建命令

c 复制代码
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;
}

检查是否为重定向

c 复制代码
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++;
        }
    }
}

执行命令

c 复制代码
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);
        }
    }
}

主函数设置

c 复制代码
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;
}

测试

代码

c 复制代码
#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;
}
相关推荐
勤奋的凯尔森同学4 分钟前
webmin配置终端显示样式,模仿UbuntuDesktop终端
linux·运维·服务器·ubuntu·webmin
月光水岸New29 分钟前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山67530 分钟前
数据库基础1
数据库
我爱松子鱼34 分钟前
mysql之规则优化器RBO
数据库·mysql
丁卯4041 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
chengooooooo1 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser2 小时前
【SQL】多表查询案例
数据库·sql
Galeoto2 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
人间打气筒(Ada)2 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231112 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql