目录
前提引入
实现自主shell
自己输出一个命令行
命令行包括:用户名,主机名,路径。
那么怎么获取上述东西呢?其实不用接口也行因为系统有很多环境变量,直接从环境变量去拿。env:查看环境变量
每一个进程都有自己的环境变量,可以通过getenv获的环境变量:
获得用户名
查看env发现有USER=wwz是我的用户名:
获得主机名
查看env发现有HOSTNAME=zhouzhou是我的主机名:
获取路径
查看env发现有PWD显示是我的绝对路径:
实现输出命令行
获取用户命令字符串
需要注意的是fgets会将\n也算入,比如输入abcd\n,那么strlen=5.
分割字符串
需要用到strtok函数,根据delim分割成一个一个字串,返回第一个字串。
str:要分割的字符串,第一次调用时传入待分割的字符串,在后续调用中传入 NULL 表示继续分割上次操作的字符串。
执行命令
用子进程执行命令
内建命令
exec() 系列函数不能直接执行内建命令,因为内建命令是由 shell 提供的,而 exec() 函数直接执行的是独立的可执行文件。
介绍chdir:chdir是一个Linux命令,用于改变当前工作目录.你可以使用chdir命令来切换到指定的目录
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main() {
// 改变当前工作目录到指定路径
if (chdir("/path/to/directory") == 0) {
printf("Changed working directory successfully\n");
// 可以使用 getcwd 来验证是否成功改变了工作目录
char buf[1024];
if (getcwd(buf, sizeof(buf)) != NULL) {
printf("Current working directory: %s\n", buf);
} else {
perror("getcwd() error");
return EXIT_FAILURE;
}
} else {
perror("chdir() error");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
perror() 用于打印与 errno 相关的错误信息。它会将错误信息输出到标准错误流(stderr),并在错误消息前附加自定义前缀。errno 是一个全局变量,保存了最近的错误代码。
errno 是一个全局变量,用于存储最近一次系统调用或库函数调用产生的错误码。这个错误码是整数值,每个值对应一个特定的错误。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *file = fopen("nonexistent_file.txt", "r");
if (file == NULL) {
perror("Error opening file");
}
return 0;
}
Error opening file: No such file or directory
加环境变量:
介绍getcwd:
父进程打印错误信息
截取命令行路径
echo
完整代码
1 #include<stdlib.h>
2 #include<stdio.h>
3 #include<sys/types.h>
4 #include<unistd.h>
5 #include<string.h>
6 #include<sys/types.h>
7 #include<sys/wait.h>
8 #include<errno.h>
9 #define SIZE 512
10 #define NUM 32 //最多32个选项
11 #define skip(p)do{p+=(strlen(p)-1);while(*p!='/')p--;}while(0)
12 char cwd[SIZE*2];
13 int lastcode=0;
14 const char* getpwd(){
15 const char* cwd=getenv("PWD");
16 if(cwd==NULL)return "None";
17 return cwd;
18 }
19 const char* getname(){
20 const char* name=getenv("USER");
21 if(name==NULL)return "None";
22 return name;
23 }
24 const char* gethostname(){
25 const char* hostname=getenv("HOSTNAME");
26 if(hostname==NULL)return "None";
27 return hostname;
28 }
29 const char* gethome(){
30 const char*home=getenv("HOME");
31 if(home==NULL)home="/root";
32 return home;
33 }
34 void makeline(){
35 char line[SIZE];//命令行
36 const char* hostname=gethostname();
37 const char* name=getname();
38 const char* cwd=getpwd();
39 skip(cwd);
40 snprintf(line,sizeof(line),"[%s@%s %s]>",name,hostname,strlen(cwd)==1?"/":cwd+1);
41 printf("%s",line);
42 fflush(stdout);
43 }
44 int getcommandline(char usercommand[],size_t n){
45 char*s=fgets(usercommand,n,stdin);
46 if(s==NULL)return -1;
47 s[strlen(s)-1]='\0';
48 return strlen(usercommand);
49 }
50 char*gargv[NUM];
51 void splitcommand(char usercommand[],size_t t){
52 (void)t;//代表暂时不用t
53 gargv[0]=strtok(usercommand," ");
54 int dex=1;
55 while((gargv[dex++]=strtok(NULL," ")));
56 }
57 void exectuecommand(){
58 pid_t id=fork();
59 if(id<0)exit(-1);
60 else if(id==0){
61 //child
62 execvp(gargv[0],gargv);
63 exit(errno);
64 }
65 else{
66 //father
67 int status=0;
68 pid_t rid=waitpid(id,&status,0);
69 if(rid>0){
70 //success
71 lastcode=WEXITSTATUS(status);
72 if(lastcode!=0)printf("%s:%s:%d\n",gargv[0],strerror(lastcode),lastcode);
73 }
74 }
75 }
76 void cd(){
77
78 const char*path=gargv[1];
79 if(path==NULL)path=gethome();
80 chdir(path);
81 //刷新环境变量
82 char tmp[SIZE*2];
83 getcwd(tmp,sizeof(tmp));
84 snprintf(cwd,sizeof(cwd),"PWD=%s",tmp);
85 putenv(cwd);
86 }
87 int cheak(){
88 int yes=0;
89 const char*enter=gargv[0];
90 if(strcmp(enter,"cd")==0){
91 yes=1;
92 cd();
93 }
94 else if(strcmp(enter,"echo")==0&&strcmp(gargv[1],"$?")==0){
95 yes=1;
96 printf("%d\n",lastcode);
97 lastcode=0;
98 }
99 return yes;
100 }
101 int main()
102 {
103 int quit=0;
104 while(!quit){//一直执行
105 makeline();//1.命令行
106 char usercommand[SIZE];//命令字符串
107 int a=getcommandline(usercommand,sizeof(usercommand));//2.获得字符串
108 if(a<=0)return 1;
109 splitcommand(usercommand,sizeof(usercommand));//3.分割字符串
110 int n=cheak();//检查内建命令
111 if(n)continue;
112 exectuecommand();//n.执行命令
113 }
114 return 0;
115 }
大概实现一些功能。
总结
所以要写一个shell,需要循环以下过程:
1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)5. 父进程等待子进程退出(wait)
思考函数和进程之间的相似性
exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的 操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程 序之内的模式扩展到程序之间
如图:
一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。