我正在通过 git@github.com:brenns10/lsh 这个项目学习如何实现一个 Shell。今天为源码添加了第一遍注释。

还有很多问号hhh

/***************************************************************************//**

  @file         main.c

  @author       Stephen Brennan

  @date         Thursday,  8 January 2015

  @brief        LSH (Libstephen SHell)

*******************************************************************************/

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

/*
  Function Declarations for builtin shell commands:
 */
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);

/*
  List of builtin commands, followed by their corresponding functions.
 */
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 *);
}

/*
  Builtin function implementations.
*/

/**
   @brief Builtin command: change directory.
   @param args List of args.  args[0] is "cd".  args[1] is the directory.
   @return Always returns 1, to continue executing.
 */
// lsh 内置的函数中,只有 cd 是执行了有效功能的(help 用来打印帮助,exit 用于退出)
// 看起来 cd 命令使用的是 chdir 函数实现的功能
// 在 chdir 函数执行失败时(返回值不为 0 时),还会调用 perror 来打印错误信息
// 该函数总是返回 1,这是因为每个 builtin_func 都应该返回 int 值,这一点在 lsh_execute 的注释中解释过
int lsh_cd(char **args)
{
  if (args[1] == NULL) {
    fprintf(stderr, "lsh: expected argument to \"cd\"\n");
  } else {
    // 这个 chdir 是外部函数,是哪个库中的?
    if (chdir(args[1]) != 0) {
      // 这个 perror 函数也是外部的,是哪个库中的?
      perror("lsh");
    }
  }
  return 1;
}

/**
   @brief Builtin command: print help.
   @param args List of args.  Not examined.
   @return Always returns 1, to continue executing.
 */
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;
}

/**
   @brief Builtin command: exit.
   @param args List of args.  Not examined.
   @return Always returns 0, to terminate execution.
 */
// lsh_exit 通过返回 0 来使 REPL 终止,原因在 lsh_execute 的注释中有解释
int lsh_exit(char **args)
{
  return 0;
}

/**
  @brief Launch a program and wait for it to terminate.
  @param args Null terminated list of arguments (including program).
  @return Always returns 1, to continue execution.
 */
int lsh_launch(char **args)
{
  pid_t pid;
  int status;

  // 这里使用 fork 克隆了一个子进程
  pid = fork();
  // 克隆成功(pid == 0)后,使用 execvp 函数执行参数列表
  // 这个 execvp 看来也是外部函数了。它可以调用将参数列表传给 bash 执行?
  // 不对,并不是“传给bash”,bash 脚本的重定向和管道功能,lsh 并不能使用。
  // 那么 execvp 的作用究竟是什么?
  if (pid == 0) {
    // Child process
    if (execvp(args[0], args) == -1) {
      perror("lsh");
    }
    exit(EXIT_FAILURE);
  } else if (pid < 0) {
    // 子进程 fork 失败,直接报错。
    // 话说,这个情况该怎么复现呢?怎么手动使子进程 fork 失败?
    // Error forking
    perror("lsh");
  } else {
    // 这里处理 pid > 0 的情况,似乎在等待父进程?等待什么呢?
    // 等待的过程中,执行了 waitpid 函数,也是一个外部函数。
    // waitpid 函数会修改状态码 status,而 WIFEXITED 宏和 WIFSIGNALED 宏会检测 status
    // 这两个宏并没有在本文件中定义o,不过看起来是检测状态码是否代表 exited 和 signaled 的
    // Parent process
    do {
      waitpid(pid, &status, WUNTRACED);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
  }

  return 1;
}

/**
   @brief Execute shell built-in or launch program.
   @param args Null terminated list of arguments.
   @return 1 if the shell should continue running, 0 if it should terminate
   该函数的返回值为 0 时,REPL 终止
   因此,无论它返回 1、函数指针还是其他非假值,REPL 都将继续
 */
int lsh_execute(char **args)
{
  int i;

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

  // 遍历匹配命令
  // 命令的执行函数存放在一个函数指针数组 builtin_func 中,
  // 使用时只需要传入 args 就行
  for (i = 0; i < lsh_num_builtins(); i++) {
    if (strcmp(args[0], builtin_str[i]) == 0) {
      // 匹配到就调用对应的函数指针,这个函数指针显然是一定返回 int 值的
      return (*builtin_func[i])(args);
    }
  }

  // 没有匹配到就返回 lsh_launch 函数的返回值,这个函数返回一个 int 值
  // lsh_launch 函数似乎就是用来打印命令不存在的报错值的,为什么还需要传入 args ?为什么里面还需要开子进程?
  // 原来,没有匹配到内置命令,lsh_launch 就会将参数作为 bash 的命令运行,所以 pwd、cp、rm 这样的命令仍然可以在 lsh 中使用。
  // 但这是怎么做到的呢?
  return lsh_launch(args);
}

/**
   @brief Read a line of input from stdin.
   @return The line from stdin.
 */
char *lsh_read_line(void)
{
#ifdef LSH_USE_STD_GETLINE
  char *line = NULL;
  ssize_t bufsize = 0; // have getline allocate a buffer for us
  if (getline(&line, &bufsize, stdin) == -1) {
    if (feof(stdin)) {
      exit(EXIT_SUCCESS);  // We received an EOF
    } else  {
      perror("lsh: getline\n");
      exit(EXIT_FAILURE);
    }
  }
  return line;
#else
#define LSH_RL_BUFSIZE 1024
  int bufsize = LSH_RL_BUFSIZE;
  int position = 0;
  char *buffer = malloc(sizeof(char) * bufsize);
  int c;

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

  while (1) {
    // Read a character
    c = getchar();

    if (c == EOF) {
      exit(EXIT_SUCCESS);
    } else if (c == '\n') {
      buffer[position] = '\0';
      return buffer;
    } else {
      buffer[position] = c;
    }
    position++;

    // If we have exceeded the buffer, reallocate.
    if (position >= bufsize) {
      bufsize += LSH_RL_BUFSIZE;
      buffer = realloc(buffer, bufsize);
      if (!buffer) {
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
      }
    }
  }
#endif
}

#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
/**
   @brief Split a line into tokens (very naively).
   @param line The line.
   @return Null-terminated array of tokens.
 */
char **lsh_split_line(char *line)
{
  int bufsize = LSH_TOK_BUFSIZE, position = 0;
  char **tokens = malloc(bufsize * sizeof(char*));
  char *token, **tokens_backup;

  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_backup = tokens;
      tokens = realloc(tokens, bufsize * sizeof(char*));
      if (!tokens) {
		free(tokens_backup);
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
      }
    }

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

/**
   @brief Loop getting input and executing it.
 */
void lsh_loop(void)
{
  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);
}

/**
   @brief Main entry point.
   @param argc Argument count.
   @param argv Argument vector.
   @return status code
 */
int main(int argc, char **argv)
{
  // Load config files, if any.

  // Run command loop.
  lsh_loop();

  // Perform any shutdown/cleanup.

  return EXIT_SUCCESS;
}