用c语言编写shell

Write a Shell in C
今天在网上看到这篇Write a Shell in C。
先看代码

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

void lsh_loop();
char* lsh_read_line();
char** lsh_split_line(char*);
int lsh_execute(char**);

int main(int argc, char **argv)
{
  // Load config files, if any.

  // Run command loop.
  lsh_loop();

  // Perform any shutdown/cleanup.

  return EXIT_SUCCESS;
}

void lsh_loop() 
{
  char *line;
  char **args;
  int status;

  do {
    printf("> ");
    line = lsh_read_line();
    args = lsh_split_line(line);
    status = lsh_execute(args);

    free(line);
    free(args);
  } while (status);
}

char *lsh_read_line(void)
{
  char *line = NULL;
  size_t bufsize = 0; // have getline allocate a buffer for us

  if (getline(&line, &bufsize, stdin) == -1){
    if (feof(stdin)) {
      exit(EXIT_SUCCESS);  // We recieved an EOF
    } else  {
      perror("readline");
      exit(EXIT_FAILURE);
    }
  }
  return line;
}

#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
char **lsh_split_line(char *line)
{
  int bufsize = LSH_TOK_BUFSIZE, position = 0;
  char **tokens = malloc(bufsize * sizeof(char*));
  char *token;

  if (!tokens) {
    fprintf(stderr, "lsh: allocation error\n");
    exit(EXIT_FAILURE);
  }

  //分解字符串
  token = strtok(line, LSH_TOK_DELIM);
  while (token != NULL) {
    tokens[position] = token;
    position++;

    if (position >= bufsize) {
      bufsize += LSH_TOK_BUFSIZE;
      tokens = realloc(tokens, bufsize * sizeof(char*));
      if (!tokens) {
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
      }
    }

    token = strtok(NULL, LSH_TOK_DELIM);
  }
  tokens[position] = NULL;
  return tokens;
}

int lsh_launch(char **args)
{
  pid_t pid, wpid;
  int status;

  pid = fork();
  //一旦fork()返回,我们实际上有两个进程同时运行。子进程将采用第一个 if 条件 (where pid == 0)。
  if (pid == 0) {
    // Child process
    //int execvp(const char *file, char *const argv[]); file文件路径,argv用于接收命令行参数,执行一个新程序当前程序不执行
    //-1说明执行失败
    if (execvp(args[0], args) == -1) {
      perror("lsh");
    }
    exit(EXIT_FAILURE);
  } else if (pid < 0) {
    // Error forking
    perror("lsh");
  } else {
    // Parent process
    do {
      wpid = waitpid(pid, &status, WUNTRACED);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
  }
  return 1;
}

int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);

char *builtin_str[] = {"cd",  "help",  "exit"};
int (*builtin_func[]) (char **) = {&lsh_cd,  &lsh_help,&lsh_exit};

int lsh_num_builtins() {
  return sizeof(builtin_str) / sizeof(char *);
}

int lsh_cd(char **args)
{
  if (args[1] == NULL) {
    fprintf(stderr, "lsh: expected argument to \"cd\"\n");
  } else {
    if (chdir(args[1]) != 0) {
      perror("lsh");
    }
  }
  return 1;
}

int lsh_help(char **args)
{
  int i;
  printf("Stephen Brennan's LSH\n");
  printf("Type program names and arguments, and hit enter.\n");
  printf("The following are built in:\n");
  for (i = 0; i < lsh_num_builtins(); i++) {
    printf("  %s\n", builtin_str[i]);
  }
  printf("Use the man command for information on other programs.\n");
  return 1;
}

int lsh_exit(char **args)
{
  return 0;
}

int lsh_execute(char **args)
{
  int i;
  if (args[0] == NULL) {
    // An empty command was entered.
    return 1;
  }
  for (i = 0; i < lsh_num_builtins(); i++) {
    if (strcmp(args[0], builtin_str[i]) == 0) {
      return (*builtin_func[i])(args);
    }
  }
  return lsh_launch(args);
}

这里面实现了三个功能
cd
help
exit

cd

