自主shell

本文介绍了如何通过系统环境变量获取用户名、主机名和路径,实现一个简单的命令行shell,包括获取用户输入的命令字符串、分割、执行子进程以及处理内建命令如`cd`和`echo`。作者还探讨了函数和进程之间的相似性,将程序内的call/return模式扩展到进程间通信。
摘要由CSDN通过智能技术生成

目录

前提引入

实现自主shell

 自己输出一个命令行

 获得用户名

获得主机名 

 获取路径

 实现输出命令行

获取用户命令字符串

分割字符串 

 执行命令

内建命令 

父进程打印错误信息

 截取命令行路径

echo 

 完整代码

 总结

 思考函数和进程之间的相似性


前提引入

实现自主shell

 自己输出一个命令行

命令行包括:用户名,主机名,路径。
那么怎么获取上述东西呢?其实不用接口也行因为系统有很多环境变量,直接从环境变量去拿。

env:查看环境变量

每一个进程都有自己的环境变量,可以通过getenv获的环境变量:

 获得用户名

查看env发现有USER=wwz是我的用户名:

获得主机名 

查看env发现有HOSTNAME=zhouzhou是我的主机名:

 获取路径

查看env发现有PWD显示是我的绝对路径:

 实现输出命令行

获取用户命令字符串

返回值是*s

需要注意的是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的返回值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wangsir.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值