天天看點

[ Linux ] 手動實作一個簡易版的shell

綜合上篇博文的知識,我們可以實作一個簡易的shell -->/​​*上篇文章連結*​​/

用下圖的時間軸來表示時間的發生次序。其中時間從左到右。shell由辨別符為sh的方塊代表,它随着時間的流逝從左到右移動。shell從使用者讀入字元串"ls" 。shell建議一個新的子程序,然後在子程序中運作ls程式并等待子程序結束。其中這部分包含了程序的建立,程序程式替換,程序等待部分知識,在上篇博文都有介紹。

[ Linux ] 手動實作一個簡易版的shell

然後shell讀取新的一行輸入,建立一個新的程序,在這個程序中運作程式,并等待這個程序結束。

思路和步驟

是以要寫一個shell,需要循環一下過程:

  • 擷取指令行
  • 解析指令行(使用strtok分割字元串)
  • 建立子程序(fork)
  • 替換子程序(execvp)
  • 父程序等待子程序退出(waitpid)

代碼實作

根據這些思路和步驟,我們來看一下實作的代碼:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define SEP " "
#define SIZE 128
#define NUM 1024
char command_line[NUM];
char* command_args[SIZE];

char env_buffer[NUM];
extern char** environ;

int ChangeDir(const char* new_path)
{
  chdir(new_path);
  return 0;//調用成功
}

void PutEnvInMyShell(char* new_env)
{
  putenv(new_env);
}

int main()
{
  //一個shell 本質上就是一個死循環
  while(1)
  {
    //不關心擷取這些屬性的接口
    //1.顯示提示符
    printf("[張三@我的主機名 目前目錄]# ");
    fflush(stdout);
    //2.擷取使用者輸入
    memset(command_line,'\0',sizeof(command_line)*sizeof(char));
    fgets(command_line,NUM,stdin);//擷取 輸入 stdin
    command_line[strlen(command_line) - 1] = '\0';//清空\n
    //printf("%s\n",command_line);

    //3."ls -l -a -i" --> "ls","-l","-a","-i" 字元串切分
    command_args[0] = strtok(command_line, SEP);
    int index = 1;
    //給ls添加顔色
    if(strcmp(command_args[0]/*程式名*/,"ls") == 0)
    command_args[index++] = (char*)"--color=auto";
    //strtok 截取成功 傳回字元串起始位址
    //截取失敗 傳回NULL
    while(command_args[index++] = strtok(NULL,SEP));
    // for debug
    //for(int i = 0;i<index;++i) 
    //{
    //  printf("%d:%s\n",i,command_args[i]);
    //}

    //4.TODO
    //如果直接exec*執行cd,最多隻是讓子程序進行路徑切換,
    //子程序是一運作就完畢的程序!我們在shell中,更希望
    //父程序的路徑切換
    //如果有些行為必須讓父程序shell執行,不想讓子程序
    //這種情況下不能建立子程序,隻能讓父程序自己實作對應的代碼
    //這部分由父shell自己執行的指令稱之為内建指令
    if(strcmp(command_args[0],"cd") == 0 && command_args[1] != NULL)
  {
    ChangeDir(command_args[1]);
    continue;
  }

    if(strcmp(command_args[0],"export") == 0 && command_args[1] != NULL)
  {
    strcpy(env_buffer,command_args[1]);
    PutEnvInMyShell(env_buffer);
    continue;
  }

    //5.建立子程序
    pid_t id = fork();
    if(id == 0)
  {
    //child
    //6.程式替換
    execvp(command_args[0],/*裡面儲存的就是執行的名字*/command_args);

    exit(1);//執行到這裡一定失敗了
  }
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if(ret>0)
  {
    printf("等待子程序成功: sig:%d, code:%d\n",status&0x7F,(status>>8)&0xFF);
  }
  }

  return 0;
}      

程式測試

[ Linux ] 手動實作一個簡易版的shell
[ Linux ] 手動實作一個簡易版的shell