简易shell

目录

一、整体功能概述

函数准备

1.env命令

2.getenv()函数

3.snprintf

4.strtok()函数

三、全局变量

四、核心功能函数解析

[1. 信息获取函数](#1. 信息获取函数)

[2. 命令行交互](#2. 命令行交互)

[3. 命令解析](#3. 命令解析)

[4. 普通命令执行](#4. 普通命令执行)

[5. 内置命令处理(核心功能)](#5. 内置命令处理(核心功能))

五、主函数流程

六、总代码


一、整体功能概述

这是一个增强版的命令行解释器,具有以下功能:

  • 显示彩色命令行提示符
  • 支持内置命令(cd、export、echo)
  • 支持环境变量操作
  • 记录上一条命令的退出状态
  • 为 ls 命令自动添加颜色选项

函数准备

1.env命令

输出对应的环境变量

2.getenv()函数

这是一个获取操作系统环境变量的函数

所需参数:

cpp 复制代码
#include<stdlib.h>
char *getenv(const char *name);//获取当前进程环境变量值

代码:

结果:

3.snprintf

snprintf 是一个安全版本的字符串格式化函数 ,用于将格式化的数据写入字符串缓冲区,同时防止缓冲区溢出。

函数参数:

cpp 复制代码
int snprintf(char *str, size_t size, const char *format, ...);

参数说明:

str:目标字符串缓冲区

size:缓冲区大小(包括结尾的 null 字符)

format:格式化字符串

...:可变参数列表

返回值:

成功:返回想要写入的字符数(不包括结尾的 null)

失败:返回负值

例子:

cpp 复制代码
char buffer[50];
int n = snprintf(buffer, sizeof(buffer), "Hello, %s!", "World");
// buffer = "Hello, World!"
// n = 13 (实际写入的字符数)

4.strtok()函数

strtok 是 C 标准库中的一个字符串分割函数,用于将字符串按指定的分隔符拆分为多个子串。该函数属于<string.h>头文件,常用于解析字符串数据

参数说明:

str:要分割的字符串(第一次调用时传入,后续传入 NULL)

delim:分隔符字符串(包含所有可能的分隔字符)

返回值:

成功:返回下一个令牌的指针

失败/结束:返回 NULL

例子:

cpp 复制代码
char str[] = "apple,banana,cherry";
char *token = strtok(str, ",");

while (token != NULL) {
    printf("Token: %s\n", token);
    token = strtok(NULL, ",");
}

结果:

cpp 复制代码
Token: apple
Token: banana
Token: cherry

二、头文件和宏定义

cpp 复制代码
#include <stdio.h>       // 标准输入输出
#include <stdlib.h>      // 标准库函数
#include <string.h>      // 字符串处理
#include <assert.h>      // 断言
#include <unistd.h>      // Unix标准函数
#include <sys/types.h>   // 系统类型
#include <sys/wait.h>    // 进程等待

#define LEFT "["         // 提示符左括号
#define RIGHT "]"        // 提示符右括号
#define LABLE "#"        // 提示符标签
#define DELIM " \t"      // 命令分隔符(空格和制表符)
#define LINE_SIZE 1024   // 命令行缓冲区大小
#define ARGC_SIZE 32     // 参数数组大小
#define EXIT_CODE 44     // 子进程退出码

三、全局变量

cpp 复制代码
int lastcode = 0;        // 上一条命令的退出码
int quit = 0;            // 退出标志
extern char **environ;   // 系统环境变量
char commandline[LINE_SIZE]; // 命令行输入缓冲区
char *argv[ARGC_SIZE];   // 参数数组
char pwd[LINE_SIZE];     // 当前工作目录缓冲区

char myenv[LINE_SIZE];   // 自定义环境变量存储

四、核心功能函数解析

1. 信息获取函数

cpp 复制代码
const char *getusername() // 获取用户名
{
    return getenv("USER"); // 从环境变量获取
}

const char *gethostname() // 获取主机名
{
    return getenv("HOSTNAME"); // 从环境变量获取
}

void getpwd() // 获取当前工作目录
{
    getcwd(pwd, sizeof(pwd)); // 系统调用获取当前目录
}

2. 命令行交互

cpp 复制代码
void interact(char *cline, int size)
{
    getpwd(); // 更新当前目录
    // 显示提示符:[user@host directory]#
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ", 
           getusername(), gethostname(), pwd);
    
    char *s = fgets(cline, size, stdin); // 读取用户输入
    assert(s); // 确保读取成功
    cline[strlen(cline)-1] = '\0'; // 去掉换行符
}

3. 命令解析

cpp 复制代码
int splitstring(char cline[], char *_argv[])
{
    int i = 0;
    argv[i++] = strtok(cline, DELIM); // 分割第一个参数
    while(_argv[i++] = strtok(NULL, DELIM)); // 继续分割剩余参数
    return i - 1; // 返回参数个数
}

4. 普通命令执行

cpp 复制代码
void NormalExcute(char *_argv[])
{
    pid_t id = fork(); // 创建子进程
    if(id < 0){
        perror("fork");
        return;
    }
    else if(id == 0){
        // 子进程执行命令
        execvp(_argv[0], _argv); // 自动搜索PATH
        exit(EXIT_CODE); // 执行失败退出
    }
    else{
        // 父进程等待子进程
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid == id) {
            lastcode = WEXITSTATUS(status); // 记录退出码
        }
    }
}

5. 内置命令处理(核心功能)

cpp 复制代码
int buildCommand(char *_argv[], int _argc)
{
    // cd 命令:切换目录
    if(_argc == 2 && strcmp(_argv[0], "cd") == 0){
        chdir(argv[1]); // 切换目录
        getpwd(); // 更新当前目录
        sprintf(getenv("PWD"), "%s", pwd); // 更新PWD环境变量
        return 1; // 表示是内置命令,不需要执行NormalExcute
    }
    
    // export 命令:设置环境变量
    else if(_argc == 2 && strcmp(_argv[0], "export") == 0){
        strcpy(myenv, _argv[1]); // 复制到全局变量
        putenv(myenv); // 设置环境变量
        return 1;
    }
    
    // echo 命令:输出内容
    else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){
        if(strcmp(_argv[1], "$?") == 0) {
            // 输出上一条命令的退出码
            printf("%d\n", lastcode);
            lastcode = 0;
        }
        else if(*_argv[1] == '$'){
            // 输出环境变量值
            char *val = getenv(_argv[1]+1);
            if(val) printf("%s\n", val);
        }
        else{
            // 直接输出字符串
            printf("%s\n", _argv[1]);
        }
        return 1;
    }

    // 为 ls 命令自动添加颜色选项
    if(strcmp(_argv[0], "ls") == 0) {
        _argv[_argc++] = "--color"; // 添加颜色参数
        _argv[_argc] = NULL; // 保持数组以NULL结束
    }
    
    return 0; // 不是内置命令,需要执行NormalExcute
}

五、主函数流程

cpp 复制代码
int main()
{
    while(!quit){ // 主循环
        // 1. 显示提示符并获取命令
        interact(commandline, sizeof(commandline));
        
        // 2. 解析命令
        int argc = splitstring(commandline, argv);
        if(argc == 0) continue; // 空命令跳过
        
        // 3. 调试输出(注释状态)
        // for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);
        
        // 4. 处理内置命令
        int n = buildCommand(argv, argc);
        
        // 5. 注释中描述了管道功能的实现思路
        // 这里预留了管道功能的框架
        
        // 6. 执行普通命令
        if(!n) NormalExcute(argv);
    }
    return 0;
}

六、总代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44

int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];

// 自定义环境变量表
char myenv[LINE_SIZE];
// 自定义本地变量表


const char *getusername()
{
    return getenv("USER");
}

const char *gethostname()
{
    return getenv("HOSTNAME");
}

void getpwd()
{
    getcwd(pwd, sizeof(pwd));
}

void interact(char *cline, int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);
    char *s = fgets(cline, size, stdin);
    assert(s);
    (void)s;
    // "abcd\n\0"
    cline[strlen(cline)-1] = '\0';
}

