Linux 简易shell 实现与分析

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

源码

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

#define NUM 1024 
#define SIZE 32 
#define SEP " "

//bufeer: 缓冲区

char g_myval[64];

//保存完整的命令行字符串 
char cmd_line[NUM] ;

//保存打散后的字符串 
char *g_argv[SIZE]; 


//定义宏

#define INPUT_REDTR 1 
#define OUTPUT_REDIR 2 
#define APPEND_REDIR 3 
#define NONE_REDIR 0 

int redir_status = NONE_REDIR;

char *CheckRedir(char *start)
{
  assert(start);
  char* end = start + strlen(start) - 1 ; //类似迭代器,但指向的是最后一个元素/字符
  while(end >= start)
  {
    if(*end == '>')
    {
      if(*(end-1) == '>')
      {
        redir_status = APPEND_REDIR ; 
        *(end - 1) = '\0' ;
        end++ ; 
        break ;
      }
      redir_status = OUTPUT_REDIR ; 
      *end = '\0';
      end++ ;
      break;
    }
    else if(*end == '<')
    {
      redir_status = INPUT_REDTR;
      *end = '\0';
      end++;
      break; 
    }
    else
    {
      end--;
    }
  }

  
  if(end >= start) //保证 << 出现在规范位置 
  {
    return end; //end 此时指向要打开的文件名称第一个字符的位置,也就代表了要打开的文件名
  }
  else
  {
    return NULL ; 
  }
}

//shell主体
int main()
{
  //environ是一个全局变量,它是一个字符指针数组,用于存储当前进程的环境变量列表。
  extern char** environ;

  // shell执行命令后不退出
  while(1)
  {
    //打印提示信息
    printf("[root@localhost myshell]#");
    fflush(stdout);
    memset(cmd_line, '\0', sizeof cmd_line ); //初始化
    
    //获取用户输入指令 
    
    //从stdin中读取 sizeof cmd_line 大小的内容进入cmd_line,遇到结尾停止读取 
    if(fgets(cmd_line, sizeof cmd_line , stdin) == NULL)
    {
      continue ;
    }

    cmd_line[strlen(cmd_line) - 1 ] = '\0';

    // 分析是否有重定向 
    
    char* sep = CheckRedir(cmd_line);

    //将读到的字符串,以空格为标记打散,方便指令的执行
    g_argv[0] = strtok(cmd_line , SEP) ; // 第一次调用strtok 
    int index = 1; 
    while(g_argv[index++] = strtok(NULL,SEP)) ; //后续循环调用  // 如果没有更多可以分割则返回NULL结束循环

    // 支持export 指令
    if(strcmp (g_argv[0] , "export") == 0 && g_argv[1] != NULL)
    {
      strcpy(g_myval, g_argv[1]);
      int ret = putenv(g_myval); //putenv是系统接口

      if(ret ==  0)
      {
        printf(" %s export success \n", g_argv[1]) ;
      }
      continue; 
    }
     
    //内置命令 
    if(strcmp(g_argv[0], "cd") == 0)
    {
      if(g_argv[1] != NULL) 
      {
        chdir(g_argv[1]); // 切换当前工作目录/即进程所在目录 的系统接口
      }
      continue ; 
    }
    
    // fork 执行其他命令
    pid_t id = fork();

    //重定向指令
    if(id == 0) // 子进程执行
    {
      if(sep != NULL) // 返回值为空说明发生了重定向
      {
        int fd = -1; // ???
        
        switch(redir_status)
        {
          case INPUT_REDTR: 
            fd = open(sep, O_RDONLY );
            dup2(fd, 0); //new位置只会是0/1因为只会是输入/输出重定向
            break;
          case OUTPUT_REDIR:
            fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT , 0666);
            dup2(fd, 1);
            break;
          case APPEND_REDIR:
            fd = open(sep,O_WRONLY | O_APPEND | O_CREAT , 0666); //0666为文件默认权限 //O_CREAT 文件不存在则创建文件
            dup2(fd, 1);
            break;
          default:
            printf("错误\n");
            break;
        }
      }

       execvp(g_argv[0],g_argv);                                                                                                                                       exit(1);
    }
    int status = 0 ;
    pid_t ret = waitpid(id , &status,0);
    if(ret > 0)
    {
      printf("exit code: %d\n" , WEXITSTATUS(status)); //如果子进程正常退出,返回子进程的退出状态码。
    }  
  }
  

}

