Linux Shell开发实战:从零打造命令行工具

前言:

通过我们之前对Linux的学习,今天来利用所学知识做出来一个可执行程序(一个简单的shell)。

一.程序所需要用到的工具

1.getcmd:返回当前工作目录的路径。

复制代码
char *getcwd(char *buf, size_t size);

他的两个参数分别是所存储的空间以及buf的空间大小。

2.snprintf

int snprintf(char *str, size_t size, const char *format, ...);

char *str:指向目标缓冲区的指针,用于存储生成的字符串。

size_t size最关键的参数 。指定缓冲区的大小(能存放的最大字符数,包括结尾的 \0)。函数最多会写入 size - 1 个字符,然后在末尾自动添加 \0const char *format :格式化字符串(和 printf 用法完全一样,如 "Hello %s")。

...:可变参数,要格式化的变量。

主要的作用是将想要在屏幕上打出的内容放到str中,str为自己常见的一个数组。size为这个数组的大小。

3.std::string::npos用于检查在一个字符串中是否没有找到指定的字符或子串一般与rfind搭配使用,如果rfind没有找到则会返回npos。

4.find和rfind

findrfind 都是 C++ std::string 中用于查找的成员函数,主要区别在于查找方向返回结果

主要区别

特性 find() rfind()
查找方向 从左到右(正向) 从右到左(反向)
返回位置 第一次出现的位置 最后一次出现的位置
默认起始位置 从索引0开始 从字符串末尾开始

5.substr

复制代码
string substr(size_t pos = 0, size_t count = npos) const;

substr 是 C++ std::string 中的一个成员函数 ,用于提取字符串中的子串

  • 参数1 pos:起始位置(默认从0开始)

  • 参数2 count:要提取的字符数(默认到字符串末尾)

  • 返回值:提取出的新字符串

6.fgets()

char *fgets(char *str, int n, FILE *stream);

