【Linux进程控制】自主Shell

目录

自主shell实现

获取基本变量

实现命令行

 获取用户命令字符串

命令行字符串分割

内建命令CD()

chdir

getcwd

putenv

检查是否为内建命令

 检查是否为重定向

执行命令

主函数设置

测试用例

项目代码


自主shell实现

根据之前学的内容,我们已经可以模拟一个shell的实现;一个shell程序需要循环做以下的事情: 1.获取命令行

2.解析命令行

3.创建子程序

4.替换子程序

5.父进程等待子进程退出

获取基本变量

首先我们根据shell发现我们要实现自助shell就要先获取以下变量

我们可以在env查看我们需要的变量,以及使用getenv()获取我们需要的变量 

//获取用户名
const char* GetUserName(){
  const char* name = getenv("USER");
  if(name == NULL) return "None";
  return name;
}

const char* GetHome(){
  const char* home = getenv("HOME");
  if(home == NULL) return "/";
  return home;
}

const char* GetHostName(){
  const char* hostname = getenv("HOSTNAME");
  if(hostname == NULL) return "None";
  return hostname;
}

const char* GetCwd(){
  const char* cwd = getenv("PWD");
  if(cwd == NULL) return "None";
  return cwd;
}

实现命令行

我们要把从env获取的内容拼成一个字符串

#define SIZE 512
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)

void MakeCommandLineAndPrint(){
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  //只保留相对路径
  SkipPath(cwd);
  //写入指定缓冲区
  snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);
  //打印命令行
  printf("%s",line);
  fflush(stdout);
}

 获取用户命令字符串

#define SIZE 512
#define ZERO '\0'

//获取用户指令
int GetUserCommand(char command[], size_t n){
  char* s = fgets(command, n, stdin);
  if(s == NULL) return -1;

  //由于fgets也会获取我们输入的回车换行符
  //为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'
  command[strlen(command)-1] = ZERO;
  return strlen(command);
}

命令行字符串分割

认识字符串分割函数

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

参数说明:

  • str:指向要分解的字符串的指针。在第一次调用时,这个参数应该是要分解的字符串;在随后的调用中,应该为 NULL,因为 strtok 会保存上一次的位置。

  • delim:包含分隔符的字符串。

函数工作原理:

  • strtok 在第一次调用时,会在字符串 str 中查找包含在 delim 中的任意一个字符,找到后将其替换为 null 字符 ('\0'),并返回指向该位置的指针。

  • 在随后的调用中,strtok 会从上次替换的 null 字符之后开始查找,重复同样的过程。

函数返回值:

  • 成功时,strtok 返回指向下一个标记的指针。

  • 当没有更多的标记时,返回 NULL

注意事项:

  • strtok 会修改原始字符串,因为它会在找到的分隔符位置放置 null 字符。

  • 使用 strtok 时,通常需要使用一个临时变量来保存原始字符串的副本,以避免破坏原始数据。

【示例代码】

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "a-b-c-d-e";
    char *token;

    /* 获取第一个标记 */
    token = strtok(str, "-");

    /* 继续获取其他标记 */
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok(NULL, "-");
    }

    return 0;
}

在这个例子中,字符串 "a-b-c-d-e" 将被分割为 "a", "b", "c", "d", 和 "e",每个部分由换行符分隔打印出来。

#define SEP " "
#define NUM 32
#define SkipSpace(cmd, pos) do{\
  while(1){\
    if(isspace(cmd[pos]))\
      pos++;\
    else break;\
  }\
}while(0)

char *gArgv[NUM];

//分割命令
void SplitCommand(char command[], size_t n){
 (void)n; 
  // "ls -a -l -n" -> "ls" "-a" "-l" "-n"
  gArgv[0] = strtok(command, SEP);
  int index = 1;
  //故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL
  //刚好让gArgv最后一个元素是NULL, 并且while判断结束
  while((gArgv[index++] = strtok(NULL,SEP)));
}

内建命令CD()

我们需要让父进程执行CD命令,我们打印的命令行是父进程打印的,如果子进程执行cd命令是无法进行路径切换的

