关于房产的网站有哪些,东莞市疾控中心24小时咨询电话,手机类网站设计,苏州企业网站建站#x1f341;你好#xff0c;我是 RO-BERRY #x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 #x1f384;感谢你的陪伴与支持 #xff0c;故事既有了开头#xff0c;就要画上一个完美的句号#xff0c;让我们一起加油 目录 1.shell2.自定义shell的准… 你好我是 RO-BERRY 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 感谢你的陪伴与支持 故事既有了开头就要画上一个完美的句号让我们一起加油 目录 1.shell2.自定义shell的准备工作3. 编写myshell.c3.1获取主机名3.2获取用户名3.3获取当前工作目录3.4 命令行缓冲区3.5 命令行切割3.6 执行命令3.7 进行多命令操作3.8 内建命令3.9 导入环境变量3.10 echo命令 1.shell
Linux的Shell是一种命令行解释器它是用户与操作系统内核之间的接口。 通过Shell用户可以输入命令并与操作系统进行交互。Shell可以执行各种任务如文件管理、进程控制、系统配置等。 Linux中最常用的Shell是BashBourne Again Shell它是Bourne Shell的增强版本。Bash提供了丰富的功能和命令使得用户可以更加高效地管理和操作系统。 以下是一些常用的Shell命令和功能
文件和目录操作ls列出文件和目录、cd切换目录、mkdir创建目录、rm删除文件或目录等。文件查看和编辑cat查看文件内容、grep在文件中搜索指定内容、vi文本编辑器等。进程管理ps查看进程信息、kill终止进程等。系统信息查看uname显示系统信息、df查看磁盘空间使用情况等。网络管理ping测试网络连接、ifconfig配置网络接口等。
考虑下面这个与shell典型的互动 用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表它随着时间的流逝从左向右移动。shell从用户读入字符串ls。shell建立一个新的进程然后在那个进程中运行ls程序并等待那个进程结束。 然后shell读取新的一行输入建立一个新的进程在这个进程中运行程序 并等待这个进程结束。 所以要写一个shell需要循环以下过程:
获取命令行解析命令行建立一个子进程fork替换子进程execvp父进程等待子进程退出wait
根据这些思路和我们前面的学的技术就可以自己来实现一个shell了。 2.自定义shell的准备工作
首先做一个shell的目录用来装我们的自定义shel mkdir shell 然后在目录下创建Makefile文件以及myshell.c文件 ls Makefile touch myshell.c 编写Makefile和myshell.c文件
Makefile
myshell:myshell.c gcc -o $ $^
.PHONY:cleanrm -rf myshell上面几步很好走最重要的是我们的myshell.c文件应该如何去编写呢 3. 编写myshell.c
之前我们总体学了环境变量环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。我们想得到当前用户名以及当前目录等等都依靠的是环境变量因此我们可以通过环境变量来进行编写shell。 getenv(环境变量)这个函数接口就是我们获取环境变量中存储的数据的方式之前讲过 3.1获取主机名
环境变量HOSTNAME
//获取主机名
const char* HostName()
{char* hostnamegetenv(HOSTNAME); if(hostname) return hostname;else return None;
}3.2获取用户名
环境变量USER
//获取用户名
char* UserName()
{char* hostnamegetenv(USER); if(hostname) return hostname;else return None;
}3.3获取当前工作目录
环境变量PWD
//获取当前工作目录
char* CurrentWorkDir()
{char* hostnamegetenv(PWD);if(hostname) return hostname;else return None;
}3.4 命令行缓冲区 我们有了上面三个信息后就可以将上面三个当成我们的命令提示符了也就是命令行前面显示的我们的用户名以及权限、当前目录。 有了命令行提示符还不够我们还需要输入我们的指令操作就必须创建一个命令行缓冲区帮我们接受命令 在这里我们使用scanf是肯定不行的scanf只能获取一个指令操作 所以需要用到fgets函数来帮助我们获取多个指令 char *fgets(char *s, int size, FILE *stream) 是C语言中的一个函数用于从指定的文件流中读取一行字符串并将其存储到字符数组 s 中。 它的参数解释如下 s指向字符数组的指针用于存储读取到的字符串。 size要读取的最大字符数包括空字符。 stream指向要读取的文件流的指针。 该函数会从文件流中读取字符直到遇到换行符 \n、文件结束符 EOF 或者达到最大字符数 size-1。读取到的字符会存储在字符数组 s 中并在末尾添加一个空字符 \0。 函数返回值为指向字符数组 s 的指针如果成功读取到字符串则返回 s 的地址如果在读取过程中发生错误或者已经到达文件末尾则返回 NULL。
注意使用该函数时需要确保字符数组 s 的大小足够大以容纳要读取的字符串。 stream文件流指针我们目前并未涉及我们想输入的话这里就需要写stdin,输出的话这里需要写stdout #define SIZE 1024
int main()
{char commandline[SIZE]; //命令行缓冲区printf(%s%s %s]$ ,UserName(),HostName(),CurrentWorkDir());//scanf(%s,commandline); scanf只能获取一个字符串fgets(commandline, SIZE, stdin); //获取字符串 printf(test: %s\n, commandline);return 0;
} 我们简单进行测试一下 我们这里最后的printf里是没有输出换行的这是因为我们的fgets函数使用了之后会在末尾添加一个空字符 \0 我们也可以将这个\0去掉
因为\0在字符串的末尾所以我们在字符串长度减一的位置将其改为0即可
int main()
{char commandline[SIZE]; //命令行缓冲区printf(%s%s %s]$ ,UserName(),HostName(),CurrentWorkDir());//scanf(%s,commandline); scanf只能获取一个字符串fgets(commandline, SIZE, stdin);commandline[strlen(commandline)-1] 0; printf(test: %s\n, commandline);return 0;
}最后做成一个交互接口
int Interactive(char out[],int size)
{printf(%s%s %s]$ ,UserName(),HostName(),CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] 0;return strlen(out);
}int main()
{char commandline[SIZE]; //命令行缓冲区int n Interactive(commandline,SIZE);if(n 0) continue; //解决空串问题printf(test: %s\n, commandline);return 0;
}3.5 命令行切割
我们对ls -a -l进行了接受储存并打印但是这里只有ls是指令而后面的-a-l则是命令行参数我们在这里就要对指令和命令行参数进行分割处理。那我们如何分割呢
我们可以使用指针数组来分别存储将其拆开 我们这里想将字符串一变多可以使用函数 strtok strtok是一个C语言中的字符串处理函数用于将字符串分割成多个子字符串。它的原型定义如下 char *strtok(char *str, const char *delim); 其中str是要分割的字符串delim是分割符。函数会将str按照delim进行分割并返回第一个子字符串的指针。之后每次调用strtok时传入NULL作为第一个参数函数会继续返回下一个子字符串的指针直到所有子字符串都被返回。
需要注意的是strtok会修改原始字符串将分割符替换为’\0’因此在使用strtok后原始字符串会被改变。
#define MAX_ARGC 64//对命令行进行切割char *argv[MAX_ARGC];int i 0;argv[i]strtok(commandline, SEP);while(argv[i] strtok(NULL,SEP)); //故意将 写成 这里我们故意将写成是为了让我们的argv以NULL结尾当我们的argv被赋值为NULL之后while判断会失败就能跳出循环这是因为我们的命令行参数必须以NULL结尾进行程序替换的接口都必须要求我们的命令行参数以NULL结尾 接下来我们将我们的切割操作定义成一个接口
#includestdio.h
#includestdlib.h
#includestring.h#define MAX_ARGC 64
#define SIZE 1024
#define SEP
char* argv[MAX_ARGC];
//获取主机名
const char* HostName()
{char* hostnamegetenv(HOSTNAME);if(hostname) return hostname;else return None;
}//获取用户名
const char* UserName()
{ char* hostnamegetenv(USER);if(hostname) return hostname;else return None;
}//获取当前工作目录
const char* CurrentWorkDir()
{char* hostnamegetenv(PWD);if(hostname) return hostname;else return None;
}int Interactive(char out[],int size)
{printf(%s%s %s]$ ,UserName(),HostName(),CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] 0;return strlen(out);
}
void Split(char in[])
{int i 0;argv[i]strtok(in, SEP);while(argv[i] strtok(NULL,SEP)); //故意将 写成
}
int main()
{//接受命令字符串char commandline[SIZE];//命令行缓冲区int n Interactive(commandline,SIZE);if(n 0) continue; //解决空串问题//对命令行进行切割Split(commandline);int i0;for(i 0;argv[i];i){printf(argv[%d]: %s\n, i, argv[i]);}return 0;
}此时的运行结果如下 3.6 执行命令
我们接收到了命令并进行了分割接下来我们就要进行最关键的一步对其进行执行操作。 此时我们要执行这个命令我们是命令行解释器bash我们不能直接进行程序替换执行命令那么我们整个进程就被替换成这一个命令了我们在这里需要用到子进程让子进程执行这个命令 这里要使用execp接口 execp是一个系统调用函数用于在当前进程中一个新的程序。它是exec函数族中的一员execp函数的原型如下 int execp(const char *file, const char *arg, ...); 其中file参数是要执行的程序的路径arg参数是传递给新程序的命令行参数。execp函数会将当前进程替换为新程序并开始执行新程序的代码。 execp函数与其他exec函数的区别在于它接受一个可变参数列表可以传递任意数量的参数给新程序。这些参数在新程序中可以通过main函数的参数列表获取到。 execp函数执行成功时不会返回到原来的程序而是直接开始执行新程序。如果执行失败则会返回-1并设置errno变量来指示错误类型。 需要注意的是execp函数只能在子进程中调用因为它会替换当前进程的代码和数据段。如果在父进程中调用execp函数那么父进程的代码和数据也会被替换导致父进程无法继续执行。 //执行命令pid_t id fork();if(id 0){//让子进程执行命令execvp(argv[0], argv);exit(1);}//父进程等待子进程并进行回收pid_t rid waitpid(id, NULL, 0); printf(run done, rid: %d\n, rid); 我们现在已经可以执行我们的命令了
封装成接口
void Execute()
{pid_t id fork();if(id 0){//让子进程执行命令execvp(argv[0], argv);exit(1);}//父进程等待子进程并进行回收pid_t rid waitpid(id, NULL, 0);
}
3.7 进行多命令操作
众所周知shell不可能只能执行一次命令我们要进行多次命令操作
int main()
{while(1){//接受命令字符串char commandline[SIZE];//命令行缓冲区Interactive(commandline,SIZE); //对命令行进行切割Split(commandline);//执行命令Execute();}return 0;
}我们使用一个死循环 我们这个时候用的shell就是我们自己的shell了 但是为什么我们的cd命令却跑不了呢 这是因为cd命令是由我们的子进程运行的改变的是子进程的路径而没有改变我们bash进程的路径 3.8 内建命令
内建命令是指直接内置在操作系统中的命令可以直接在命令行或终端中使用而无需额外安装或配置。内建命令通常具有更高的执行效率和更快的响应速度。
我们上面想执行cd是因为其为内建命令而不是让子进程进行。需要我们单独进行处理
char* Home()
{return getenv(HOME);
}
int BuildinCmd()
{int ret 0;if(strcmp(cd, argv[0]) 0) //检测是否是cd命令{//执行内建命令ret 1;char* target argv[1]; //这里是cd命令后面紧跟着的路径或者空路径if(!target) target Home(); //如果target为空则为家目录不为空则为目标目录chdir(target); //chdir为改变当前路径为target}return ret;
}可以看到我们当前路径是发生了改变的但是前面的命令提示符上面的路径没有改变我们的命令提示符提取的是我们的环境变量也就是说当我们的环境变量发生改变的时候我们这里也要进行一个同步的更新才行
我们这里为了方便定义一个pwd的全局变量字符串 char* pwd[MAX_ARGC]; 我们还要涉及两个函数 putenv函数是一个C语言标准库函数用于设置环境变量的值。它的原型如下 int putenv(char *string);
putenv函数接受一个形如keyvalue的字符串参数将该字符串解析为一个环境变量并将其添加到当前进程的环境变量列表中。如果该环境变量已存在则会更新其值。putenv函数的返回值为0表示成功非零值表示失败。使用putenv函数可以在程序运行时动态地设置环境变量这对于需要根据不同的条件来改变程序行为的情况非常有用。 注意putenv函数在修改环境变量时只会影响当前进程及其子进程不会影响其他进程。 snprintf是一个C语言中的函数用于将格式化的数据写入字符串中。它的原型如下 int snprintf(char *str, size_t size, const char *format, ...);
其中str是目标字符串的指针size是目标字符串的最大长度format是格式化字符串后面的参数是要格式化的数据。snprintf函数的作用是将格式化的数据按照指定的格式写入到目标字符串中并返回写入的字符数不包括终止符’\0’。如果目标字符串的长度超过了指定的最大长度snprintf会截断超出部分的数据以保证不会发生缓冲区溢出。snprintf函数与sprintf函数类似但是它多了一个参数size用于指定目标字符串的最大长度从而避免了缓冲区溢出的风险。 当我们使用cd改变了当前路径的时候我们就要用putenv来改变环境变量 然后我们使用snprintf让改变的路径存储到我们的全局变量里 代码实现如下
int BuildinCmd()
{int ret 0;//检测是非是内建命令是为1.否为0if(strcmp(cd, argv[0]) 0){//执行内建命令ret 1;char* target argv[1]; //cd XXX or cdif(!target) target Home();chdir(target);snprintf(pwd,SIZE,PWD%s,target);putenv(pwd);}return ret;
} 现在确实已经可以和我实现的操作进行同步了只不过当我们cd两个点的时候这是特殊代表上级目录的它跟不上我们的操作
为了解决这个麻烦我们需要使用getcwd函数接口 getcwd是一个C语言函数用于获取当前工作目录的路径名。它的原型如下 char *getcwd(char *buf, size_t size); 该函数接受两个参数第一个参数是一个字符数组指针用于存储获取到的当前工作目录路径名第二个参数是buf的大小用于指定buf的长度。getcwd函数会将当前工作目录的路径名复制到buf中并返回buf的指针。如果获取成功则返回的指针与buf相同如果获取失败则返回NULL。getcwd函数在实际应用中常用于获取当前程序所在的目录路径以便进行文件操作或其他相关操作。
代码实现如下
int BuildinCmd()
{int ret 0;//检测是非是内建命令是为1.否为0if(strcmp(cd, argv[0]) 0){//执行内建命令ret 1;char* target argv[1]; //cd XXX or cdif(!target) target Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd,SIZE,PWD%s,temp);putenv(pwd);}return ret;
}3.9 导入环境变量
我们现在进行导入环境变量其实际上是子进程在运行我们的bash进程是得不到我们的环境变量的所以我们对于导入环境变量也需要特殊处理
也就是说export命令也是一个内建命令需要我们自己导入到自己的环境变量里 设置一个全局变量存储我们的环境变量 env[SIZE] int BuildinCmd()
{int ret 0;//检测是非是内建命令是为1.否为0if(strcmp(cd, argv[0]) 0){//执行内建命令ret 1;char* target argv[1]; if(!target) target Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd,SIZE,PWD%s,temp);putenv(pwd);}else if(strcmp(export,argv[0]) 0){ret 1;if(argv[1]){strcpy(env,argv[1]) //防止argv每次接收到的命令行数据覆盖掉导致环境变量消失putenv(env); //将环境变量导入}}return ret;
}3.10 echo命令
echo命令也是一个内建命令 echo $?:可以打印出上次进程退出的退出码 echo $PATH:打印当前用户的主目录路径 char lastcode 0 ; //全局变量退出码int BuildinCmd()
{int ret 0;//检测是非是内建命令是为1.否为0if(strcmp(cd, argv[0]) 0){//执行内建命令ret 1;char* target argv[1]; if(!target) target Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd,SIZE,PWD%s,temp);putenv(pwd);}else if(strcmp(export,argv[0]) 0){ret 1;if(argv[1]){strcpy(env,argv[1]); putenv(env);}}else if(strcmp(echo, argv[0]) 0){ret 1 ;if(argv[1] NULL){printf(\n);}else{if(argv[1][0] $){if(argv[1][1] ?){printf(%d\n,lastcode);lastcode 0;}else{char *e getenv(argv[1]1);if(e) printf(%s\n,e);}}elseprintf(%s\n, argv[1]);}}return ret;