自定义实现shell/bash

这篇具有很好参考价值的文章主要介绍了自定义实现shell/bash。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

函数和进程之间的相似性

exec/exit就像call/return一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间
自定义实现shell/bash,Linux,bash,开发语言
一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

shell

有了程序替换我们可以让一个进程来调用其他进程,所以我们想如果我们在执行程序替换的时候创建一个子进程让子进程去执行,然后我们主进程(父进程)来和用户交互,我们是不是就可以实现一个简易的shell命令行解释器。

打印提示符,以及获取用户输入

自定义实现shell/bash,Linux,bash,开发语言
我们可以看到每次在运行的时候,shell都会先打印前面的一堆东西,并且光标会停留在后面等待用户输入,所以我们父进程可以先把打印以及输入这个工作搞定。
那么这一堆东西如何获取打印呢?我们可以直接写死,但是换个主机可能就不满足要求了,我们可以分析一下,前面的这一堆东西是不是可以通过环境变量来获取呢?我们可以查一下环境变量
自定义实现shell/bash,Linux,bash,开发语言

自定义实现shell/bash,Linux,bash,开发语言
我们可以发现前面是用户@主机名 然后加当前路径,所以我们可以通过环境变量来动态获取这些内容。
我们可以先来三个函数,一个用来获取用户名,一个用来获取当前路径,一个用来获取主机名。

char *getUsername()
{
    char *name = getenv("USER");
    if (name)
        return name;
    return "none";
}

char *getHostname()
{
    char *name = getenv("HOSTNAME");
    if (name)
        return name;
    return "none";
}

char *getCwd()
{
    char *cwd = getenv("PWD");
    if (cwd)
        return cwd;
    return "none";
}

然后我们可以定义一个缓冲区,用来保存用户的输入,然后把打印起那么的一堆东西和输入放到一个函数里,进行一下封装。

int getComment(char comment[], int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());

    char *ch = fgets(comment, num, stdin);
    if (ch == NULL)
        return -1;
    int n = strlen(comment);
    comment[n - 1] = '\0';
    return n - 1;
}

这里面的细节还是挺多的,不管怎么说用户一定会输入一个回车,所以我们不用担心 comment[n - 1] = ‘\0’ 会越界访问,对于返回值问题,如果用户输入失败或者没有输入了内容返回值就会是一个 <= 0的数,就没必要进行后面创建子进程等的工作,直接跳过此次循环就OK。所以我们只要传进来一个缓冲区,就可以了。这个函数就会把用户输入的内容放到缓冲区中去。

分割用户的输入

用户输入进来的诸如"ls -a -l"等等,都是一整个字符串,但是我们进程程序替换是需要将这些个子串以空格分割进行分割成若干个子串,所以我们可以把这部分在进行封装成一个函数,我们把用户输入的内容传给这个函数,让这个函数把用户输入的内容分割成若干个子串,然后传出去就可以了,分割子串我们可以用到C语言的strtok函数,还是比较简单的。

#define SEP " "
void commentSplit(char *in, char *out[])
{
    int pos = 0;
    out[pos++] = strtok(in, SEP);
    while (out[pos++] = strtok(NULL, SEP));
}

判断是否是内建命令

我们学到的命令由两部分,有一部分是普通命令也就是shell创建子进程让子进程去执行的,还有一部分就是内建命令,如(cd,export,echo等)这些命令是shell的一个函数,是需要shell自己去执行的。所以我们在创建子进程之前,需要判断一下是否是内建命令,如果是的话,就没必要创建子进程了,所以我们可以设计一个返回值来进行区分是否是内建命令。

char cwd[NUM];
char add_env[NUM][NUM];
int envi = 0;
static int lastcode = 0;

char* getHomePath()
{
    char* path = getenv("HOME");
    if(path) return path;
    return "none";
}
void cd(const char* path)
{
    char tmp[NUM];
    //改变工作目录为当前的path
    chdir(path);
    //获取当前的工作目录
    getcwd(tmp,sizeof tmp);
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}
int dobuildcom(char* argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        char* path;
        if(argv[1] == NULL) path = getHomePath();
        else if(strcmp(argv[1], "~") == 0) 
        {
            path = getHomePath();
        }else path = argv[1];

        cd(path);
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(add_env[envi],argv[1]);
        putenv(add_env[envi]);
        envi++;
        return 1;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        if(argv[0] == NULL) 
        {
            printf("\n");
            return 1;
        }
        char* tmp = argv[1];
        if(tmp[0] == '$')
        {
            tmp++;
            if(strcmp(tmp,"?") == 0) 
            {
                printf("%d\n",lastcode);
            }else
            {
                char* c = getenv(tmp);
                if(c) printf("%s\n",c);
                else printf("\n");

            }
        }
        else{
            printf("%s\n",tmp);
        }
        lastcode = 0;
        return 1;
    }

    return 0;
}