认识几个函数

chdir

在 C 语言中,chdir 函数用于更改当前工作目录。这个函数属于标准库中的 unistd.h 头文件。

int chdir(const char *path);

参数说明:

  • path:指向新工作目录路径的指针。

函数返回值:

  • 成功时,chdir 返回 0。

  • 出错时,返回 -1,并且设置 errno 来指示错误。

chdir 函数的行为会根据不同的操作系统和文件系统有所不同,但它通常用于更改进程的当前工作目录。更改当前工作目录后,所有相对路径的文件操作都将相对于新的工作目录进行。

getcwd

在 C 语言中,getcwd 函数用于获取当前工作目录的路径。这个函数定义在 unistd.h 头文件中,它返回一个指向字符串的指针,该字符串包含了当前工作目录的绝对路径。

char *getcwd(char *buf, size_t size);

参数说明:

  • buf:指向用于存储当前工作目录路径的缓冲区的指针。如果 bufNULL,则 getcwd 会分配足够大的缓冲区来存储路径,调用者必须使用 free 函数来释放这块内存。

  • size:缓冲区 buf 的大小。

函数返回值:

  • 成功时,getcwd 返回指向当前工作目录路径字符串的指针,该字符串存储在 buf 中。

  • 出错时,返回 NULL,并设置 errno 来指示错误。

putenv

在 C 语言中,putenv 函数用于添加或修改环境变量。环境变量是影响程序运行的外部参数,它们通常用于配置程序的行为。putenv 函数定义在 stdlib.h 头文件中。

int putenv(char *string);

参数说明:

  • string:指向一个形式为 “name=value” 的字符串的指针,其中 name 是环境变量的名称,value 是要赋予环境变量的值。

函数返回值:

  • 成功时,putenv 返回 0。

  • 出错时,返回非零值,并设置 errno 来指示错误。

使用 putenv 添加或修改环境变量后,这些更改仅对当前进程及其子进程有效,不会影响父进程或其他无关进程。

void Cd()
{
    const char *path = gArgv[1];
    if(path == NULL) path = GetHome();
    // path 一定存在
    chdir(path);

    // 刷新环境变量
    char temp[SIZE*2];
    getcwd(temp, sizeof(temp));
    snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
    putenv(cwd); // OK
}

★ps:如果不修改环境变量,则命令行上路径是变了,但打印的确没有变

检查是否为内建命令

char *gArgv[NUM];
int lastcode = 0;

int CheckBuildin()
{
    int yes = 0;
    const char *enter_cmd = gArgv[0];
    if(strcmp(enter_cmd, "cd") == 0)
    {
        yes = 1;
        Cd();
    }
    else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        yes = 1;
        printf("%d\n", lastcode);
        lastcode = 0;
    }
    return yes;
}

 检查是否为重定向

void CheckRedir(char cmd[])
{
    // > >> <
    // "ls -a -l -n >  myfile.txt"
    int pos = 0;
    int end = strlen(cmd);

    while(pos < end)
    {
        if(cmd[pos] == '>')
        {
            if(cmd[pos+1] == '>')
            {
                cmd[pos++] = 0;
                pos++;
                redir_type = App_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
            else
            {
                cmd[pos++] = 0;
                redir_type = Out_Redir;
                SkipSpace(cmd, pos);
                filename = cmd + pos;
            }
        }
        else if(cmd[pos] == '<')
        {
            cmd[pos++] = 0;
            redir_type = In_Redir;
            SkipSpace(cmd, pos);
            filename = cmd + pos;
        }
        else
        {
            pos++;
        }
    }
}

执行命令

void ExecuteCommand()
{
    pid_t id = fork();
    if(id < 0) Die();
    else if(id == 0)
    {
        //重定向设置
        if(filename != NULL){
            if(redir_type == In_Redir)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0);
            }
            else if(redir_type == Out_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd, 1);
            }
            else if(redir_type == App_Redir)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                dup2(fd, 1);
            }
            else
            {}
        }

        // child
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        // fahter
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
            if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
        }
    }
}

主函数设置