// ls -a -l | wc -l | head 
int splitstring(char cline[], char *_argv[])
{
    int i = 0;
    argv[i++] = strtok(cline, DELIM);
    while(_argv[i++] = strtok(NULL, DELIM)); // 故意写的=
    return i - 1;
}

void NormalExcute(char *_argv[])
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return;
    }
    else if(id == 0){
        //让子进程执行命令
        //execvpe(_argv[0], _argv, environ);
        execvp(_argv[0], _argv);
        exit(EXIT_CODE);
    }
    else{
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid == id) 
        {
            lastcode = WEXITSTATUS(status);
        }
    }
}

int buildCommand(char *_argv[], int _argc)
{
    if(_argc == 2 && strcmp(_argv[0], "cd") == 0){
        chdir(argv[1]);
        getpwd();
        sprintf(getenv("PWD"), "%s", pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0], "export") == 0){
        strcpy(myenv, _argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){
        if(strcmp(_argv[1], "$?") == 0)
        {
            printf("%d\n", lastcode);
            lastcode=0;
        }
        else if(*_argv[1] == '$'){
            char *val = getenv(_argv[1]+1);
            if(val) printf("%s\n", val);
        }
        else{
            printf("%s\n", _argv[1]);
        }

        return 1;
    }

    // 特殊处理一下ls
    if(strcmp(_argv[0], "ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}

int main()
{
    while(!quit){
        // 1.
        // 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txt
        interact(commandline, sizeof(commandline));

        // commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"
        // 3. 子串分割的问题,解析命令行
        int argc = splitstring(commandline, argv);
        if(argc == 0) continue;

        // 4. 指令的判断 
        // debug
        //for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);
        //内键命令,本质就是一个shell内部的一个函数
        int n = buildCommand(argv, argc);

        // ls -a -l | wc -l
        // 4.0 分析输入的命令行字符串,获取有多少个|, 命令打散多个子命令字符串
        // 4.1 malloc申请空间,pipe先申请多个管道
        // 4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始. 输出重定向, 1->指定的一个管道的写端 
        // 中间:输入输出重定向, 0标准输入重定向到上一个管道的读端 1标准输出重定向到下一个管道的写端
        // 最后一个:输入重定向,将标准输入重定向到最后一个管道的读端
        // 4.3 分别让不同的子进程执行不同的命令--- exec* --- exec*不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向
        // 5. 普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}
相关推荐
四时久成14 分钟前
服务器认证系统
运维·服务器
iFulling16 分钟前
【云原生】CentOS安装Kubernetes+Jenkins
linux·云原生·kubernetes·centos·jenkins
徐子元竟然被占了!!20 分钟前
Windows Server 2019 DateCenter搭建 FTP 服务器
运维·服务器·windows
-L721 分钟前
进入docker中mysql容器的方法
运维·mysql·docker·容器
wayuncn2 小时前
影响服务器托管费用的因素
运维·服务器·数据中心·服务器托管·物理服务器租用·服务器机柜·idc机房托管
喜欢你,还有大家2 小时前
Linux笔记10——shell编程基础-4
linux·运维·服务器·笔记
玩转以太网2 小时前
基于 W55MH32Q-EVB 实现 FatFs 文件系统+FTP 服务器
服务器·单片机·物联网
不懂机器人2 小时前
linux编程----网络通信(TCP)
linux·服务器·tcp/ip
司徒轩宇2 小时前
Python secrets模块:安全随机数生成的最佳实践
运维·python·安全
wjm05192 小时前
jenkins使用publishover ssh 进行远程连接
运维·ssh·jenkins