【Linux笔记】Linux自定义Shell

一、自定义shell

1.1 myshell.h

复制代码
#ifndef __MYSHELL_H__
#define __MYSHELL_H__
​
//在所有系统头文件之前定义,暴露 getenv/setenv 等的正确声明
#define _DEFAULT_SOURCE
​
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <ctype.h>
​
//命令行长度
#define COMMAND_SIZE 1024
​
//命令行格式
#define FORMAT "[%s@%s %s]# "
​
//定义字符缓冲区,用于获取真实的目录路径
#define MaxBuf_SIZE 1024
​
//获取分割字符  ---命令行参数表
#define MAXARGV_SIZE 128
​
//定义重定向符号的宏
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
​
//记录重定向文件
#define MAXFN_SIZE 1024
​
​
//全局变量声明
extern char buf[MaxBuf_SIZE];
extern char* g_argv[MAXARGV_SIZE];
extern int g_argc;
extern int g_redir;
extern char filename[MAXFN_SIZE];
​
​
//函数声明
​
//获取用户名
const char* GetUserName();
//获取主机名
const char* GetHostName();
//获取当前目录的路径
//注:getenv("PWD")读取的是进程启动时从父进程继承的环境变量。它只是一份静态的快照,不会因为你调用了 chdir() 而自动更新。
const char* GetPwd();
//只显示当前用户路径,不显示全路径
const char* DirName(const char* route);
//将命令行提示词输出到 字符串cmd_prompt
void MakeCommandLine(char cmd_prompt[], int size);
//打印命令行提示词
void PrintCommandPrompt();
//获取用户输入的命令
int GetCommandLine(char *out,int size);
//分割命令行参数 "ls -l -a"  -> "ls" "-l" "-a"
int ParseCommandLine(char* commandline);
//判定是否出现重定向操作
void RedirCheck(char* commandline);
//测试字符串重定向符号和filename是否被正确分割
void TestRedirCheck();
//测试字符串分割
void TestCmd();
//cd命令的执行
void Execd();
//判断内键命令,例如:"cd"
int CheckAndExecBuiltin();
//执行命令
int Execute();
​
#endif
​

1.2 myshell.c

复制代码
#include "myshell.h"

// 命令分割的分隔符(实现内部使用,不对外暴露)
#define SEP " "

// 全局变量定义
char buf[MaxBuf_SIZE];
char *g_argv[MAXARGV_SIZE];
// 定义分隔符的数量
int g_argc;
// 判定是否出现重定向操作
int g_redir = NONE_REDIR;
char filename[MAXFN_SIZE];

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

// 获取主机名
const char *GetHostName()
{
    static char hostname[128];
    if (gethostname(hostname, sizeof(hostname)) != 0)
        return "None";
    hostname[sizeof(hostname) - 1] = '\0';
    return hostname;
}

// 获取当前目录的路径
// getenv("PWD")读取的是进程启动时从父进程继承的环境变量。它只是一份静态的快照,不会因为你调用了 chdir() 而自动更新。
const char *GetPwd()
{
    // const char* pwd=getenv("PWD");
    const char *pwd = getcwd(buf, sizeof(buf));
    return pwd == NULL ? "None" : pwd;
}

// 只显示当前用户路径,不显示全路径
const char *DirName(const char *route)
{
    if (!route || *route == '\0')
        return ".";

    const char *slash = strrchr(route, '/');
    if (slash == route && *(slash + 1) == '\0')
        return "/";
    return slash ? slash + 1 : route;
}

// 将命令行提示词输出到 字符串cmd_prompt
void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()));
}

// 打印命令行提示词
void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}

// 获取用户输入的命令
int GetCommandLine(char *out, int size)
{
    // 获取用户输入的指令
    char *str = fgets(out, size, stdin);
    if (str == NULL)
        return 0;
    // 清理\n
    str[strlen(str) - 1] = '\0';
    return 1;
}

// 分割命令行参数 "ls -l -a"  ->  "ls" "-l" "-a"
int ParseCommandLine(char *commandline)
{
    g_argc = 0;
    char *token = strtok(commandline, SEP);
    while (token != NULL && g_argc < MAXARGV_SIZE)
    {
        g_argv[g_argc++] = token;
        token = strtok(NULL, SEP);
    }
    g_argv[g_argc] = NULL;
    return g_argc > 0 ? 1 : 0;
}

// 判定是否出现重定向操作
void RedirCheck(char *commandline)
{
    // 初始化操作
    g_redir = NONE_REDIR;
    filename[0] = '\0';

    char *p;          // 遍历字符串的指针
    char *name_start; // 文件名的起始位置
    int name_len;

    if (commandline == NULL)
    {
        return;
    }

    p = commandline;
    while (*p != '\0')
    {
        // 处理单引号' 和 双引号 "
        if (*p == '"' || *p == '\'')
        {
            // 记录引号的类型
            char quote = *p;
            // 寻找闭合的引号,防止出现>在输入的字符串中: echo "hello > world"
            p++;
            while (*p != '\0' && *p != quote)
            {
                p++;
            }
            // 找到闭合引号,进行寻找 重定向符号 > 、 >> 和 <
            if (*p != '\0')
                p++;
            continue;
        }
        if (*p == '<')
        {
            g_redir = INPUT_REDIR;
            *p = '\0';
            p++;
            break;
        }
        if (*p == '>')
        {
            *p = '\0'; // 截断原字符串 commandline

            // 在输入重定向>的基础上,进一步检测是否为追加重定向 >>
            p++;
            if (*p == '>')
            {
                g_redir = APPEND_REDIR;
                p++;
            }
            else
            {
                g_redir = OUTPUT_REDIR;
            }
            break;
        }

        p++;
    }

    // 如果没有找到重定向符号,直接提前返回
    if (g_redir == NONE_REDIR)
    {
        return;
    }

    // 清除空格
    // echo "hello" > filename
    while (*p != '\0' && isspace((unsigned char)*p))
    {
        p++;
    }

    if (*p == '\0')
    {
        fprintf(stderr, "语法错误: 重定向符号后缺少文件名\n");
        g_redir = NONE_REDIR;
        return;
    }

    // 获取文件名
    // 向后扫描,直到遇到空格或字符串结尾,确定文件名的结束位置
    name_start = p;
    while (*p != '\0' && !isspace((unsigned char)*p))
    {
        p++;
    }

    // 文件名的长度
    name_len = (int)(p - name_start);
    // 如果文件名的长度超过了缓冲区的长度,则拷贝字符数最多为filename长度-1,需要留一个字符'\0'
    if (name_len >= (int)sizeof(filename))
    {
        name_len = (int)sizeof(filename) - 1;
    }
    // 拷贝到filename中
    strncpy(filename, name_start, name_len);
    filename[name_len] = '\0';

    *name_start = '\0';
}