int main(){
  int quit = 0;
  while(!quit){
  //1.我们需要自己输出一个命令行
  MakeCommandLineAndPrint(); 
  
  //2.获取用户命令字符串
  char usercommand[SIZE];
  int n = GetUserCommand(usercommand, sizeof(usercommand));
  if(n <= 0) return 1;

  //检测是否为重定向
  CheckRedir(usercommand);

  // 3. 命令行字符串分割. 
  SplitCommand(usercommand, sizeof(usercommand));
  
  //4.检测是否为内建命令
  n = CheckBuildin();
  if(n) continue;
  // 5. 执行命令
  ExecuteCommand();
  }
  return 0;
}

测试用例

[wuxu@Nanyi myshell]$ gcc -o myshell myshell.c -std=c99
myshell.c: In function ‘Cd’:
myshell.c:152:2: warning: implicit declaration of function ‘putenv’ [-Wimplicit-function-declaration]
  putenv(cwd); // OK 
  ^
[wuxu@Nanyi myshell]$ pwd
/home/wuxu/lesson20/myshell
[wuxu@Nanyi myshell]$ cd ..
[wuxu@Nanyi lesson20]$ pwd
/home/wuxu/lesson20
[wuxu@Nanyi lesson20]$ ls
log.txt  myshell  test  test1.c  test2.c
[wuxu@Nanyi lesson20]$ ls -a
.  ..  log.txt  myshell  test  test1.c  test2.c
[wuxu@Nanyi lesson20]$ ls -a -l
total 36
drwxrwxr-x  3 wuxu wuxu 4096 Sep  1 17:43 .
drwx------ 11 wuxu wuxu 4096 Sep  1 20:50 ..
-rw-rw-rw-  1 wuxu wuxu   50 Aug 31 19:49 log.txt
drwxrwxr-x  2 wuxu wuxu 4096 Sep  1 21:15 myshell
-rwxrwxr-x  1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r--  1 wuxu wuxu  468 Aug 31 19:36 test1.c
-rw-rw-r--  1 wuxu wuxu  413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ echo $?
0
[wuxu@Nanyi lesson20]$ echo "hello wuxu" > log.txt
[wuxu@Nanyi lesson20]$ ll
total 28
-rw-rw-rw- 1 wuxu wuxu   11 Sep  1 21:16 log.txt
drwxrwxr-x 2 wuxu wuxu 4096 Sep  1 21:15 myshell
-rwxrwxr-x 1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r-- 1 wuxu wuxu  468 Aug 31 19:36 test1.c
-rw-rw-r-- 1 wuxu wuxu  413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ echo "hello Nanyi" >> log.txt
[wuxu@Nanyi lesson20]$ ll
total 28
-rw-rw-rw- 1 wuxu wuxu   23 Sep  1 21:16 log.txt
drwxrwxr-x 2 wuxu wuxu 4096 Sep  1 21:15 myshell
-rwxrwxr-x 1 wuxu wuxu 8640 Aug 31 19:49 test
-rw-rw-r-- 1 wuxu wuxu  468 Aug 31 19:36 test1.c
-rw-rw-r-- 1 wuxu wuxu  413 Aug 31 19:49 test2.c
[wuxu@Nanyi lesson20]$ ^C
[wuxu@Nanyi lesson20]$ 

项目代码

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


#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p+= (strlen(p)-1); while(*p != '/') p--; }while(0)
#define SkipSpace(cmd, pos) do{\
  while(1){\
    if(isspace(cmd[pos]))\
      pos++;\
    else break;\
  }\
}while(0)


// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define In_Redir   1
#define Out_Redir  2
#define App_Redir  3

int redir_type = None_Redir;
char *filename = NULL;

// 为了方便,我就直接定义了
char cwd[SIZE*2];
char *gArgv[NUM];
int lastcode = 0;

void Die()
{
    exit(1);
}

//获取用户名
const char* GetUserName(){
  const char* name = getenv("USER");
  if(name == NULL) return "None";
  return name;
}

const char* GetHome(){
  const char* home = getenv("HOME");
  if(home == NULL) return "/";
  return home;
}

