使用c语言实现一个简易shell——Wsh

本文将介绍一个使用 C 语言编写的简单 Shell,这个 Shell 可以执行一些基本命令,例如 cd 和 exit。此外,Shell 还支持管道和重定向操作。

系统编程需知

1.进程间通信:

程序中使用了管道(pipe)进行两个子进程之间的通信,使用了标准输入输出重定向进行输出到文件的操作。

2.系统调用:

程序中使用了多个系统调用,如gethostname() 、getuid()、getpwuid()、chdir()、execvp()、dup2()、 freopen()等。通过这些系统调用可以实现对进程、文件、目录等的操作。

3.进程控制:

程序中使用了多个进程控制函数,如fork() 、wait() 和 waitpid() 等。通过这些函数可以实现父子进程分离、子进程退出状态的获取等功能。

4.GNU Readline 库:

程序中使用了 GNU Readline 库提供的函数,如readline() 和 add_history()等,实现了命令行的交互式输入和历史命令的保存和显示。

主要框架

1.初始化

main()
在程序开始运行时,首先需要进行一些初始化操作。例如初始化一些变量、启用历史记录、记录上一个目录等。这些初始化操作可以在函数中完成,也可以单独写一个函数来实现。

2.循环读取用户输入并解析命令

在进入循环之前,需要设置一个循环标记,用于控制程序何时结束。接着进入一个无限循环,在每次循环中,首先获取当前工作目录,并根据该目录设置提示符;然后读取用户输入,并将其添加到历史记录中;接着调用 函数来解析并执行命令;最后,调用 函数来初始化各种参数和标志位。

3.解析命令并执行

parse()
在 函数中,首先判断是否需要在后台运行,然后将字符串按照管道符号 “|” 进行分割,如果有多个子命令则创建一个管道;接着使用 函数来处理重定向符号 “<” 和 “>”,确定输入或输出文件。最后,根据用户输入的第一个命令查找对应的可执行文件或内置命令,并调用相应函数来运行命令。

4.执行外部命令

execute()execvp()
在 函数中,首先关闭多余的文件描述符,然后根据输入输出重定向符号 “<” 和 “>” 来打开对应的文件,将标准输入、输出、错误输出重定向到文件或管道,最后使用 函数来执行命令。

5.执行内置命令

execute()
在 函数中,首先判断用户输入的是哪个内置命令,然后调用相应函数来执行。对于 cd 命令,它需要改变当前工作目录,而对于 exit 命令,则直接退出程序。

函数实现细节

在 main 函数中,首先初始化了一些变量,然后进入了一个无限循环。循环体内部分别进行了如下操作:设置提示符、读取用户输入并记录历史、解析用户命令、执行命令或报错、初始化变量。

解析命令主要是为了识别出是否是内置命令(例如 cd 和 exit)、是否需要管道操作和是否需要重定向操作。如果是内置命令则直接调用相应函数处理,否则 fork 一个子进程来执行该命令。如果需要管道,则需要再次 fork 一个子进程,前面的子进程执行第一个命令并把输出发送给后面的子进程,后面的子进程执行第二个命令。如果需要重定向,则关闭标准输出,重新打开一个文件,然后执行命令,最后再关闭文件。

#include <pwd.h>
#include <readline/history.h>
#include <readline/readline.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAX_LINE_LEN 256
#define MAX_CMD_LEN 1024
#define CMDMAX 256
#define ARGMAX 10

char lastdir[100];
char command[CMDMAX][MAX_CMD_LEN] = {0};
int pipecnt = 0;
int pipe_location;