我们这里就实现了3个内建命令,这里面最值的一说的就是环境变量了,所以我们在cd或者使用export添加环境时,是不能使用临时变量的,因为环境变量拥有全局属性,如果使用局部变量的话当函数执行完之后,这个空间就会还给OS,所以我们在导环境环境变量的时候需要定义全局变量,然后把需要导的环境变量拷贝到全局变量中在进行添加环境变量。返回值是1就是内建命令,否则就不是。

执行相关的命令

我们知道了程序替换这个就比较简单了,我们只需要将分割好的子串给这个函数,然后我们有了子串数组我们那选择好程序替换函数就可以了,因为我们直接就有子串的数组,所以我们可以选择execvp这个函数。

void execute(char *argv[])
{
    pid_t id = fork();
    if (id == 0)
    {
        execvp(argv[0], argv);
        exit(0);
    }
    int status = 0;
    waitpid(id, &status, 0);
    lastcode = WEXITSTATUS(status);
}

我们在执行完一个命令后,我们有一个全局变量来记录最后一条命令执行的结果,所以我们可以获取一下退出码赋值给这个变量。

全部代码

到这里我们的自定义shell的整体逻辑差不多就结束了,最后奉上全部代码。文章来源地址https://www.toymoban.com/news/detail-860613.html

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

#define NUM 1024
#define SEP " "

extern int putenv(char* );

char cwd[NUM];
char add_env[NUM][NUM];
int envi = 0;
static int lastcode = 0;

char *getUsername()
{
    char *name = getenv("USER");
    if (name)
        return name;
    return "none";
}

char *getHostname()
{
    char *name = getenv("HOSTNAME");
    if (name)
        return name;
    return "none";
}

char *getCwd()
{
    char *cwd = getenv("PWD");
    if (cwd)
        return cwd;
    return "none";
}

char* getHomePath()
{
    char* path = getenv("HOME");
    if(path) return path;
    return "none";
}

int getComment(char comment[], int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());

    char *ch = fgets(comment, num, stdin);
    if (ch == NULL)
        return -1;
    int n = strlen(comment);
    comment[n - 1] = '\0';
    return n - 1;
}

void commentSplit(char *in, char *out[])
{
    int pos = 0;
    out[pos++] = strtok(in, SEP);
    while (out[pos++] = strtok(NULL, SEP));
}

void execute(char *argv[])
{
    pid_t id = fork();
    if (id == 0)
    {
        execvp(argv[0], argv);
        exit(0);
    }
    int status = 0;
    waitpid(id, &status, 0);
    lastcode = WEXITSTATUS(status);
}

void cd(const char* path)
{
    char tmp[NUM];
    chdir(path);
    getcwd(tmp,sizeof tmp);
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}
int dobuildcom(char* argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        char* path;
        if(argv[1] == NULL) path = getHomePath();
        else if(strcmp(argv[1], "~") == 0) 
        {
            path = getHomePath();
        }else path = argv[1];

        cd(path);
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(add_env[envi],argv[1]);
        putenv(add_env[envi]);
        envi++;
        return 1;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        if(argv[0] == NULL) 
        {
            printf("\n");
            return 1;
        }
        char* tmp = argv[1];
        if(tmp[0] == '$')
        {
            tmp++;
            if(strcmp(tmp,"?") == 0) 
            {
                printf("%d\n",lastcode);
            }else
            {
                char* c = getenv(tmp);
                if(c) printf("%s\n",c);
                else printf("\n");

            }
        }
        else{
            printf("%s\n",tmp);
        }
        lastcode = 0;
        return 1;
    }

    return 0;
}
int main()
{
    while (1)
    {
        char usercomment[NUM];
        char *argv[64] = {NULL};
        int n = getComment(usercomment, sizeof usercomment);
        if(n <= 0) continue;
        // 分割字符串
        commentSplit(usercomment, argv);
        //检查并内建命令
        n = dobuildcom(argv);
        if(n) continue;
        // 执行命令
        execute(argv);
    }
    return 0;
}