相关函数

Linux C标准库提供的environ

environ是一个全局变量,它是一个字符指针数组,用于存储当前进程的环境变量列表。
环境变量是一组以key=value形式存储的字符串,用于在操作系统中传递配置信息和参数。例如,PATH=/usr/bin:/usr/local/bin就是一个环境变量的示例,表示可执行文件的搜索路径。
environ变量是一个指向环境变量列表的指针数组,每个元素都是一个指向以key=value形式表示的环境变量字符串的指针。environ数组的最后一个元素为NULL,用于表示环境变量列表的结束。
通过遍历environ数组,可以访问和操作当前进程的所有环境变量。例如,可以使用printf()函数打印出所有的环境变量及其对应的值:

#include <stdio.h>

extern char **environ;

int main() {
    char **env = environ;
    while (*env != NULL) {
        printf("%s\n", *env);
        env++;
    }

    return 0;
}

在这个示例中,通过访问全局变量environ来获取当前进程的环境变量列表。然后使用一个循环遍历environ数组,并使用printf()函数打印出每个环境变量的字符串。
需要注意的是,environ变量是标准C库提供的全局变量,定义在<unistd.h>头文件中。在使用environ变量之前,需要包含该头文件。

memset函数

memset函数
内存设置函数 memset(地址,设置的必须为整形的内容x,改变的字节数n) 用整形内容x替换地址容器中前n个字节的内容

void *memset(void *s, int c, size_t n);

fgets函数

文本行输入函数
char * fgets ( char * str, int num, FILE * stream );
从文件指针开始向str字符串中拷贝num个字符,并返回这段字符。(如遇到文件尾则立刻停止) 成功时,该函数返回str,str为字符串首地址指针。

strtok分割函数

char *strtok(char *str, const char *delim)

str – 要被分解成一组小字符串的字符串。
delim – 包含分隔符的 C 字符串(分割标记,本质就是分割标记被替换为\0)。

调用方法:分多次使用
例如:
第一次调用strtok(),传入的参数str是要被分割的字符串{aaa - bbb -ccc},而成功后返回的是第一个子字符串{aaa};

而第二次调用strtok的时候,传入的参数应该为NULL(char *str位置的参数,后面的参数还是要输入),使得该函数默认使用上一次未分割完的字符串继续分割 ,就从上一次分割的位置{aaa-}作为本次分割的起始位置,直到分割结束。

注意:
1.strtok函数会修改原始字符串,因此如果需要保留原始字符串,可以先创建一个副本进行分割操作。
2.如果没有更多的子字符串可供返回,则返回NULL

strcpy函数

char *strcpy(char *dest, const char *src)

将后者的字符串内容赋给前者
源字符串必须以 ‘\0’ 结束。
会将源字符串中的 ‘\0’ 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。
目标空间必须可变。

putenv函数

putenv()函数
在Linux中,putenv()函数用于设置环境变量的值。它的函数原型定义在<stdlib.h>头文件中。
以下是putenv()函数的函数原型:

#include <stdlib.h>
int putenv(char *string);

putenv()函数接受一个以key=value形式表示的字符串参数,用于设置环境变量的值。它会将该字符串添加到当前进程的环境变量中,或者如果该环境变量已经存在,则会更新其值。
putenv()函数返回一个整数值,表示执行结果。如果成功设置或更新环境变量,则返回0;如果出现错误,则返回非零值。
具体使用:


```cpp
#include <stdio.h>
#include <stdlib.h>

int main() {
    char *env_var = "MY_VAR=my_value";
    putenv(env_var);

    char *value = getenv("MY_VAR");
    if (value != NULL) {
        printf("MY_VAR value: %s\n", value);
    } else {
        printf("MY_VAR not found.\n");
    }

    return 0;
}


chdir函数

在Linux的C语言编程中,chdir函数用于改变当前工作目录。它的原型如下:

int chdir(const char *path);

其中,path是一个字符串,表示要切换到的目标目录的路径。

chdir函数会将当前进程的工作目录更改为指定的目录。如果目录切换成功,则返回0;如果出现错误,则返回-1,并设置相应的错误码,可以通过errno变量获取具体的错误信息。

需要注意的是,chdir函数只影响当前进程的工作目录,并不影响其他进程。在多进程或多线程的程序中,使用chdir函数只会改变当前进程的工作目录,不会影响其他进程或线程的工作目录。

下面是一个简单的示例:

#include <stdio.h>
#include <unistd.h>

int main() {
    if (chdir("/path/to/directory") == 0) {
        printf("目录切换成功\n");
    } else {
        perror("目录切换失败");
    }
    
    return 0;
}

上述示例将当前进程的工作目录切换到/path/to/directory目录,并打印相应的结果。

open函数

在Linux的C语言编程中,open函数用于打开文件或创建文件。它的原型如下:

#include <fcntl.h>

int open(const char *path, int flags, mode_t mode);

其中,path是一个字符串,表示要打开或创建的文件的路径;flags是一个整数,表示打开文件的方式和选项;mode是一个文件权限的模式,用于创建新文件时指定权限。

open函数会返回一个文件描述符(file descriptor),用于后续对文件的读写操作。如果打开或创建文件成功,则返回一个非负整数的文件描述符;如果出现错误,则返回-1,并设置相应的错误码,可以通过errno变量获取具体的错误信息。

flags参数可以使用以下常用的标志(可以使用按位或|组合多个标志):

  • O_RDONLY:以只读方式打开文件。
  • O_WRONLY:以只写方式打开文件。
  • O_RDWR:以读写方式打开文件。
  • O_CREAT:如果文件不存在,则创建文件。
  • O_TRUNC:如果文件存在且以写方式打开,则将文件截断为空文件。
  • O_APPEND:以追加方式打开文件,每次写入都会添加到文件末尾。

mode参数只在创建新文件时使用,指定新文件的权限。它通常使用八进制表示,例如0644表示权限为rw-r--r--

下面是一个简单的示例:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "Hello, World!", 13);
        close(fd);
        printf("文件写入成功\n");
    } else {
        perror("文件打开失败");
    }
    
    return 0;
}

execve封装的函数

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1,exec函数只有出错的返回值而没有成功的返回值

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[])

wait和waitpid函数

wait()函数是一个系统调用,用于使当前进程进入等待状态,直到它的一个子进程结束或收到一个信号为止。当子进程结束时,它会向父进程发送一个信号,父进程通过wait()函数来获取子进程的退出状态。
wait()函数的原型如下:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

其中,status是一个指向整型变量的指针,用于存储子进程的退出状态。如果不关心子进程的退出状态,可以将status设置为NULL。wait()函数的返回值是子进程的进程ID,如果出错则返回-1。如果当前进程没有子进程,调用wait()函数会立即返回。

waitpid函数用于等待指定的子进程结束并获取其状态。它的原型如下:
其中,pid是要等待的子进程的进程ID,可以指定为以下值:

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

pid > 0:等待具有指定进程ID的子进程结束。
pid == -1:等待任意子进程结束,相当于wait函数。
pid == 0:等待与调用进程属于同一个进程组的任意子进程结束。
pid < -1:等待指定进程组ID的任意子进程结束。
status是一个整型指针,用于存储子进程的退出状态。如果不关心子进程的退出状态,可以将status设置为NULL。

options是一个整数,用于指定等待子进程的行为和选项,常用的选项有:

WCONTINUED:等待被暂停的子进程继续执行。
WNOHANG:如果没有子进程结束,则立即返回,不阻塞。
WUNTRACED:等待被停止的子进程。

waitpid函数会阻塞调用进程,直到指定的子进程结束或满足指定的条件。如果成功等待到子进程结束,则返回子进程的进程ID;如果出现错误,则返回-1。

解析宏
可以使用一些宏来解析waitpid函数的返回值,以获取子进程的退出状态。
以下是常用的宏:
WIFEXITED(status):如果子进程正常退出,则返回非零值。
WEXITSTATUS(status):如果子进程正常退出,返回子进程的退出状态码。
WIFSIGNALED(status):如果子进程被信号终止,则返回非零值。
WTERMSIG(status):如果子进程被信号终止,返回导致终止的信号编号。
WIFSTOPPED(status):如果子进程被暂停,则返回非零值。
WSTOPSIG(status):如果子进程被暂停,返回导致暂停的信号编号。文章来源地址https://www.toymoban.com/news/detail-523196.html

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

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

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

相关文章

  • linux操作系统中shell和bash

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

    2024年02月11日
    浏览(27)
  • 简易英文统计和加密系统的设计实现(纯C语言实现,包含文件操作、注释多、易理解)

    ❤️作者主页:微凉秋意 🔥系列专栏:数据结构与课程设计 ✅作者简介:后端领域优质创作者🏆,CSDN内容合伙人🏆,阿里云专家博主🏆

    2024年02月02日
    浏览(31)
  • 【Linux】操作系统的基本概念 {冯诺依曼体系结构,操作系统的基本概念,系统调用及用户操作接口,shell程序}

    现代计算机设计大都遵守冯·诺依曼体系结构: 截至目前,我们所认识的计算机,都是由一个个的硬件组件组成 输入单元:包括键盘, 鼠标,扫描仪, 磁盘,网卡等 存储器: 内存(提高数据读写速度,降低硬件成本) 中央处理器(CPU):含有运算器(算数运算,逻辑运算)和控

    2024年02月11日
    浏览(34)
  • 【Linux】进程控制 — 进程程序替换 + 实现简易shell

    上一节我们讲了进程终止和进程等待等一系列问题,并做了相应的验证,本章将继续对进程控制进行学习,我们将学习进程程序替换,进行相关验证,运用系统进程程序替换接口,自己模拟写一个shell,该shell能够实现执行指令,等一系列命令行操作…… 概念引入: 将可执行

    2024年02月06日
    浏览(37)
  • 【Linux操作系统】【综合实验二 vi应用与shell脚本编辑】【浅试编辑命令】

    要求进一步掌握Linux基础操作,掌握全屏幕编辑命令vi的高级应用,熟悉shell脚本编辑与命令行编辑。 通过这个第二阶段实验,要求掌握以下操作与相关知识: (1)进一步掌握Linux系统的 文件类、目录类、进程管理类与磁盘操作类常用命令 ; (2)了解或掌握Linux系统支持的

    2023年04月22日
    浏览(54)
  • Linux 操作系统和C语言(详解)

    1、操作系统 定义:本质是运行在计算机上的软件程序 组成:内核 + 外壳(图形化界面+软件工具...) 作用:向用户提供操作接口,管理计算机硬件和软件资源。 主流操作系统有Windows、 MacOS、 Linux 2、GNU/Linux Linux1.0 1.Linux又称为类Unix操作系统 Minux 2.Linux的特点免费、开源、可

    2023年04月27日
    浏览(63)
  • Linux之进程控制&&进程终止&&进程等待&&进程的程序替换&&替换函数&&实现简易shell

    1.1 fork的使用 我们可以使用man指令来查看一下 子进程会复制父进程的PCB,之间代码共享,数据独有,拥有各自的进程虚拟地址空间。 这里就有一个代码共享,并且子进程是拷贝了父进程的PCB,虽然他们各自拥有自己的进程虚拟地址空间,数据是拷贝过来的,通过页表映射到

    2024年04月17日
    浏览(40)
  • 【项目分析】仿linux0.11的操作系统内核

    系列综述: 💞目的:本系列是个人整理为了 秋招面试 的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于 《操作系统 真象还原》及各大佬博客 进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验

    2024年02月09日
    浏览(24)
  • 如何在linux(ubantu)操作系统运行c语言文件

    关于linux的其他文章: ​1.linux命令、vi命令、vim命令、shell语法(完整详细) 2.linux(ubantu)系统如何安装vim编辑器以及如何使用vim 3.如何在linux(ubantu)操作系统运行c语言文件 4.如何在Linux(ubantu)系统通过c程序将文档1指定内容替换到文档2的指定内容 5.如何在Linux(ubantu)系统

    2023年04月08日
    浏览(28)
  • 【看表情包学Linux】插叙:实现简易的 Shell | 通过内建命令实现路径切换 | 再次理解环境变量

       🤣  爆笑 教程  👉 《看表情包学Linux》👈   猛戳订阅     🔥 💭 写在前面: 本章是个 \\\"插叙\\\",前几章我们学了程序替换,现在我们可以尝试动手做一个 \\\"会创建,会终止,会等待,会程序替换\\\" 的简易 shell 了。通过本章的内容,可以进一步巩固进程替换,学习内建

    2024年02月22日
    浏览(36)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包