【Linux 小实战】自定义 Shell 的编写

文章目录

1.输出自己的命令行

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define SIZE 256

const char* GetUserName()
{
  const char* name = getenv("USER");
  if (name == NULL) return "None";

  return name;
}

const char* GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  if (hostname == NULL) return "None";

  return hostname;
}

const char* GetCwd()
{
  const char* cwd = getenv("PWD");
  if (cwd == NULL) return "None";

  return cwd;
}

void  MakeCommandLineAndPrint()
{
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, cwd);
  printf("%s", line);
  fflush(stdout);
}


int main()
{
  //1.输出自己的命令行
  MakeCommandLineAndPrint();
  sleep(5); //用来测试 MakeCommandLinePrint函数中fflush刷新缓冲区的作用

  return 0;
}

2.获取用户命令字符串

  • 使用 char *fgets(char *s, int size, FILE *stream); 库函数
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SIZE 256
#define ZERO '\0'

const char* GetUserName()
{
  const char* name = getenv("USER");
  if (name == NULL) return "None";

  return name;
}

const char* GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  if (hostname == NULL) return "None";

  return hostname;
}

const char* GetCwd()
{
  const char* cwd = getenv("PWD");
  if (cwd == NULL) return "None";

  return cwd;
}

void  MakeCommandLineAndPrint()
{
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, cwd);
  printf("%s", line);
  fflush(stdout);
}

int GetUserCommand(char command[], size_t n)
{
  char* s = fgets(command, n, stdin);

  if (s == NULL)  return -1;

  //目的是为了解决 "\n"被都进来的情况
  command[strlen(command) - 1] = ZERO;

  return strlen(command);
}

int main()
{
  //1.输出自己的命令行
  MakeCommandLineAndPrint();

  //2.获取用户输入的命令字符串
  char usercommand[SIZE];
  int n = GetUserCommand(usercommand, sizeof(usercommand));

  printf("%s\n", usercommand);

  return 0;
}

3.分割命令行字符串

》》 strtok() 分割的使用

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32


const char* GetUserName()
{
  const char* name = getenv("USER");
  if (name == NULL) return "None";

  return name;
}

const char* GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  if (hostname == NULL) return "None";

  return hostname;
}

const char* GetCwd()
{
  const char* cwd = getenv("PWD");
  if (cwd == NULL) return "None";

  return cwd;
}

void  MakeCommandLineAndPrint()
{
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, cwd);
  printf("%s", line);
  fflush(stdout);
}

int GetUserCommand(char command[], size_t n)
{
  char* s = fgets(command, n, stdin);

  if (s == NULL)  return -1;

  //目的是为了解决 "\n"被都进来的情况
    command[strlen(command) - 1] = ZERO;

  return strlen(command);
}

char* gArgv[NUM];

void SplitCommand(char command[], size_t n)
{
  gArgv[0] = strtok(command, SEP);

  int index = 1;
  while(gArgv[index++] = strtok(NULL, SEP));

}

int main()
{
  //1.输出自己的命令行
  MakeCommandLineAndPrint();

  //2.获取用户输入的命令字符串
  char usercommand[SIZE];
  int n = GetUserCommand(usercommand, sizeof(usercommand));

  //3.命令行字符串分割
  SplitCommand(usercommand, sizeof(usercommand));

  for (int i = 0; gArgv[i]; i ++)
    printf("gArgv[%d]:%s\n",i, gArgv[i]);

  return 0;
}

4.开始执行,第一版本Shell

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32


void Die()
{
  exit(1);
}

const char* GetUserName()
{
  const char* name = getenv("USER");
  if (name == NULL) return "None";

  return name;
}

const char* GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  if (hostname == NULL) return "None";

  return hostname;
}

const char* GetCwd()
{
  const char* cwd = getenv("PWD");
  if (cwd == NULL) return "None";

  return cwd;
}

void  MakeCommandLineAndPrint()
{
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, cwd);
  printf("%s", line);
  fflush(stdout);
}

int GetUserCommand(char command[], size_t n)
{
  char* s = fgets(command, n, stdin);

  if (s == NULL)  return -1;

  //目的是为了解决 "\n"被都进来的情况
  command[strlen(command) - 1] = ZERO;

  return strlen(command);
}


char* gArgv[NUM];

void SplitCommand(char command[], size_t n)
{
  gArgv[0] = strtok(command, SEP);

  int index = 1;
  while(gArgv[index++] = strtok(NULL, SEP));

}