const char* GetHostName(){
  const char* hostname = getenv("HOSTNAME");
  if(hostname == NULL) return "None";
  return hostname;
}

const char* GetCwd(){
  const char* cwd = getenv("PWD");
  if(cwd == NULL) return "None";
  return cwd;
}

void MakeCommandLineAndPrint(){
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  //只保留相对路径
  SkipPath(cwd);
  //写入指定缓冲区
  snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);
  //打印命令行
  printf("%s",line);
  fflush(stdout);
}

//获取用户指令
int GetUserCommand(char command[], size_t n){
  char* s = fgets(command, n, stdin);
  if(s == NULL) return -1;

  //由于fgets也会获取我们输入的回车换行符
  //为了防止多打印一行 我们需要将获取到的'\n' 修改为'\0'
  command[strlen(command)-1] = ZERO;
  return strlen(command);
}

//分割命令
void SplitCommand(char command[], size_t n){
 (void)n; 
  // "ls -a -l -n" -> "ls" "-a" "-l" "-n"
  gArgv[0] = strtok(command, SEP);
  int index = 1;
  //故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL
  //刚好让gArgv最后一个元素是NULL, 并且while判断结束
  while((gArgv[index++] = strtok(NULL,SEP)));
}

void ExecuteCommand(){
  pid_t id = fork();
  if(id < 0 ) Die();
  else if(id == 0)
  {
    //重定向设置
    if(filename != NULL){
      if(redir_type == In_Redir){
        int fd = open(filename,O_RDONLY);
        dup2(fd,0);
      }
    }
    else if(redir_type == Out_Redir){
      int fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);
      dup2(fd,0);
    }
    else if(redir_type == App_Redir){
      int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
      dup2(fd, 1);
    }else{}
    //child
    execvp(gArgv[0],gArgv);
    exit(errno); 
  }
  else{
    //father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0){
      //wait success
    }
  }
}

void Cd(){
  const char* path = gArgv[1];
  if(path == NULL) path = GetHome();
  //path 一定存在
  chdir(path);

        //刷新环境变量
        char temp[SIZE*2];
        getcwd(temp, sizeof(temp));
        snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
        putenv(cwd); // OK 
}


int CheckBuildin(){
  int yes = 0;
  const char *enter_cmd = gArgv[0];
  if(strcmp(enter_cmd, "cd") == 0){
    yes = 1;
    Cd();
  }else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){
                yes = 1;
                printf("%d\n",lastcode);
                lastcode = 0;
}
        return yes;
}

void CheckRedir(char cmd[]){
    // > >> <
    // "ls -a -l -n >  myfile.txt"
    int pos = 0;
    int end = strlen(cmd);

    while(pos < end){
      if(cmd[pos] == '>')
      {
        //是否为追加重定向
        if(cmd[pos+1] == '>'){
          //是追加重定向
          cmd[pos++] = 0;
          pos++;
          redir_type = App_Redir;
          SkipSpace(cmd, pos);
          filename = cmd + pos;
        }
        else{
          //是输出重定向
          cmd[pos++] = 0;
          redir_type = Out_Redir;
          SkipSpace(cmd, pos);
          filename = cmd + pos;
        }
      }else if(cmd[pos] == '<')//是输入重定向
      {
        cmd[pos++] = 0;
        redir_type = In_Redir;
        SkipSpace(cmd,pos);
        filename = cmd + pos;

      }else{
        pos++;
      }
    }
}




int main(){
  int quit = 0;
  while(!quit){
  //1.我们需要自己输出一个命令行
  MakeCommandLineAndPrint(); 
  
  //2.获取用户命令字符串
  char usercommand[SIZE];
  int n = GetUserCommand(usercommand, sizeof(usercommand));
  if(n <= 0) return 1;

  //检测是否为重定向
  CheckRedir(usercommand);

  // 3. 命令行字符串分割. 
  SplitCommand(usercommand, sizeof(usercommand));
  
  //4.检测是否为内建命令
  n = CheckBuildin();
  if(n) continue;
  // 5. 执行命令
  ExecuteCommand();
  }
  ret

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值