int lsh_launch(char **args)
{
  pid_t pid, wpid;
  int status;

  pid = fork();
  //一旦fork()返回,我们实际上有两个进程同时运行。子进程将采用第一个 if 条件 (where pid == 0)。
  if (pid == 0) {
    // Child process
    //int execvp(const char *file, char *const argv[]); file文件路径,argv用于接收命令行参数,执行一个新程序当前程序不执行
    //-1说明执行失败
    if (execvp(args[0], args) == -1) {
      perror("lsh");
    }
    exit(EXIT_FAILURE);
  } else if (pid < 0) {
    // Error forking
    perror("lsh");
  } else {
    // Parent process
    do {
      wpid = waitpid(pid, &status, WUNTRACED);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
  }

  return 1;
}

pid_t pid, wpid; int status;: 声明变量以存储进程ID和状态信息。
pid = fork();: 通过复制现有进程创建一个新进程。父进程获得子进程的ID,而子进程获得0。
if (pid == 0) { … }: 这段代码块由子进程执行。它使用 execvp 用一个新程序替换子进程的内存空间。如果 execvp 失败,将打印错误并以失败状态退出子进程。
else if (pid < 0) { … }: 如果 fork 失败(返回负值),将打印错误。
else { … }: 这个块由父进程执行。它使用 waitpid 等待子进程完成,直到子进程退出或被信号终止。子进程的状态存储在 status 变量中。
总的来说,这个函数使用 fork 创建一个新进程,子进程使用 execvp 执行指定的命令。父进程等待子进程完成,然后返回1。
当我们在写好的shell中输入一段可执行程序的地址,它将调用这个函数,然后在执行程序。

int lsh_cd(char **args)
{
  if (args[1] == NULL) {
    fprintf(stderr, "lsh: expected argument to \"cd\"\n");
  } else {
    if (chdir(args[1]) != 0) {
      perror("lsh");
    }
  }
  return 1;
}

if (args[1] == NULL) { … }: 检查是否提供了cd命令的参数。如果没有提供参数,打印错误消息到标准错误输出(stderr),指示用户期望得到一个参数。
else { … }: 如果提供了参数,则执行这个块的代码。
chdir(args[1]): 使用chdir函数改变当前工作目录为参数args[1]指定的目录。如果成功,chdir返回0;如果失败,返回非零值。
if (chdir(args[1]) != 0) { … }: 检查chdir的返回值,如果不是0,表示发生错误。在这种情况下,使用perror函数打印一个描述错误的消息到标准错误输出。

help

char *builtin_str[] = {"cd",  "help",  "exit"};
int lsh_num_builtins() {
  return sizeof(builtin_str) / sizeof(char *);
}
int lsh_help(char **args)
{
  int i;
  printf("Stephen Brennan's LSH\n");
  printf("Type program names and arguments, and hit enter.\n");
  printf("The following are built in:\n");

  for (i = 0; i < lsh_num_builtins(); i++) {
    printf("  %s\n", builtin_str[i]);
  }

  printf("Use the man command for information on other programs.\n");
  return 1;
}

当输入help之后将提示以上输出的内容

exit

int lsh_exit(char **args)
{
  return 0;
}

当输入exit后将返回0,终止之前的循环,结束程序

char *lsh_read_line(void)
{
  char *line = NULL;
  size_t bufsize = 0; // have getline allocate a buffer for us

  if (getline(&line, &bufsize, stdin) == -1){
    if (feof(stdin)) {
      exit(EXIT_SUCCESS);  // We recieved an EOF
    } else  {
      perror("readline");
      exit(EXIT_FAILURE);
    }
  }
  return line;
}

#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
char **lsh_split_line(char *line)
{
  int bufsize = LSH_TOK_BUFSIZE, position = 0;
  char **tokens = malloc(bufsize * sizeof(char*));
  char *token;

  if (!tokens) {
    fprintf(stderr, "lsh: allocation error\n");
    exit(EXIT_FAILURE);
  }

  //分解字符串
  token = strtok(line, LSH_TOK_DELIM);
  while (token != NULL) {
    tokens[position] = token;
    position++;

    if (position >= bufsize) {
      bufsize += LSH_TOK_BUFSIZE;
      tokens = realloc(tokens, bufsize * sizeof(char*));
      if (!tokens) {
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
      }
    }

    token = strtok(NULL, LSH_TOK_DELIM);
  }
  tokens[position] = NULL;
  return tokens;
}

这两个函数分别实现读取一整行和将字符串拆分,具体细节原作者提供了注释,我也加上了我自己的注释。

最后lsh_execute根据输入的内容判断调用哪个函数

int lsh_execute(char **args)
{
  int i;

  if (args[0] == NULL) {
    // An empty command was entered.
    return 1;
  }

  for (i = 0; i < lsh_num_builtins(); i++) {
    if (strcmp(args[0], builtin_str[i]) == 0) {
      return (*builtin_func[i])(args);
    }
  }

  return lsh_launch(args);
}

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值