char **cutline(char *buf, const char *delim);
void initial();
void init_lastdir();
void history_setup();
void loop();
void history_finish();
void set_prompt(char *prompt);
char *get_name();
int parse(char *buf);
void execute(int argcnt);
void commandWithPipe(int index, int n, int input_fd);
// void handle_signal(int sig) {  }
int main() {
  // 提示符
  char prompt[BUFSIZ];
  int argcnt = 0;
  char *line;
  // signal(SIGINT, handle_signal);

  // getpwd
  init_lastdir();
  // gethistory
  history_setup();
  while (1) {
    set_prompt(prompt);
    if (!(line = readline(prompt)))
      break;
    if (*line)
      add_history(line);
    argcnt = parse(line);
    free(line);
    execute(argcnt);
    initial(); // initial
  }
  history_finish();
}
/* 输出提示符 */
void set_prompt(char *prompt) {
  char hostname[100];
  char cwd[100];
  char super = '#';
  // to cut the cwd by "/"
  char delims[] = "/";
  struct passwd *pwp;

  if (gethostname(hostname, sizeof(hostname)) == -1) {
    // get hostname failed
    strcpy(hostname, "unknown");
  } // if
  // getuid() get user id ,then getpwuid get the user information by user id
  pwp = getpwuid(getuid());
  if (!(getcwd(cwd, sizeof(cwd)))) {
    // get cwd failed
    strcpy(cwd, "unknown");
  } // if
  char cwdcopy[100];
  strcpy(cwdcopy, cwd);
  char *first = strtok(cwdcopy, delims);
  char *second = strtok(NULL, delims);
  // if at home
  if (!(strcmp(first, "home")) && !(strcmp(second, pwp->pw_name))) {
    int offset = strlen(first) + strlen(second) + 2;
    char newcwd[100];
    char *p = cwd;
    char *q = newcwd;

    p += offset;
    while ((*(q++) = *(p++)))
      ;
    char tmp[100];
    strcpy(tmp, "~");
    strcat(tmp, newcwd);
    strcpy(cwd, tmp);
  }

  if (getuid() == 0) // if super
    super = '#';
  else
    super = '$';
  char *name = get_name();
  sprintf(
      prompt,
      "\001\e[1;30;44m[%s]\001\e[1;30;42m\002%s@%s "
      "\001\e[0m\002\001\e[1;30;41m\002%s\001\e[0m\002\033[43;1m%c➜ \033[0m",
      name, pwp->pw_name, hostname, cwd, super);
}
// 得到系统发行版本
char *get_name() {

  FILE *fp;
  char line[MAX_LINE_LEN];
  char *name = NULL;

  fp = fopen("/etc/os-release", "r");
  if (fp == NULL) {
    printf("Failed to open /etc/os-release\n");
    return NULL;
  }

  while (fgets(line, MAX_LINE_LEN, fp)) {
    // Remove newline character at the end of the line
    line[strcspn(line, "\n")] = 0;

    // Find the first '=' character in the line
    char *equals_pos = strchr(line, '=');
    if (equals_pos == NULL) {
      continue;
    }

    // Split the line into key and value
    *equals_pos = '\0';
    char *key = line;
    char *value = equals_pos + 1;

    // Remove quotes from the value string
    size_t value_len = strlen(value);
    if (value[0] == '"' && value[value_len - 1] == '"') {
      value[value_len - 1] = '\0';
      value++;
    }

    // Check if the key is NAME or VERSION_ID
    if (strcmp(key, "NAME") == 0) {
      name = strdup(value);
    }
  }

  fclose(fp);

  if (name != NULL) {
    return name;
  } else {
    printf("Failed to find NAME in /etc/os-release\n");
    return NULL;
  }
}

/* 获取历史输入 */
void history_setup() {
  using_history();
  stifle_history(50);
  read_history("/tmp/msh_history");
}

void init_lastdir() { getcwd(lastdir, sizeof(lastdir)); }

