1. 程式人生 > >Linux C實現簡單的shell

Linux C實現簡單的shell

Linux C下實現簡單的Shell

宗旨:技術的學習是有限的,分享的精神是無限的。

【需求描述】

用各種C函式實現一個簡單的互動式Shell

1給出提示符,讓使用者輸入一行命令,識別程式名和引數並呼叫適當的exec函式執行程式,待執行完成後再次給出提示符。

2、識別和處理以下符號:簡單的標準輸入輸出重定向( <>),dup2然後exec管道(|): Shell程序先呼叫pipe建立一對管道描述符,然後fork出兩個子程序,一個子程序關閉讀端,呼叫dup2把寫端賦給標準輸出,另一個子程序關閉寫端,呼叫dup2把讀端賦給標準輸入,兩個子程序分別呼叫exec執行程式,而

Shell程序把管道的兩端都關閉,呼叫wait待兩個子程序終止。程式應該可以處理以下命令:
○ls
-l-R○>○file1○
○cat○<○file1○|○wc
-c○>○file1○
表示零個或多個空格,表示一個或多個空格

//=====================================================================

一、結構定義

       使用者輸入一行命令,將分解後的命令存放在arg中,最多10個。之所以定義輸入、輸出重定向檔名是為了方便使用。還要餘外處理重定向和管道問題。必須建立子程序(有多少命令就有多少個子程序),然後呼叫exec函式族。

typedef struct command
{
  char *arg[SHELL_CMD_MAX_LENGTH]; // 命令列引數,最多10個引數
  char *input_file; // 存放輸入重定向的檔名
  char *output_file; // 存放輸出重定向的檔名
} cmd_t;<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

二、介面定義

管道處理用strtok_r()函式,重定向處理使用strtok()函式,仔細研究一下他們的區別。

1、管道個數【管道的個數 =(命令個數 1)】

static uint8_t shell_parse_pipe(char *data, cmd_tcmd[]);

2、重定向

static uint8_t shell_parse_cmd_line(char *data,cmd_t *cmd);

填充命令結構體中的各個成員,輸入輸出填充input_file,output_file成員,命令填充arg成員。命令成員的最後一個字元指向NULL。

3、內建命令的單獨處理 --- cd【內建命令都需要一個個處理 --- 如type cd檢視命令是內建還是外部shell】

int shell_cd_command(char *command, char *path);

/*
只能處理外部命令,內建命令不能處理,內建命令要一個一個單獨實現
type命令可以檢視命令是外部命令還是內建命令:type cd // 是shell的內建
*/

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

#define SHELL_CMD_MAX_COUNT        10
#define SHELL_CMD_MAX_LENGTH       20
#define SHELL_IN_OUT_FILE_MAX_SIZE 20
#define SHELL_BUFFER               64
#define SHELL_PIPE_MAX_COUNT       10
#define SHELL_PIPE_READ_WRITE      2

typedef unsigned int uint32_t;
typedef unsigned char uint8_t;

typedef struct command
{
  char *arg[SHELL_CMD_MAX_LENGTH]; // 命令列引數,最多10個引數
  char *input_file; // 存放輸入重定向的檔名
  char *output_file; // 存放輸出重定向的檔名
} cmd_t;


/* 以空格符分開命令列字串 */
static uint8_t
shell_parse_cmd_line(char *data, cmd_t *cmd)
{
  uint32_t i = 0;

  cmd->input_file = NULL;
  cmd->output_file = NULL;

  char *token = strtok(data, " ");

  while(token)
  {
    /*如果>後面有空格,那麼執行完strtok後,空格被替換成'\0',*(p+1)就是'\0',為假,不執行cmd_buf->out=p+1*/
    /*如果>後面沒有空格,那麼執行完strtok後,>符號被替換成'\0'了,直接呼叫strtok函式*/
    if(*token == '>')
    {
      if(*(token + 1))
      {
        cmd->output_file = token + 1;
      }
      else
      {
        cmd->output_file = strtok(NULL, " ");
      }

    }
    else if(*token == '<')
    {
      if(*(token + 1))
      {
        cmd->input_file = token + 1;
      }
      else
      {
        cmd->input_file = strtok(NULL, " ");
      }

    }
    else
    {
      printf("token....\n");
      /*如果獲取的命令列引數不是>或者<,那麼就將它們儲存在arg中*/
      cmd->arg[i++] = token;

    }
    token = strtok(NULL, " ");
  }

  cmd->arg[i] = NULL;

  return 0;
}

/* 以管道符分開命令列字串 */
static uint8_t
shell_parse_pipe(char *data, cmd_t cmd[])
{
  uint8_t count = 0;
  char *temp;
  char *token = strtok_r(data, "|",  &temp);

  while(token)
  {
    /*以管道符分開的第一個字串存放在結構陣列cmd[0]中,第二個字串存放在結構陣列cmd[1]中,依次遞推*/
    shell_parse_cmd_line(token, &cmd[count++]);
    token = strtok_r(NULL, "|", &temp);
  }

  return count;
}

/* cd內部命令 */
int shell_cd_command(char *command, char *path)
{
  int return_value = 0;


  if(strncmp(command, "cd", 2) == 0)
    if((return_value = chdir(path)) < 0)
    {
      perror("chdir");
    }
  return return_value;
}

