Linux 简易shell 实现与分析

文章目录

  • 源码
  • 相关函数
    • Linux C标准库提供的environ
    • memset函数
    • fgets函数
    • strtok分割函数
    • strcpy函数
    • putenv函数
    • chdir函数
    • open函数
    • execve封装的函数
    • wait和waitpid函数

源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 

extern char **environ;

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

    return 0;
}

在这个示例中,通过访问全局变量environ来获取当前进程的环境变量列表。然后使用一个循环遍历environ数组,并使用printf()函数打印出每个环境变量的字符串。
需要注意的是,environ变量是标准C库提供的全局变量,定义在头文件中。在使用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()函数用于设置环境变量的值。它的函数原型定义在头文件中。
以下是putenv()函数的函数原型:

#include
int putenv(char *string);

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


```cpp
#include 
#include 

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 
#include 

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

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

open函数

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

#include 

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 
#include 
#include 

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 `
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 
#include 

pid_t wait(int *status);

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

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

#include 
#include 

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):如果子进程被暂停,返回导致暂停的信号编号。

你可能感兴趣的:(linux,shell,C语言,c语言,操作系统)