void history_finish() {
  append_history(history_length, "/tmp/msh_history");
  history_truncate_file("/tmp/msh_history", history_max_entries);
}
/* parse */
int parse(char *buf) {
  char *token;
  int cnt = 0;
  int head = 0;
  int len = strlen(buf);
  for (int i = 0; i < len; i++) {
    if (buf[i] == '|') {
      if (pipecnt == 0) {
        pipe_location = cnt;
      }
      pipecnt++;
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      // strcpy(command[cnt], "|");
      // cnt++;
      head = i + 1;
    }
    if (buf[i] == '<') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], "<");
      cnt++;
      head = i + 1;
    }
    if (buf[i] == '>' && buf[i + 1] != '>') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], ">");
      cnt++;
      head = i + 1;

    } else if (buf[i] == '>' && buf[i + 1] == '>') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], ">>");
      cnt++;
      head = i + 2;
      i++;
    }
    if (buf[i] == '&') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], "&");
      cnt++;
      head = i + 1;
    }
  }
  strncpy(command[cnt], buf + head, len - head);
  cnt++;
  return cnt;
}
/* execute */
void execute(int argcnt) {
  int key = 0;
  for (int i = 0; i < argcnt; i++) {
    if (i == pipe_location) {
      key = 1;
    }
    i += pipecnt;
    commandWithPipe(pipe_location, pipecnt, STDIN_FILENO);

    if (strcmp(command[i], "<") == 0) {
      key = 1;
      printf("<");
      exit(0);
    }
    if (strcmp(command[i], ">") == 0) {
      key = 1;
      printf(">");
      exit(0);
    }
    if (strcmp(command[i], ">>") == 0) {
      key = 1;
      printf(">>");
      exit(0);
    }
    if (strcmp(command[i], "&") == 0) {
      key = 1;
      printf("&");
      exit(0);
    }
    if (strcmp(command[i], "exit") == 0) {
      key = 1;
      exit(0);
    }
  }
  for (int i = 0; key == 0 && i < argcnt; i++) {
    char **args;
    args = cutline(command[i], " ");
    if (strcmp(args[i], "exit") == 0) {
    exit(EXIT_SUCCESS);
  }
    pid_t pid;
    pid = fork();
    if (pid < 0) {
      perror("fork error\n");
      exit(0);
    } else if (pid == 0) {
      execvp(args[0], args);
    } else if (pid > 0) {
      wait(NULL);
    }
  }
}
/* pipecmd */
void commandWithPipe(int index, int pipecnt, int input_fd) {
  // 创建管道
  pid_t pid;
  int arg_count = pipecnt + 1;
  char *args[arg_count][ARGMAX];
  int pd[pipecnt][2];
  int status;
  for (int i = 0; i < pipecnt; i++) {
    pipe(pd[i]);
  }

  // 分割命令
  for (int in = index, j = 0; j <= pipecnt; j++, in++) { // command[i]
    int k = 0;
    char *token = strtok(command[in], " ");
    while (token != NULL && arg_count < ARGMAX) {
      args[j][k++] = token;
      // printf("args[%d][%d]=%s\n", j, k - 1, args[j][k - 1]);
      token = strtok(NULL, " ");
    }
    // printf("args[%d] END\n", j);
    args[j][k] = NULL;
  }
  int i = 0;
  // 父进程循环创建多个并列子进程
  for (i = 0; i < arg_count; i++) {
    pid = fork();
    if (pid == 0) // 子进程出口
      break;
  }
  if (pid < 0) {
    perror("fork()");
  } else if (pid == 0 && pipecnt > 0) {
    if (i == 0) {
      dup2(pd[0][1], STDOUT_FILENO);
      close(pd[0][0]);
      for (int j = 1; j < pipecnt; j++) {
        close(pd[j][1]);
        close(pd[j][0]);
      }
    } else if (i == pipecnt) // 最后一个进程
    {
      dup2(pd[i - 1][0], 0); // 打开读端
      close(pd[i - 1][1]);   // 关闭写端
                             // 其他进程读写端全部关闭
      for (int j = 0; j < pipecnt - 1; j++) {
        close(pd[j][1]);
        close(pd[j][0]);
      }
    } else // 中间进程
    {
      dup2(pd[i - 1][0], 0); // 前一个管道的读端打开
      close(pd[i - 1][1]);   // 前一个写端关闭
      dup2(pd[i][1], 1);     // 后一个管道的写端打开
      close(pd[i][0]);       // 后一个读端关闭
      // 其他的全部关闭
      for (int j = 0; j < pipecnt; j++) {
        if (j != i && j != (i - 1)) {
          close(pd[j][0]);
          close(pd[j][1]);
        }
      }
    }
  }
  if (pid == 0) {
    execvp(args[i][0], args[i]); // 执行命令
    perror("execvp");
    exit(1);
  }

  // 父进程关闭所有的管道
  for (i = 0; i < pipecnt; i++) {
    close(pd[i][0]);
    close(pd[i][1]); // 父进程端口全部关掉
  }
  for (int j = 0; j < arg_count; j++) // 父进程等待子进程
    wait(NULL);
}

