进程控制---自定义Shell

目录

1.自定义Shell

1.打出对应命令行

1.样式上

2.第二版

3.第三版

命令行解析

执行命令

小调整

4.第四版

[chdir 更改工作路径 哪个进程调chdir就更改哪个路径](#chdir 更改工作路径 哪个进程调chdir就更改哪个路径)

内建命令

系统调用

5.echo

shell有两张表,一张命令行参数表,一张环境变量表


1.自定义Shell

user1@iZ5waahoxw3q2bZ myshell$

我们自己有个对应的字符串,shell启动时也可以打出类似形式的字符串

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.c
#include<stdio.h>

int main()
{
    printf("[user1@iZ5waahoxw3q2bZ myshell]#");
    return 0;
}

获取指定环境变量的值

复制代码
#include <stdlib.h>

char *getenv(const char *name);

1.打出对应命令行

1.样式上

复制代码
#[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

int main()
{
    printf("[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd());
    return 0;
}

user1@iZ5waahoxw3q2bZ myshell$ ./myshell

user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell#user1@iZ5waahoxw3q2bZ myshell \[user1@iZ5waahoxw3q2bZ myshell\]

系统卡在这里是在等用户输入,我们想让自己的命令行也可以这样

如果是用scanf会把我们这个读成三部分

比如我们输入ls -a -l在shell看来时"ls" "-a" "-l"字符串。

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ mv myshell.c myshell.cc

搞成C跟C++混编

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat Makefile
myshell:myshell.cc
	g++ -o $@ $^ #-std=c99 
.PHONY:clean
clean:
	rm -f myshell

fgets

复制代码
      char *fgets(char *s, int size, FILE *stream);
                           缓冲区大小

C标准可以从指定的文件流中获取信息,按行获取

成功了就是s的地址,失败就是NULL。FILE *标准输入

为了避免将回车搞成多一行加入一行commandlinestrlen(commandline)-1=0;//清理\n

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>

#define COMMAND_SIZE 1024

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

int main()
{
    printf("[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd());
    
    char commandline[COMMAND_SIZE];

    char *c = fgets(commandline,sizeof(commandline),stdin);
    if(c==NULL) return 1;
    commandline[strlen(commandline)-1]=0;//清理\n
    printf("echo %s\n",commandline);
    
    return 0;
}

[user1@iZ5waahoxw3q2bZ myshell]$ clear
[user1@iZ5waahoxw3q2bZ myshell]$ make
make: `myshell' is up to date.
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]#ls -a-^H ^C
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]#ls -a -l
echo ls -a -l

int snprintf(char *str, size_t size, const char *format, ...);

安全的格式化输出函数,用于将格式化字符串写入字符数组,并限制最大写入长度,防止缓冲区溢出。

  • snprintf 是 C 标准库函数,用于格式化字符串并写入缓冲区,同时保证不会超过缓冲区大小(防止溢出)。

  • cmd_prompt:输出缓冲区(字符数组)。

  • size :缓冲区总大小,snprintf 最多写入 size-1 个字符,末尾自动加 '\0'

  • FORMAT :通常定义为类似 "%s@%s:%s$ " 的格式字符串,包含三个 %s 占位符。

  • GetUserName():返回当前用户名(字符串)。

  • GetHostName():返回主机名(字符串)。

  • GetPwd():返回当前工作目录路径(字符串)。

2.第二版

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        printf("echo %s\n",commandline); 
    }
    return 0;
}

3.第三版

命令行参数在进行输出的时候,命令行参数这个表是由shell自己维护的

命令行分析 "ls -a -l" -> "ls" "-a" "-l",我们可以用一个接口去分割

strtok 字符串切割

SYNOPSIS

#include <string.h>

char *strtok(char *str, const char *delim);

命令行解析
复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat Makefile
myshell:myshell.cc
	g++ -o $@ $^ -std=c++11 #-std=c99 
.PHONY:clean
clean:
	rm -f myshell

[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    return true;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        CommandParse(commandline);
        PrintArgv();
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ myshell]$ make
g++ -o myshell myshell.cc -std=c++11 #-std=c99 
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# ls -a -l
echo ls -a -l
argv[0]->ls
argv[1]->-a
argv[2]->-l
argc:4

为什么这里是4呢?因为把NULL也算上了。加上一行g_argc--;

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return true;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        CommandParse(commandline);
        PrintArgv();
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ myshell]$ make
g++ -o myshell myshell.cc -std=c++11 #-std=c99 
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# ls -a -l
echo ls -a -l
argv[0]->ls
argv[1]->-a
argv[2]->-l
argc:3

已经按照对应的需求把命令行解析完了

这也符合曾经讲的shell提供两张表

执行命令
复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return true;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        CommandParse(commandline);
        //PrintArgv();
        

        //4.执行命令
        pid_t id = fork();
        if(id==0)
        {
            //child
            //程序替换
            execvp(g_argv[0],g_argv);
            exit(1);
        }

        //father
        pid_t rid = waitpid(id,nullptr,0);
        (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ myshell]$ make
g++ -o myshell myshell.cc -std=c++11 #-std=c99 
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# ls -a  -l
echo ls -a  -l
total 32
drwxrwxr-x 2 user1 user1  4096 Apr 28 20:23 .
drwxrwxr-x 3 user1 user1  4096 Apr 28 17:28 ..
-rw-rw-r-- 1 user1 user1    90 Apr 28 20:02 Makefile
-rwxrwxr-x 1 user1 user1 13904 Apr 28 20:23 myshell
-rw-rw-r-- 1 user1 user1  2499 Apr 28 20:23 myshell.cc
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd
echo pwd
/home/user1/linux-learning/linux/26-4-29/myshell

再改地优雅点

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return true;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

int Execute()//执行命令
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        //程序替换
        execvp(g_argv[0],g_argv);
        exit(1);
    }

    //father
    pid_t rid = waitpid(id,nullptr,0);
    (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    return 0;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        CommandParse(commandline);
        //PrintArgv();
        

        //4.执行命令
        Execute();
    }
    return 0;
}

我们发现我们的命令都是带很长的路径的,修改一下

小调整
复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

// / /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);//逆向找
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);//+1是为了不想加上前面/

}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return true;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

int Execute()//执行命令
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        //程序替换
        execvp(g_argv[0],g_argv);
        exit(1);
    }

    //father
    pid_t rid = waitpid(id,nullptr,0);
    (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    return 0;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        CommandParse(commandline);
        //PrintArgv();
        

        //4.执行命令
        Execute();
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ myshell]$ make
g++ -o myshell myshell.cc -std=c++11 #-std=c99 
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ myshell]# ls -a -l
echo ls -a -l
total 32
drwxrwxr-x 2 user1 user1  4096 Apr 28 20:39 .
drwxrwxr-x 3 user1 user1  4096 Apr 28 17:28 ..
-rw-rw-r-- 1 user1 user1    90 Apr 28 20:02 Makefile
-rwxrwxr-x 1 user1 user1 14840 Apr 28 20:39 myshell
-rw-rw-r-- 1 user1 user1  2827 Apr 28 20:39 myshell.cc
[user1@iZ5waahoxw3q2bZ myshell]# pwd
echo pwd

4.第四版

user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell# cd ..

user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell#

我们发现输入cd ..路径没有改变

为什么不变化呢?

因为目前的命令都是让子进程去执行的!cd真正要更改的是bash的路径。所以遇到cd这样的命令不能让子进程去执行,应该让父进程自己亲自执行然后把自己切掉。这种命令称之为内建命令

内建命令检查

bool CheckAndExecBuiltin()

{

std::string cmd = g_argv0;

if(cmd == "cd")

{

return true;

}

return false;

}

//4.检测并执行内建命令

if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行

continue;

CommandParse中

return g_argc > 0 ? true : false;向调用者返回命令解析是否成功

if(!CommandParse(commandline)) continue;保证代码健壮性

chdir 更改工作路径 哪个进程调chdir就更改哪个路径

NAME

chdir, fchdir - change working directory

SYNOPSIS

#include <unistd.h>

int chdir(const char *path);

int fchdir(int fd);

进程先变,然后就是环境变量

要执行内建命令要bash自己去执行

内建命令
复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()//获取家目录
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}

// / /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);//逆向找
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);//+1是为了不想加上前面/

}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        if(g_argc == 1)
        {
            std::string home = GetHome();
            if(home.empty()) return true;
            chdir(home.c_str());
        }
        else
        {
            std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where
            //cd - /cd ~ //但是还有类似这两个这种不支持
            if(where == "-")
            {
                //TODO
            }
            else if(where == "~")
            {
                //TODO
            }
            else
            {
                chdir(where.c_str());
            }
        }
        return true;
    }

    return false;
}

int Execute()//执行命令
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        //程序替换
        execvp(g_argv[0],g_argv);
        exit(1);
    }

    //father
    pid_t rid = waitpid(id,nullptr,0);
    (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    return 0;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        //printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();
        
        //4.检测并执行内建命令
        if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行
            continue;

        //5.执行命令
        Execute();
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd
/home/user1/linux-learning/linux/26-4-29/myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# cd
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd
/home/user1
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# cd /
[user1@iZ5waahoxw3q2bZ /home/user1/linux-learning/linux/26-4-29/myshell]# pwd
/

但是我们发现路径表示没有改变

所以其实当进程或者shell路径发生变化时,一定是进程路径先变才更新的环境变量。

所以不用环境变量去获取,用getcwd();

系统调用

getcwd 获得当前进程的工作路径

NAME

getcwd, getwd, get_current_dir_name - get current working directory

SYNOPSIS

#include <unistd.h>

char *getcwd(char *buf, size_t size);

char *getwd(char *buf);

char *get_current_dir_name(void);

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

//for test
char cwd[1024];
char cwdenv[1024];


const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    //const char *pwd = getenv("PWD");
    const char *pwd = getcwd(cwd,sizeof(cwd));
    if(pwd != NULL)
    {
        snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd);
        putenv(cwdenv);
        //return "None";
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()//获取家目录
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}

//command
bool Cd()
{
    ///std::string where;
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where
        //cd - /cd ~ //但是还有类似这两个这种不支持
        if(where == "-")
        {
            //TODO
        }
        else if(where == "~")
        {
            //TODO
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

// / /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);//逆向找
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);//+1是为了不想加上前面/

}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }

    return false;
}

int Execute()//执行命令
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        //程序替换
        execvp(g_argv[0],g_argv);
        exit(1);
    }

    //father
    pid_t rid = waitpid(id,nullptr,0);
    (void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    return 0;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        //printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();
        
        //4.检测并执行内建命令
        if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行
            continue;

        //5.执行命令
        Execute();
    }
    return 0;
}

5.echo

int status = 0;//退出信息

其实echo命令也是一条内建命令

linux中有些命令可能既属于内建又属于独立的命令,说明那些执行两份,shell执行一份,进程执行一份

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

//for test
char cwd[1024];
char cwdenv[1024];

//last exit code 最后退出码
int lastcode=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    //const char *pwd = getenv("PWD");
    const char *pwd = getcwd(cwd,sizeof(cwd));
    if(pwd != NULL)
    {
        snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd);
        putenv(cwdenv);
        //return "None";
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()//获取家目录
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}

//command
bool Cd()
{
    ///std::string where;
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where
        //cd - /cd ~ //但是还有类似这两个这种不支持
        if(where == "-")
        {
            //TODO
        }
        else if(where == "~")
        {
            //TODO
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

// / /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);//逆向找
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);//+1是为了不想加上前面/

}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        if(g_argc==2)//要有两个命令行参数
        {
            //echo "hello world"
            //echo $?
            //echo $PATH
            std::string opt = g_argv[1];
            if(opt == "$?")
            {
                std::cout<< lastcode <<std::endl;
                lastcode = 0;
            }
            if(opt[0] == '$')
            {
                std::string env_name = opt.substr(1);
                const char *env_value = getenv(env_name.c_str());
                if(env_value)
                    std::cout<< env_value <<std::endl;
            }
        }
        return true;
    }

    return false;
}

int Execute()//执行命令
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        //程序替换
        execvp(g_argv[0],g_argv);
        exit(1);
    }
    int status = 0;//退出信息
    //father
    pid_t rid = waitpid(id,&status,0);
    //(void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    if(rid>0)
    {
        lastcode=WEXITSTATUS(status);
    }
    return 0;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        //printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();
        
        //4.检测并执行内建命令
        if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行
            continue;

        //5.执行命令
        Execute();
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ myshell]$ make
g++ -o myshell myshell.cc -std=c++11 #-std=c99 
[user1@iZ5waahoxw3q2bZ myshell]$ ./myshell
[user1@iZ5waahoxw3q2bZ myshell]# echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/user1/.local/bin:/home/user1/bin
[user1@iZ5waahoxw3q2bZ myshell]# echo $?
0

shell有两张表,一张命令行参数表,一张环境变量表

environ 是一个指向环境变量数组的全局指针 ,在 C/C++(Unix/Linux)中可用。它定义在 <unistd.h> 中(POSIX),允许程序直接访问整个环境变量列表。

SYNOPSIS
#include <unistd.h>

extern char **environ;

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<cstring>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

//环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;

//for test
char cwd[1024];
char cwdenv[1024];

//last exit code 最后退出码
int lastcode=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    //const char *pwd = getenv("PWD");
    const char *pwd = getcwd(cwd,sizeof(cwd));
    if(pwd != NULL)
    {
        snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd);
        putenv(cwdenv);
        //return "None";
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()//获取家目录
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}

void InitEnv()//初始化一个用于保存环境变量的全局数据结构
{
    extern char **environ;//声明一个环境变量所对应的信息
    memset(g_env,0,sizeof(g_env));
    g_envs=0;

    //本来要从配置文件来
    //1.获取环境变量
    for(int i=0;environ[i];i++)
    {
        //申请空间
        g_env[i]= (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i],environ[i]);
        g_envs++;
    }
    g_env[g_envs++] = (char*)"HAHA=for_test";//for test 为了验证是我们自己的环境变量,如果有就说明是
    g_env[g_envs] = NULL;

    //2.导成环境变量
    for(int i =0; g_env[i];i++)
    {
        putenv(g_env[i]);
    }
}

//command
bool Cd()
{
    ///std::string where;
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where
        //cd - /cd ~ //但是还有类似这两个这种不支持
        if(where == "-")
        {
            //TODO
        }
        else if(where == "~")
        {
            //TODO
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

void Echo()
{
    if(g_argc==2)//要有两个命令行参数
    {
        //echo "hello world"
        //echo $?
        //echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            std::cout<< lastcode <<std::endl;
            lastcode = 0;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout<< env_value <<std::endl;
        }
        else
        {
            std::cout<< opt << std::endl;
        }
    }
}

// / /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);//逆向找
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);//+1是为了不想加上前面/

}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }

    return false;
}

int Execute()//执行命令
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        //程序替换
        execvp(g_argv[0],g_argv);
        exit(1);
    }
    int status = 0;//退出信息
    //father
    pid_t rid = waitpid(id,&status,0);
    //(void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    if(rid>0)
    {
        lastcode=WEXITSTATUS(status);
    }
    return 0;
}

int main()
{
    // shell启动的时候,需要从系统中获取环境变量
    // 我们的环境变量信息应该从父shell统一来
    InitEnv();

    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        //printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();
        
        //4.检测并执行内建命令
        if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行
            continue;

        //5.执行命令
        Execute();
    }
    //cleanup();
    return 0;
}

然后make,接着./myshell之后env,发现我们环境变量表里面有这个HAHA=for_test

所以这样也就能明白之前讲的export

export

export 的核心作用

  • 将一个变量标记为 环境变量,添加到当前 Shell 的环境表中。

  • 之后通过 fork 创建的子进程(例如执行外部命令)会自动复制当前的环境变量,从而能访问到这些导出的变量。

alias也是一个内建命令

复制代码
[user1@iZ5waahoxw3q2bZ myshell]$ cat myshell.cc
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<cstring>
#include<unordered map>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//下面是shell定义的全局数据

//1.命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc=0;

//2.环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;

//3.别名映射表
std::unordered_map<std::string,std::string> alias_list;

//for test
char cwd[1024];
char cwdenv[1024];

//last exit code 最后退出码
int lastcode=0;

const char *GetUserName()//获取用户名
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()//获取主机名
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()//获取当前路径
{
    //const char *pwd = getenv("PWD");
    const char *pwd = getcwd(cwd,sizeof(cwd));
    if(pwd != NULL)
    {
        snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd);
        putenv(cwdenv);
        //return "None";
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()//获取家目录
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}

void InitEnv()//初始化一个用于保存环境变量的全局数据结构
{
    extern char **environ;//声明一个环境变量所对应的信息
    memset(g_env,0,sizeof(g_env));
    g_envs=0;

    //本来要从配置文件来
    //1.获取环境变量
    for(int i=0;environ[i];i++)
    {
        //申请空间
        g_env[i]= (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i],environ[i]);
        g_envs++;
    }
    g_env[g_envs++] = (char*)"HAHA=for_test";//for test 为了验证是我们自己的环境变量,如果有就说明是
    g_env[g_envs] = NULL;

    //2.导成环境变量
    for(int i =0; g_env[i];i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}

//command
bool Cd()
{
    ///std::string where;
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
    }
    else
    {
        std::string where = g_argv[1];//将命令解析后的第二个参数(g_argv[1])复制给一个 std::string 对象 where
        //cd - /cd ~ //但是还有类似这两个这种不支持
        if(where == "-")
        {
            //TODO
        }
        else if(where == "~")
        {
            //TODO
        }
        else
        {
            chdir(where.c_str());
        }
    }
    return true;
}

void Echo()
{
    if(g_argc==2)//要有两个命令行参数
    {
        //echo "hello world"
        //echo $?
        //echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            std::cout<< lastcode <<std::endl;
            lastcode = 0;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout<< env_value <<std::endl;
        }
        else
        {
            std::cout<< opt << std::endl;
        }
    }
}

// / /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);//逆向找
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);//+1是为了不想加上前面/

}

void MakeCommandLine(char cmd_prompt[],int size)//制作一个commandline
{
    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt,sizeof(prompt));
    printf("%s",prompt);
    fflush(stdout);
}

bool GetCommandLine(char *out,int size)
{
    //ls -a -l => "ls -a -l\n" 字符串
    char *c = fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc=0;
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline,SEP);
    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for(int i=0;g_argv[i];i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    else if(cmd == "export")
    {
        
    }
    else if(cmd == "alias")
    {
        //std::string nickname = g_argv[i];
        //alias_list.insert(k,v);
    }

    return false;
}

int Execute()//执行命令
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        //程序替换
        execvp(g_argv[0],g_argv);
        exit(1);
    }
    int status = 0;//退出信息
    //father
    pid_t rid = waitpid(id,&status,0);
    //(void)rid; //让rid使用一下,为了消除"未使用的变量 rid"的编译器警告
    if(rid>0)
    {
        lastcode=WEXITSTATUS(status);
    }
    return 0;
}

int main()
{
    // shell启动的时候,需要从系统中获取环境变量
    // 我们的环境变量信息应该从父shell统一来
    InitEnv();

    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
            continue;
        //printf("echo %s\n",commandline); 

        //3.命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();
        
        //检测别名

        //4.检测并执行内建命令
        if(CheckAndExecBuiltin())//是内建命令就不再向下执行代码,避免创建子进程去执行
            continue;

        //5.执行命令
        Execute();
    }
    //cleanup();
    return 0;
}
变量 含义 示例(命令 ls -a -l
g_argc 命令中单词的个数 3("ls"、"-a"、"-l")
g_argv 指向每个单词的指针数组 g_argv[0] → "ls" g_argv[1] → "-a" g_argv[2] → "-l" g_argv[3] → NULL

真正执行系统命令的地方是 Execute() 函数内部的 execvp 调用

感谢你的观看,期待我们下次再见!

相关推荐
c238566 小时前
Linux C++ 进度条进阶美化与工程化封装
linux·运维·服务器
玖玥拾7 小时前
C/C++ 基础笔记(十四)多态与模板编程
c语言·c++·多态·模板
凡人叶枫9 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
RisunJan9 小时前
Linux命令-pgrep (通过进程名查找进程 ID)
linux·运维
信创工程师-小杨10 小时前
Linux内网环境如何解决依赖的问题
linux·运维·服务器
设计师小聂!10 小时前
宝塔 Linux 面板保姆级教程
linux·mysql·开源·运维开发
不吃土豆的马铃薯11 小时前
C++ 高性能网络缓冲区 Buffer 源码解析
linux·服务器·开发语言·网络·c++
java知路11 小时前
linux yum 下载docker安装包及依赖安装包,并离线安装
linux·运维·docker
fanged11 小时前
设备树学习2--一个DTBO实验
linux·嵌入式开发
caimouse11 小时前
Reactos 第1章 概述
c语言·开发语言·架构