void ExecuteCommand()
{
  pid_t id = fork();
  if (id < 0) Die();

  //child
  if (id == 0)
  {
    execvp(gArgv[0], gArgv);
    exit(errno);
  }
  else
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
  }
}


int main()
{
  int quit = 0;
  while (!quit)
  {
          //1.输出自己的命令行
          MakeCommandLineAndPrint();

          //2.获取用户输入的命令字符串
 		  char usercommand[SIZE];
          int n = GetUserCommand(usercommand, sizeof(usercommand));

          //3.命令行字符串分割
          SplitCommand(usercommand, sizeof(usercommand));

          //4.执行命令
          ExecuteCommand();

  }
  return 0;
}

5.检查内键命令

  • 第一版本Shell存在的问题,cd 无效,工作的路径根本没有改变,原因是由于 cd属于内建命令,而内建命令是要由父进程Bash来执行的,下面出现Bug的原因就是由于子进程执行的cd命令;

  • 🍊处理内建命令 cd的主要核心代码如下图:
    代码解释: char *getcwd(char *buf, size_t size); 获取当前的工作目录
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32

char cwd[SIZE*4];

void Die()
{
  exit(1);
}

const char* GetHome()
{
  const char* home = getenv("HOME");
  if (home == NULL) return "/";
  return home;
}

const char* GetUserName()
{
  const char* name = getenv("USER");
  if (name == NULL) return "None";

  return name;
}

const char* GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  if (hostname == NULL) return "None";

  return hostname;
}
const char* GetCwd()
{
  const char* cwd = getenv("PWD");
  if (cwd == NULL) return "None";

  return cwd;
}

void  MakeCommandLineAndPrint()
{
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, cwd);
  printf("%s", line);
  fflush(stdout);
}

int GetUserCommand(char command[], size_t n)
{
  char* s = fgets(command, n, stdin);

  if (s == NULL)  return -1;

  //目的是为了解决 "\n"被都进来的情况
  command[strlen(command) - 1] = ZERO;

  return strlen(command);
}

char* gArgv[NUM];

void SplitCommand(char command[], size_t n)
{
  gArgv[0] = strtok(command, SEP);

  int index = 1;
  while(gArgv[index++] = strtok(NULL, SEP));

}

void ExecuteCommand()
{
  pid_t id = fork();
  if (id < 0) Die();

  //child
  if (id == 0)
  {
    execvp(gArgv[0], gArgv);
    exit(errno);
  }
  else
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
  }
}

void Cd()
{
  const char* path = gArgv[1];
  if (path == NULL) path = GetHome();
	
  chdir(path);

  char temp[SIZE*2];
  getcwd(temp, sizeof(temp));

  snprintf(cwd, sizeof(cwd), "PWD=%s",temp);

  putenv(cwd);
}

int CheckBuildin()
{
  int yes = 0;
  const char* enter_cmd = gArgv[0];

  if (!strcmp(enter_cmd, "cd"))
  {
    yes = 1;
    Cd();
  }
  return yes;
}

int main()
{
  int quit = 0;
  while (!quit)
  {
          //1.输出自己的命令行
          MakeCommandLineAndPrint();

          //2.获取用户输入的命令字符串
          char usercommand[SIZE];
          int n = GetUserCommand(usercommand, sizeof(usercommand));
          if (n <= 0) return 1;

          //3.命令行字符串分割
          SplitCommand(usercommand, sizeof(usercommand));

          //4.检测命令是否是内键命令
          n = CheckBuildin();
          if (n) continue;

          //5.执行命令
          ExecuteCommand();
  }
  return 0;
}
相关推荐
小田冲冲冲43 分钟前
搭建域服务器
运维·服务器
亦安✘1 小时前
服务器从0到1微服务所需的环境的安装
运维·服务器·spring cloud·微服务
用户51681661458412 小时前
[VMware 无法检测此光盘中映像中的操作系统] VMware创建虚拟机无法检测操作系统iso镜像文件
linux·前端
MacroZheng2 小时前
斩获 7.8K star!一款堪称开源监控新标杆的项目,牛皮!
java·linux·后端
YC运维3 小时前
Ansible模块
java·服务器·前端
博语小屋3 小时前
程序(进程)地址空间(1)
linux
yunyi3 小时前
使用acme.sh来实现自动化申请和续订TLS证书
linux·nginx·docker
_君落羽_4 小时前
Linux操作系统——TCP服务端并发模型
linux·服务器·c++
望获linux6 小时前
【Linux基础知识系列】第一百一十篇 - 使用Nmap进行网络安全扫描
java·linux·开发语言·前端·数据库·信息可视化·php