char **cutline(char *buf, const char *delim) {
  int num_args = 0;
  char **args = (char **)malloc(sizeof(char *) * 10); // 最多支持 10 个参数
  char *token;
  // 解析单个命令及其参数
  token = strtok(buf, delim);
  while (token != NULL) {
    args[num_args++] = token;
    token = strtok(NULL, delim);
  }
  args[num_args] = NULL; // 参数列表以 NULL 结尾
  return args;
}
void initial() {
  for (int i = 0; i < CMDMAX; i++) {
    memset(command[i], '\0', MAX_CMD_LEN);
  }
  pipecnt = 0;
}#include <pwd.h>
#include <readline/history.h>
#include <readline/readline.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAX_LINE_LEN 256
#define MAX_CMD_LEN 1024
#define CMDMAX 256
#define ARGMAX 10

char lastdir[100];
char command[CMDMAX][MAX_CMD_LEN] = {0};
int pipecnt = 0;
int pipe_location;

char **cutline(char *buf, const char *delim);
void initial();
void init_lastdir();
void history_setup();
void loop();
void history_finish();
void set_prompt(char *prompt);
char *get_name();
int parse(char *buf);
void execute(int argcnt);
void commandWithPipe(int index, int n, int input_fd);
// void handle_signal(int sig) {  }
int main() {
  // 提示符
  char prompt[BUFSIZ];
  int argcnt = 0;
  char *line;
  // signal(SIGINT, handle_signal);

  // getpwd
  init_lastdir();
  // gethistory
  history_setup();
  while (1) {
    set_prompt(prompt);
    if (!(line = readline(prompt)))
      break;
    if (*line)
      add_history(line);
    argcnt = parse(line);
    free(line);
    execute(argcnt);
    initial(); // initial
  }
  history_finish();
}
/* 输出提示符 */
void set_prompt(char *prompt) {
  char hostname[100];
  char cwd[100];
  char super = '#';
  // to cut the cwd by "/"
  char delims[] = "/";
  struct passwd *pwp;

  if (gethostname(hostname, sizeof(hostname)) == -1) {
    // get hostname failed
    strcpy(hostname, "unknown");
  } // if
  // getuid() get user id ,then getpwuid get the user information by user id
  pwp = getpwuid(getuid());
  if (!(getcwd(cwd, sizeof(cwd)))) {
    // get cwd failed
    strcpy(cwd, "unknown");
  } // if
  char cwdcopy[100];
  strcpy(cwdcopy, cwd);
  char *first = strtok(cwdcopy, delims);
  char *second = strtok(NULL, delims);
  // if at home
  if (!(strcmp(first, "home")) && !(strcmp(second, pwp->pw_name))) {
    int offset = strlen(first) + strlen(second) + 2;
    char newcwd[100];
    char *p = cwd;
    char *q = newcwd;

    p += offset;
    while ((*(q++) = *(p++)))
      ;
    char tmp[100];
    strcpy(tmp, "~");
    strcat(tmp, newcwd);
    strcpy(cwd, tmp);
  }

  if (getuid() == 0) // if super
    super = '#';
  else
    super = '$';
  char *name = get_name();
  sprintf(
      prompt,
      "\001\e[1;30;44m[%s]\001\e[1;30;42m\002%s@%s "
      "\001\e[0m\002\001\e[1;30;41m\002%s\001\e[0m\002\033[43;1m%c➜ \033[0m",
      name, pwp->pw_name, hostname, cwd, super);
}
// 得到系统发行版本
char *get_name() {

  FILE *fp;
  char line[MAX_LINE_LEN];
  char *name = NULL;

  fp = fopen("/etc/os-release", "r");
  if (fp == NULL) {
    printf("Failed to open /etc/os-release\n");
    return NULL;
  }

  while (fgets(line, MAX_LINE_LEN, fp)) {
    // Remove newline character at the end of the line
    line[strcspn(line, "\n")] = 0;

    // Find the first '=' character in the line
    char *equals_pos = strchr(line, '=');
    if (equals_pos == NULL) {
      continue;
    }

    // Split the line into key and value
    *equals_pos = '\0';
    char *key = line;
    char *value = equals_pos + 1;

    // Remove quotes from the value string
    size_t value_len = strlen(value);
    if (value[0] == '"' && value[value_len - 1] == '"') {
      value[value_len - 1] = '\0';
      value++;
    }

    // Check if the key is NAME or VERSION_ID
    if (strcmp(key, "NAME") == 0) {
      name = strdup(value);
    }
  }

  fclose(fp);

  if (name != NULL) {
    return name;
  } else {
    printf("Failed to find NAME in /etc/os-release\n");
    return NULL;
  }
}