fgets 是 C 语言标准库中的一个文件输入函数 ,用于从文件流中读取一行字符串

  • str:存储读取内容的字符数组(缓冲区)

  • n :最多读取的字符数(包括最后的 \0

  • stream :文件流指针(如 stdin 或打开的文件)

7.strtuk

复制代码
char *strtok(char *str, const char *delim);
  • str:要分割的字符串(第一次调用)(当多次分割同一个字符串时第二次之后传的参数为nullptr)

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

  • 返回值 :指向当前找到的令牌的指针,如果没有更多令牌则返回 NULL

8.waitpid

复制代码
#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
  • pid:指定要等待的进程

    • > 0:等待指定 PID 的子进程

    • -1:等待任意子进程(同 wait

    • 0:等待与调用进程同一进程组的任意子进程

    • < -1:等待进程组 ID 为 |pid| 的任意子进程

  • status :存储子进程退出状态的指针(可为 NULL

  • options :控制等待行为的选项(常用:WNOHANGWUNTRACED)(一般阻塞等待时传0即可,

    复制代码
    WNOHANG为非阻塞时等待)
作用 使用条件
WIFEXITED(status) 检查是否正常退出 总是先调用
WEXITSTATUS(status) 获取退出码 WIFEXITED 为真时

返回值

  • 成功:返回状态改变的子进程 PID

  • WNOHANG 且无子进程退出 :返回 0

  • 出错 :返回 -1

9.chdir

复制代码
#include <unistd.h>

int chdir(const char *path);

chdir 是 UNIX/Linux 系统中用于改变当前工作目录的系统调用。

参数说明

  • path:要切换到的目录路径(绝对路径或相对路径)

返回值

  • 成功 :返回 0

  • 失败 :返回 -1,并设置 errno 错误码

10.environ

environ 是一个全局变量 ,用于访问进程的环境变量 。它是一个字符串数组,每个字符串都是 "key=value" 格式,以 NULL 结尾。

二.正文代码

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
const int basesize=1024;
const int garsum=64;
const int envnum=64;
char *gargv[garsum];
int gargc=0;
int lastcode=0;
char*genv[envnum];
char* pwd[basesize];
char* pwdnev[basesize];
string GetUserName()
{
    string Username=getenv("USER");
    return Username.empty()?"done":Username;
}

string HostName()
{
    string Hostname=getenv("HOSTNAME");
    return Hostname.empty()?"none":Hostname;
}
string Getpwd()
{    

   // if(nullptr==getcmd(pwd,sizeof(pwd)))return "Done";

    string Mypwd=getenv("PWD");
    return Mypwd.empty()?"done":Mypwd;
}
string Lastdir()
{
    string str=Getpwd();
    if(str=="/"||str=="done")return str;
    size_t jud=str.rfind("/");
    if(jud==std::string::npos)return str;
    return str.substr(jud+1);
}
string Makecommandline()
{
    char comandline[basesize];
    snprintf(comandline,sizeof(comandline)-1,"[%s@%s %s]#",GetUserName().c_str(),HostName().c_str(),Lastdir().c_str());
    return comandline;
}
void  PriontComLine()
{
    printf("%s",Makecommandline().c_str());
    fflush(stdout);
}
bool GetcommanLine(char buffer[],int size)
{
   char*pos= fgets(buffer,size,stdin);
    if(!pos)
    {
        perror("fgets");
        return false;
    }
    buffer[strlen(buffer)-1]=0;
    if(strlen(buffer)==0)return false;
    return true;
}

void AnalyCommanLine(char command_buffer[],int size)
{
    (void)size;
    memset(gargv,0,sizeof(gargv));
    gargc=0;
    const char* flag=" ";
    gargv[gargc++]=strtok(command_buffer,flag);
    while ((bool)( gargv[gargc++]=strtok(nullptr,flag)));
    gargc--;
}
void debug()
{
    int i=0;
    for( i=0;gargv[i];i++)
    {
        printf("%s\n",gargv[i]);
    }
}
bool ExecuteCommand()
{
    pid_t id=fork();
    if(id<0)
    {
        return false;
    }
    if(id==0)
    {
        //子进程
        execvp(gargv[0],gargv);
        exit(1);
    }
    int statue=0;
    pid_t rid=waitpid(id,&statue,0);
    if(rid>0)
    {
        //等待成功
        if(WIFEXITED(statue))
        {
            lastcode=WEXITSTATUS(statue);
        }
        else{
            lastcode=100;
        }
        return true;
    }
    return false;
}
void Addenv(const char * item)
{
    int index=0;
    while(genv[index])
    {
        index++;
    }
    genv[index]=(char*)malloc(strlen(item)+1);
    strncpy(genv[index],item,strlen(item)+1);
    genv[++index]=nullptr;
}
bool CheckCommand()
{
    if(strcmp(gargv[0],"cd")==0)
    {
        if(gargc==2)
        {
            chdir(gargv[1]);
            lastcode=0;
        }
        else{
            lastcode=1;
        }
        return true;
    }
    else if(strcmp(gargv[0],"export")==0)
    {
        if(gargc==2)
        {
            Addenv(gargv[1]);
            lastcode=0;
        }
        else{
            lastcode=1;
        }
        return true;
    }
    else if(strcmp(gargv[0],"env")==0)
    {
        int index=0;
        printf("%s\n",genv[index]);
        while(genv[index])
        {
            printf("%s\n",genv[index]);
            index++;
        }
        lastcode=0;
        return true;
    }
    return false;
}
void InintEnv()
{
    extern char**environ;
    int index=0;
    while(environ[index])
    {
        genv[index]=(char*)malloc(strlen(environ[index])+1);
        strncpy(genv[index],environ[index],strlen(environ[index])+1);
        index++;
    }
    genv[index]=nullptr;
}
int main()
{
    InintEnv();
    char command_buffer[basesize];
    while(true)
    {
        //打印命令行
        PriontComLine();
        //获取用户指令
        if(!GetcommanLine(command_buffer,basesize))
        {
            continue;
        }
        AnalyCommanLine(command_buffer,strlen(command_buffer));
        //debug();
        if(CheckCommand())
        {
            continue;
        }
        ExecuteCommand(); 
    }
    return 0;
}
相关推荐
鹓于2 小时前
OmniParser视觉鼠标自动化实战
运维·自动化·计算机外设
!沧海@一粟!2 小时前
Esxi主机iDrac密码与IP重置指南
运维·服务器
低保和光头哪个先来2 小时前
TinyEditor 篇2:剪贴板粘贴图片并同步上传至服务器
服务器·前端·javascript·css·vue.js
顺风尿一寸2 小时前
从 Java File.length() 到 Linux 内核:一次系统调用追踪之旅
java·linux
原来是猿2 小时前
Linux - 基础IO【中】
linux·运维·服务器
Xzq2105092 小时前
网络编程套接字(UDP)
运维·服务器·网络
主角1 72 小时前
Linux系统安全
linux·运维·系统安全
翼龙云_cloud2 小时前
阿里云代理商:阿里云百炼视频混剪实战
服务器·阿里云·云计算
网硕互联的小客服2 小时前
CentOS 7 实现自动备份数据到百度网盘的具体步骤与方法
运维·服务器·网络·安全·自动化