Tiny-shell
阅读原文时间:2023年07月16日阅读:1

Tiny-shell(一): 一个模仿bash的极简shell


通过构建一个简单的shell,能够对shell的工作原理进行一些了解。主要有:

  • 重定向
  • 流水线
  • 前台信号处理
  • 进程组
  • 后台进程
  • 作业控制

这篇文章里先实现一个极简的shell,后续再不断对功能进行完善添加。

main函数里,我们需要一个循环来一直进行输出,打印shell的提示符,用于提示命令的输入。同时,我们需要对从标准输入的命令进行处理,也就是字符串的处理。当按下回车时,需要我们fork出一个子进程,并调用execvp来执行处理过的命令。然后main函数wait子进程执行完毕继续打印新的一行提示符即可。这就是一个极简的shell。

make_argv

首先我们需要写一个处理字符串的函数int make_argv(const char *s, const char *delimiters, char ***argvp)s即是我们传入的字符串,比如ls -l,我们要做的就是将此字符串处理成一个参数数组,便于传递给int execvp(const char *file, char *const argv[])。函数中主要涉及用strtok来进行处理。

execute_cmd

这个函数用于调用make_argv,并进行检错,然后调用execvp

main

main函数里进行fork,并调用execute_cmd

编译:`gcc -o tsh1 tsh1.c execute_cmd_simple.c make_argv.c"

tsh1>>
tsh1>> ls
execute_cmd_simple.c  make_argv.c  tsh1  tsh1.c
tsh1>>
tsh1>> echo "This is a tiny shell~"
"This is a tiny shell~"
tsh1>> q

tsh1.c:

/**
 * @Filename: tsh1.c
 * @Description: tiny shell 的main函数
 */

#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define PROMPT_STRING "tsh1>> "
#define QUIT_STRING "q"  /* 按q退出 */ 

void execute_cmd(char *incmd);

int main (void) {
   pid_t childpid;
   char inbuf[MAX_CANON];
   int len;

   for( ; ; ) {
      if (fputs(PROMPT_STRING, stdout) == EOF)
          continue;
      if (fgets(inbuf, MAX_CANON, stdin) == NULL)
          continue;
      len = strlen(inbuf);
      if (len == 1 && inbuf[len - 1] == '\n')   /* 若直接按回车就忽略 */
          continue;
      if (inbuf[len - 1] == '\n')
          inbuf[len - 1] = 0;
      if (strcmp(inbuf, QUIT_STRING) == 0)
          break;
      if ((childpid = fork()) == -1)
          perror("Failed to fork child");
      else if (childpid == 0) {  /* 子进程 */
          execute_cmd(inbuf);
          return 1;
      } else
         wait(NULL);
   }
   return 0;
}

execite_cmd_simple.c:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BLANK_STRING " "

int make_argv(const char *s, const char *delimiters, char ***argvp);

void execute_cmd(char *incmd) {
    char **chargv;
    if (make_argv(incmd, BLANK_STRING, &chargv) <= 0) {
       fprintf(stderr, "Failed to parse command line\n");
       exit(1);
    }
    execvp(chargv[0], chargv);
    perror("Failed to execute command");
    exit(1);
}

make_argv.c:

/**
 * @Filename: make_argv.c
 * @Description: 处理字符串,形成参数数组
 */

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

/** s是传入字符串
 *  delimiters是分隔符
 *  argvp是形成的参数数组
 */
int make_argv(const char *s, const char *delimiters, char ***argvp) {
   int error;
   int i;
   int numtokens;  /* 形成的token总数 */
   const char *snew;
   char *t;

   if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
      errno = EINVAL;
      return -1;
   }
   *argvp = NULL;
   snew = s + strspn(s, delimiters);         /* snew是字符串的真正的开始,去掉前面多余的分隔符*/
   if ((t = malloc(strlen(snew) + 1)) == NULL)
      return -1;
   strcpy(t, snew);
   numtokens = 0;
   if (strtok(t, delimiters) != NULL)     /* 计算token的数目,for从1开始是因为参数数组最后一个空间要给NULL */
      for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ; 

   /* 创建参数数组 */
   if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
      error = errno;
      free(t);
      errno = error;
      return -1;
   }
   /* 对每个子串进行拷贝 */
   if (numtokens == 0)
      free(t);
   else {
      strcpy(t, snew);
      **argvp = strtok(t, delimiters);
      for (i = 1; i < numtokens; i++)
          (*argvp)[i] = strtok(NULL, delimiters);
    }
    (*argvp)[numtokens] = NULL;             /* 最后放NULL */
    return numtokens;
}