/* 获取历史输入 */
void history_setup() {
  using_history();
  stifle_history(50);
  read_history("/tmp/msh_history");
}

void init_lastdir() { getcwd(lastdir, sizeof(lastdir)); }

void history_finish() {
  append_history(history_length, "/tmp/msh_history");
  history_truncate_file("/tmp/msh_history", history_max_entries);
}
/* parse */
int parse(char *buf) {
  char *token;
  int cnt = 0;
  int head = 0;
  int len = strlen(buf);
  for (int i = 0; i < len; i++) {
    if (buf[i] == '|') {
      if (pipecnt == 0) {
        pipe_location = cnt;
      }
      pipecnt++;
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      // strcpy(command[cnt], "|");
      // cnt++;
      head = i + 1;
    }
    if (buf[i] == '<') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], "<");
      cnt++;
      head = i + 1;
    }
    if (buf[i] == '>' && buf[i + 1] != '>') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], ">");
      cnt++;
      head = i + 1;

    } else if (buf[i] == '>' && buf[i + 1] == '>') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], ">>");
      cnt++;
      head = i + 2;
      i++;
    }
    if (buf[i] == '&') {
      strncpy(command[cnt], buf + head, i - head);
      cnt++;
      strcpy(command[cnt], "&");
      cnt++;
      head = i + 1;
    }
  }
  strncpy(command[cnt], buf + head, len - head);
  cnt++;
  return cnt;
}
/* execute */
void execute(int argcnt) {
  int key = 0;
  for (int i = 0; i < argcnt; i++) {
    if (i == pipe_location) {
      key = 1;
    }
    i += pipecnt;
    commandWithPipe(pipe_location, pipecnt, STDIN_FILENO);

    if (strcmp(command[i], "<") == 0) {
      key = 1;
      printf("<");
      exit(0);
    }
    if (strcmp(command[i], ">") == 0) {
      key = 1;
      printf(">");
      exit(0);
    }
    if (strcmp(command[i], ">>") == 0) {
      key = 1;
      printf(">>");
      exit(0);
    }
    if (strcmp(command[i], "&") == 0) {
      key = 1;
      printf("&");
      exit(0);
    }
    if (strcmp(command[i], "exit") == 0) {
      key = 1;
      exit(0);
    }
  }
  for (int i = 0; key == 0 && i < argcnt; i++) {
    char **args;
    args = cutline(command[i], " ");
    if (strcmp(args[i], "exit") == 0) {
    exit(EXIT_SUCCESS);
  }
    pid_t pid;
    pid = fork();
    if (pid < 0) {
      perror("fork error\n");
      exit(0);
    } else if (pid == 0) {
      execvp(args[0], args);
    } else if (pid > 0) {
      wait(NULL);
    }
  }
}
/* pipecmd */
void commandWithPipe(int index, int pipecnt, int input_fd) {
  // 创建管道
  pid_t pid;
  int arg_count = pipecnt + 1;
  char *args[arg_count][ARGMAX];
  int pd[pipecnt][2];
  int status;
  for (int i = 0; i < pipecnt; i++) {
    pipe(pd[i]);
  }

  // 分割命令
  for (int in = index, j = 0; j <= pipecnt; j++, in++) { // command[i]
    int k = 0;
    char *token = strtok(command[in], " ");
    while (token != NULL && arg_count < ARGMAX) {
      args[j][k++] = token;
      // printf("args[%d][%d]=%s\n", j, k - 1, args[j][k - 1]);
      token = strtok(NULL, " ");
    }
    // printf("args[%d] END\n", j);
    args[j][k] = NULL;
  }
  int i = 0;
  // 父进程循环创建多个并列子进程
  for (i = 0; i < arg_count; i++) {
    pid = fork();
    if (pid == 0) // 子进程出口
      break;
  }
  if (pid < 0) {
    perror("fork()");
  } else if (pid == 0 && pipecnt > 0) {
    if (i == 0) {
      dup2(pd[0][1], STDOUT_FILENO);
      close(pd[0][0]);
      for (int j = 1; j < pipecnt; j++) {
        close(pd[j][1]);
        close(pd[j][0]);
      }
    } else if (i == pipecnt) // 最后一个进程
    {
      dup2(pd[i - 1][0], 0); // 打开读端
      close(pd[i - 1][1]);   // 关闭写端
                             // 其他进程读写端全部关闭
      for (int j = 0; j < pipecnt - 1; j++) {
        close(pd[j][1]);
        close(pd[j][0]);
      }
    } else // 中间进程
    {
      dup2(pd[i - 1][0], 0); // 前一个管道的读端打开
      close(pd[i - 1][1]);   // 前一个写端关闭
      dup2(pd[i][1], 1);     // 后一个管道的写端打开
      close(pd[i][0]);       // 后一个读端关闭
      // 其他的全部关闭
      for (int j = 0; j < pipecnt; j++) {
        if (j != i && j != (i - 1)) {
          close(pd[j][0]);
          close(pd[j][1]);
        }
      }
    }
  }
  if (pid == 0) {
    execvp(args[i][0], args[i]); // 执行命令
    perror("execvp");
    exit(1);
  }

  // 父进程关闭所有的管道
  for (i = 0; i < pipecnt; i++) {
    close(pd[i][0]);
    close(pd[i][1]); // 父进程端口全部关掉
  }
  for (int j = 0; j < arg_count; j++) // 父进程等待子进程
    wait(NULL);
}

char **cutline(char *buf, const char *delim) {
  int num_args = 0;
  char **args = (char **)malloc(sizeof(char *) * 10); // 最多支持 10 个参数
  char *token;
  // 解析单个命令及其参数
  token = strtok(buf, delim);
  while (token != NULL) {
    args[num_args++] = token;
    token = strtok(NULL, delim);
  }
  args[num_args] = NULL; // 参数列表以 NULL 结尾
  return args;
}
void initial() {
  for (int i = 0; i < CMDMAX; i++) {
    memset(command[i], '\0', MAX_CMD_LEN);
  }
  pipecnt = 0;
}

总的来说,这是一个相对简单的 Shell 程序,通过使用Linux操作系统提供的API和库函数来进行开发,实现了一些基本功能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值