前言
大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++ Linux的老铁
主要内容含:
欢迎订阅 YY滴C++专栏!更多干货持续更新!以下是传送门!
- YY的《C++》专栏
- YY的《C++11》专栏
- YY的《Linux》专栏
- YY的《数据结构》专栏
- YY的《C语言基础》专栏
- YY的《初学者易错点》专栏
- YY的《小小知识点》专栏
- YY的《单片机期末速过》专栏
- YY的《C++期末速过》专栏
- YY的《单片机》专栏
- YY的《STM32》专栏
- YY的《数据库》专栏
- YY的《数据库原理》专栏
目录
- 一.前置知识
- 【1】Shell和Bash简述
- 【2】Bash的输入原理——指针数组
- 二.自定义shell程序设计
- 【1】<主函数模块>——大体框架
- 1.程序设计框架
- 2.程序设计细节
- 【2】<打印提示符>模块
- 1.程序设计框架
- 2.程序设计细节
- 【3】<分割字符串>模块
- 1.程序设计框架
- 2.程序设计细节
- 【4】<执行对应的命令>模块
- 1.程序设计框架
- 2.程序设计细节
一.前置知识
【1】Shell和Bash简述
- Shell 是一种命令行界面,是用户与系统之间的接口,允许用户执行命令来 管理系统资源、运行程序等
- Bash 是 Shell 的一种实现,也是目前最流行的 Shell 之一
【2】Bash的输入原理——指针数组
-
我们运行Linux时会出现, bash提示符和命令行 ,我们接下来也要实现这两点
-
本质是通过 空格 作为分隔符,把一个一个字符串分隔开载入 指针数组中 ;
-
在父进程bash进程中,创建一个子进程,环境变量也会传递给子进程,并进行 进程等待wait
-
在子进程中通过 进程替换exec ,执行 指针数组中 中的命令(通过环境变量)
ifn<=0,直接结束省的创建子进程
cd就不行。因为是子进程的cd…
二.自定义shell程序设计
【1】<主函数模块>——大体框架
1.程序设计框架
- 根据前置知识中的实现原理
- 我们主函数中要有对应模块:
- 打印提示符&&获取用户命令字符串获取成功:
getUserCommand函数
- 分割字符串:
commandSplit函数
- 执行对应的命令:
execute函数
2.程序设计细节
- 设置一个命令行获取字符数组:
usercommand
- 设置一个存储———分割usercommand数组后的字符串的地址——的指针数组:
argv
#define NUM 1024
#define SIZE 64
int main()
{
while(1){ //shell要不停跑,死循环
char usercommand[NUM];
char *argv[SIZE];
// 1. 打印提示符&&获取用户命令字符串获取成功
int n= getUserCommand(usercommand, sizeof(usercommand));
// 2. 分割字符串
// "ls -a -l" -> "ls" "-a" "-l"
commandSplit(usercommand, argv);
// 3. 执行对应的命令
execute(argv);
}
}
【2】<打印提示符>模块
1.程序设计框架
- 提示符信息包括:1.HOME 2.USER 3.HOSTNAME
- 我们将上面3个信息分别封装,在
getUserCommand函数
中统一打印 - 我们通过
getenv函数
可以获取 环境变量的地址,进而打印
- command参数 接收命令行获取 字符数组usercommand
- num参数 接收 字符数组长度
2.程序设计细节
-
C语言默认会打开三个输入输出流:stdin键盘 stdout显示器stderr显示器,我们用到stdin获取输入流
-
不用scanf,用fget函数的原因:scanf遇到空格停下来,命令行本身就会出现空格。故采用
行获取接口fgets
-
command参数 接收命令行获取 字符数组usercommand ,我们输入命令后,最终你还是会输入
\n
——导致执行结果和shell之间出现空行;所以我们在输入完后要把command最后一个字符'\n'
换成'\0'
int getUserCommand(char *command, int num)
{
printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
char *r = fgets(command, num, stdin); // 最终你还是会输入\n
if(r == NULL) return -1;
// "abcd\n" "\n"
command[strlen(command) - 1] = '\0'; // 有没有可能越界?不会
return strlen(command);
}
char *homepath()
{
char *home = getenv("HOME");
if(home) return home;
else return (char*)".";
}
const char *getUsername()
{
const char *name = getenv("USER");
if(name) return name;
else return "none";
}
const char *getHostname()
{
const char *hostname = getenv("HOSTNAME");
if(hostname) return hostname;
else return "none";
}
【3】<分割字符串>模块
1.程序设计框架
- 这个模块,我们要通过 空格 作为分隔符,把一个一个字符串分隔开载入 指针数组 argv
- in参数 接收命令行获取 字符数组usercommand
- *out[]参数 是 输出型参数,用于传出 分割usercommand数组后的字符串的地址——的指针数组argv
2.程序设计细节
- 通过
strstok函数
分割; 注意语法,分成首次分割,和剩余分割
#define SEP " "
void commandSplit(char *in, char *out[])
{
int argc = 0;
out[argc++] = strtok(in, SEP);
while( out[argc++] = strtok(NULL, SEP));
}
【4】<执行对应的命令>模块
1.程序设计框架
我们回顾原理部分:
-
在父进程bash进程中,创建一个子进程,环境变量也会传递给子进程,并进行 进程等待wait
-
在子进程中通过 进程替换exec ,执行 指针数组中 中的命令(通过环境变量)
于是我们设计出:
fork函数
创建子进程- 子进程进行进程替换
execvp函数
,用到 分割usercommand数组后的字符串的地址——的指针数组argv - 父进程等待子进程
2.程序设计细节
1. fork函数:
2. execvp函数: 由于我们用到了指针数组argv,所以用exec系列的vp尾缀,execvp , 表示v(vector)数组,p(可以使用环境变量PATH,无需写全路径)
3. waitpid函数:不关心后续操作,status参数设置成NULL,options参数设置成0
int execute(char *argv[])
{
pid_t id = fork();
if(id < 0) return -1;
else if(id == 0) //child
{
// exec command
execvp(argv[0], argv); // cd ..
exit(1);
}
else // father
{
int status = 0;
pid_t rid = waitpid(id, NULL, 0);
}
return 0;
}