到了这里,关于自定义实现shell/bash的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux中阶教程:bash shell基础

    echo 表示打印字符串; read 表示获取用户输入; $ 用于引用变量。 其运行结果为 bash 中的运算符并没有什么特殊的, 运算符 说明 + , - , × , / , % 加减乘除,求余;支持 += 形式 ** , , || 乘方、关系与、或 但是,与常见编程语言不同的是, bash 中的赋值和数学计算需要在

    2024年02月06日
    浏览(48)
  • Linux Bash Shell 脚本入门(2)——GNU

    ​ 目录 Linux Bash Shell 脚本入门(2)——GNU GNU与Linux GNU GNU的组成 Shell GNOME桌面环境 X Window 软件 GNOME 可点击内容,大部分来自维基百科,可以点击了解详情。 GNU操作系统起源于GNU计划,由理查德·斯托曼在麻省理工学院人工智能实验室发起,希望发展出一套完整的开放源代码

    2024年02月19日
    浏览(36)
  • linux操作系统中shell和bash

    目录 shell命令以及运行原理 为什么不能直接使用kernel? 总的来说: Linux严格意义上说的是一个操作系统,称之为“核心( kernel )“ ,但我们一般用户,不能直接使用kernel。 而是通过kernel的“外壳”程序,也就是所谓的 shell ,来与kernel沟通。 1. 执行命令和程序: 通过Shell,

    2024年02月11日
    浏览(39)
  • linux bash shell变量操作符 —— 筑梦之路

    ${var} 返回变量var的内容,单独使用时有没有{}一样,混合多个变量和常量时,用{}界定变量名 ${#var} 返回变量var内容的长度 ${var:offset} 从变量var中的偏移量offset开始截取到字符串结尾的子字符串,offset从0开始 ${var:offset:length} 从变量var中的偏移量offset开始截取长度为length的子字

    2024年02月04日
    浏览(43)
  • bash shell实现简易进度条

    # processbar current total processbar() {   local current=$1; local total=$2;   local maxlen=80; local barlen=66; local perclen=14;   local format=\\\"%-${barlen}s%$((maxlen-barlen))s\\\"   local perc=\\\"[$current/$total]\\\"   local progress=$((current*barlen/total))   local prog=$(for i in `seq 0 $progress`; do printf \\\'#\\\'; done)   printf \\\"rn$3rn$format\\\" $p

    2024年02月21日
    浏览(33)
  • Shell脚本中文英文多语言国际化和命令行批处理(bash sh cmd bat)中定义函数的简单写法

    有时候为了方便别人使用,我们会选择去编写各种各样的命令行脚本:给Windows用户编写 .bat cmd批处理脚本,给macOS、Linux用户编写 .sh bash shell脚本。 面向国内用户当然应当首选中文作为脚本的显示语言,如果还要支持海外用户使用,那么能提供国际化多语言( i18n )支持那是

    2024年02月08日
    浏览(55)
  • 【Linux】自定义shell

    👑作者主页:@安 度 因 🏠学习社区:安度因 📖专栏链接:Linux

    2024年02月01日
    浏览(27)
  • Linux自定义shell编写

    经过了创建进程,终止进程,进程等待和进程程序替换之后, 我们就可以借助这些知识实现一个简单的shell命令行解释器了 温馨提示: 建议大家自己写一遍,这些代码分块之后每一个函数都很简单, 不过实现过程中可能会有各种各样非常细枝末节的地方被我们所忽视 因此可能会发生

    2024年02月04日
    浏览(41)
  • 20.Linux Shell自定义函数

    欢迎访问个人网络日志🌹🌹知行空间🌹🌹 在脚本中一遍又一遍地编写同样的代码会很烦人,为了避免这种麻烦,可以将代码封装成函数,多次引用。 Linux Shell 中函数的定义有两种方式: 采用 function 函数名后跟空括号 使用函数: 执行, 每次引用函数名时, bash s

    2024年01月25日
    浏览(34)
  • linux环境下Shell脚本中定义函数

    说实话,这是我第一次用Shell来定义函数,之前也写了很多shell脚本,但是体量都不大,所以基本上是按照需求罗列了多个命令来实现的,这次也是一样,但是我发现重复的地方太多了,所以还是要拿出看见本事“抽象函数”来解放我懒惰的手指 检测特定名称的进程是否存在

    2024年02月12日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包