static void
shell_process(void)
{
  char cmd_buf[SHELL_BUFFER], pathname[SHELL_BUFFER];
  cmd_t cmds[SHELL_CMD_MAX_COUNT];
  pid_t pid; // 程序pid
  /* 輸入輸出重定向檔案描述符, 管道個數, 10個管道描述符, 管道分隔的命令個數*/
  uint8_t fd_in, fd_out, pipe_num, pipe_fd[SHELL_PIPE_MAX_COUNT][SHELL_PIPE_READ_WRITE], cmd_num = 0;
  uint8_t i = 0, j = 0;

  while(1)
  {
    memset(pathname, 0, sizeof(pathname));
    getcwd(pathname, sizeof(pathname)); /* 獲取當前工作路徑 */
    printf("[libang--%s--]$", pathname);
    fflush(stdout); /* 重新整理緩衝區,這連續四行只是為了顯示好看而已,不要也可以 */

    memset(cmd_buf, 0, sizeof(cmd_buf));
    fgets(cmd_buf, sizeof(cmd_buf), stdin);
    cmd_buf[strlen(cmd_buf) - 1] = '\0'; // 獲取輸入的命令到cmd_buf

    cmd_num = shell_parse_pipe(cmd_buf, cmds);

    shell_cd_command(cmds[0].arg[0], cmds[0].arg[1]); // 處理cd內建命令

    pipe_num = cmd_num - 1;
    if(pipe_num > SHELL_PIPE_MAX_COUNT)
    {
      continue;
    }
    /* 一個管道有in和out, 建立pipe_num個管道 */
    for(i = 0; i < pipe_num; ++i)
    {
      pipe(pipe_fd[i]);
    }

    /* 這一輪迴圈,建立了pipe_num+1個程序,其中一個父程序,pipe_num個子程序 */
    for(i = 0; i < cmd_num; ++i)
    {
      if((pid = fork()) < 0)
      {
        printf("fork fail!\n");
        exit(1);
      }
      if(pid == 0)
      {
        break;
      }
    }

    /* 有多少個命令就會執行多少個子程序,最終呼叫exec函式族 */
    if(pid == 0)
    {
      /* 上面迴圈中,子程序break,所以執行下面的語句,此時i就和迴圈變數i一樣 */
      if(cmds[i].input_file)
      {
        /* 重定向輸入 */
        if((fd_in = open(cmds[i].input_file, O_RDONLY)) < 0)
        {
          perror("open fail!\n");
        }
        dup2(fd_in, STDIN_FILENO);
        close(fd_in);
      }

      if(cmds[i].output_file)
      {
        /* 重定向輸出 */
        if((fd_out = open(cmds[i].output_file, O_RDWR | O_CREAT | O_TRUNC), 0644) < 0)
        {
          perror("open fail!\n");
        }
        dup2(fd_out, STDOUT_FILENO);
        close(fd_out);
      }
      /* 管道是程序間通訊的一種方式,輸入命令中有管道 */
      if(pipe_num)
      {
        /* 第一個子程序,讀入寫出,關閉讀端,把標準輸出重定向到寫端*/
        if(0 == i)
        {
          close(pipe_fd[i][0]);
          dup2(pipe_fd[i][1], STDOUT_FILENO); // 本來執行結果是在標準輸出上
          close(pipe_fd[i][1]);

          /* 關閉掉多餘的管道 */
          for(j = 1; j < pipe_num; ++j)
          {
            close(pipe_fd[j][0]);
            close(pipe_fd[j][1]);
          }
        }
        /* 最後一個子程序,關閉寫端,把標準輸入重定向到讀端 */
        else if(pipe_num == i)
        {
          close(pipe_fd[i - 1][0]);
          dup2(pipe_fd[i - 1][0], STDIN_FILENO);
          close(pipe_fd[i - 1][0]);
          /* 關閉掉多餘的管道讀寫端 */
          for(j = 0; j < pipe_num - 1; ++j)
          {
            close(pipe_fd[j][0]);
            close(pipe_fd[j][1]);
          }
        }
        /* 1~pipe_num-1, */
        else
        {
          dup2(pipe_fd[i - 1][0], STDIN_FILENO);
          close(pipe_fd[i - 1][0]);

          dup2(pipe_fd[i][1], STDOUT_FILENO);
          close(pipe_fd[i][1]);

          for(j = 0; j < pipe_num; ++j)
          {
            if((j != i - 1) || j != i)
            {
              close(pipe_fd[j][0]);
              close(pipe_fd[j][1]);
            }
          }
        }
      }

      /* arg第1個引數是命令,後面的引數是命令選項如:-l */
      execvp(cmds[i].arg[0], cmds[i].arg);
    }
    else // 父程序阻塞
    {
      for(i = 0; i < pipe_num; ++i)
      {
        close(pipe_fd[i][0]);
        close(pipe_fd[i][1]);
      }
      for(i = 0; i < cmd_num; ++i)
      {
        wait(NULL);
      }
    }
  }
}

int main(int argc, char **argv)
{
  shell_process();

  return 0;
}