// 测试字符串重定向符号和filename是否被正确分割
void TestRedirCheck()
{
    printf("g_redir: %d,filename: %s\n", g_redir, filename);
}

// 测试字符串分割
void TestCmd()
{
    for (int i = 0; i < g_argc; i++)
    {
        printf("g_argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("g_argc:%d\n", g_argc);
}

// cd命令的执行
void Execd()
{
    const char *path = g_argv[1];
    if (path == NULL) // 1.执行cd命令
    {
        path = getenv("HOME");
        if (path == NULL)
        {
            fprintf(stderr, "cd: HOME 环境变量未设置\n");
            return;
        }
    }
    else if (strcmp(path, "-") == 0) // 2.执行cd - 命令
    {
        path = getenv("OLDPWD");
        if (path == NULL)
        {
            fprintf(stderr, "cd: OLDPWD 未设置\n");
            return;
        }
        // 回显当前路径
        printf("%s\n", path);
    }

    // 3.执行 cd <path> 命令

    // 记录旧目录
    char oldpwd[4096];
    if (getcwd(oldpwd, sizeof(oldpwd)) == NULL)
    {
        oldpwd[0] = '\0';
    }

    // 更新当前目录路径
    if (chdir(path) != 0)
    {
        perror("cd");
        return;
    }

    // 更新环境变量
    char newpwd[4096];
    if (oldpwd[0] != '\0')
    {
        setenv("OLDPWD", oldpwd, 1);
    }
    if (getcwd(newpwd, sizeof(newpwd)) != NULL)
    {
        setenv("PWD", newpwd, 1);
    }
}

// 判断内键命令,例如:"cd"
int CheckAndExecBuiltin()
{
    const char *cmd = g_argv[0];
    if (cmd == NULL)
        return 0;
    if (strcmp(cmd, "cd") == 0)
    {
        Execd();
        return 1;
    }
    return 0;
}

// 执行命令
int Execute()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork fail");
        exit(1);
    }
    else if (id == 0)
    {
        int fd = -1;
        if (g_redir == INPUT_REDIR)
        {
            fd = open(filename, O_RDONLY, 0666);
            if (fd < 0)
                exit(1);
            dup2(fd, 0);
            close(fd);
        }
        else if (g_redir == OUTPUT_REDIR)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
            if (fd < 0)
                exit(2);
            dup2(fd, 1);
            close(fd);
        }
        else if (g_redir == APPEND_REDIR)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
            if (fd < 0)
                exit(3);
            dup2(fd, 1);
            close(fd);
        }

        // 子进程进行程序替换
        execvp(g_argv[0], g_argv);

        // 执行失败打印提示词
        perror(g_argv[0]);
        exit(127);
    }
    else
    {
        // 父进程回收子进程pid
        pid_t rid = waitpid(id, NULL, 0);
        if (rid < 0)
            perror("waitpid fail");
    }
    return 0;
}

1.3 main.c

复制代码
#include "myshell.h"

int main()
{

    while (1)
    {
        // 1.打印命令行提示词
        PrintCommandPrompt();

        // 2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if (!GetCommandLine(commandline, sizeof(commandline)))
            continue;

        // 3.判定是否出现重定向操作,出现了进行对输入的命令行进行分割
        RedirCheck(commandline);
        // TestRedirCheck();

        // 4.命令行分析,分割字符串,将分割的字符串存放在全局变量g_argv
        //"ls -l -a"  -> "ls" "-l" "-a"
        if (!ParseCommandLine(commandline))
            continue;

        // TestCmd();

        //.执行内建命令
        if (CheckAndExecBuiltin())
            continue;

        // 子进程执行用户输入的命令
        Execute();
    }
    return 0;
}
相关推荐
姜鱼问生1 小时前
HTTPS 部署实战:未备案域名 + 非标端口的完整方案
linux·https
悠仁さん1 小时前
【C++】vector 类
开发语言·c++
jackletter1 小时前
linux:安装 ubuntu 26.04.1
linux·ubuntu·ubuntu26.04.1
_upupup1 小时前
智能指针的原理及简单模拟实现(C++)
开发语言·c++
l1t1 小时前
DeepSeek总结的OpenZL压缩变换器
c++·算法
2401_869769591 小时前
make 进度条
linux
郝学胜-神的一滴2 小时前
C++ Templates 01:拨开迷雾,读懂C++模板经典著作
开发语言·c++·程序人生·游戏·游戏引擎·软件构建
坤坤子吖2 小时前
算法学习——高精度加减乘除
c++·笔记·学习·算法
朝朝辞暮i2 小时前
C++ 第 14 课:引用 &
开发语言·c++·算法