从零开始学习Linux(8)----自定义shell

        shell从用户读入字符串“ls”,shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序,并等待这个进程结束。所以要写一个shell,需要循环以下过程:

        1. 获取命令行
        2. 解析命令行
        3. 建立一个子进程(fork)
        4. 替换子进程(execvp)
        5. 父进程等待子进程退出(wait)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


#define SIZE 1024
#define MAX_ARGC 64
#define SEP " " // 命令参数之间的分隔符

// 全局变量    
char *argv[MAX_ARGC]; // 用于存储解析后的命令行参数
char pwd[SIZE]; // 当前工作目录
char env[SIZE]; // for test
int lastcode = 0; // 最后一个子进程的退出状态

// 获取主机名 
const char* HostName()
{
    char *hostname = getenv("HOSTNAME"); // 从环境变量中获取HOSTNAME 
    if(hostname) return hostname; // 如果找到,返回主机名
    else return "None";// 否则返回"None" 
}

// 获取用户名 
const char* UserName()
{
    char *username = getenv("USER");
    if(username) return username; // 如果找到,返回用户名
    else return "None"; // 否则返回"None"
}

// 获取当前工作目录
const char *CurrentWorkDir()
{
    char *pwd = getenv("PWD"); // 应该是从环境变量中获取PWD 
    if(pwd) return pwd; // 如果找到,返回当前工作目录 
    else return "None"; // 否则返回"None" 
}

// 获取用户主目录  
char *Home()
{
    return getenv("HOME"); // 从环境变量中获取HOME 
}

// 与用户进行交互,获取命令字符串 
int Interactive(char out[], int size)
{
    // 输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin);// 从标准输入读取一行
    out[strlen(out)-1] = 0; //'\0', commandline是空串的情况?// 移除字符串末尾的换行符'\n' 
    return strlen(out);// 返回命令字符串的长度(不包括末尾的'\0')
}

// 分割用户输入的命令字符串为参数数组 
void Split(char in[])
{
    int i = 0; 
    argv[i++] = strtok(in, SEP); // "ls -a -l"// 使用空格作为分隔符分割字符串,并将第一个参数存入argv[0] 
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =// 继续分割并存储剩余参数
    // 下面的代码块试图修改参数列表以在ls命令后添加"--color"
    if(strcmp(argv[0], "ls") ==0)
    {
        argv[i-1] = (char*)"--color";// 这会覆盖最后一个参数
        argv[i] = NULL;// 确保argv数组以NULL结尾 
    }
}

// 执行命令  
void Execute()
{
    pid_t id = fork();// 创建一个新的子进程
    if(id == 0)
    {
        // 在子进程中执行命令 
        execvp(argv[0], argv);// 使用环境变量中的PATH来查找要执行的程序  
        exit(1);// 如果execvp失败(例如找不到程序),则退出子进程并返回1  
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);// 在父进程中等待子进程结束
    if(rid == id) lastcode = WEXITSTATUS(status); // 如果子进程正常结束,获取其退出状态并保存
    //printf("run done, rid: %d\n", rid);
}

int BuildinCmd()
{
    int ret = 0;
    // 检测 argv[0] 是否为 "cd",如果是则执行 cd 命令 
    // 1. 检测是否是内建命令, 是 1, 否 0
    if(strcmp("cd", argv[0]) == 0)
    {
        // 2. 执行
        // 标记为内建命令 
        ret = 1;
        // 获取 cd 命令的参数(要切换到的目录),如果没有参数则默认为家目录  
        char *target = argv[1]; //cd XXX or cd
        if(!target) target = Home();// 如果没有指定目录,则切换到用户家目录
         // 切换到目标目录
        chdir(target);
        // 获取当前工作目录并保存到 temp 变量中
        char temp[1024];
        getcwd(temp, 1024);
        // 构造新的环境变量 "PWD",并将其设置为当前工作目录 
        snprintf(pwd, SIZE, "PWD=%s", temp);
        putenv(pwd);
    }
    // 检测 argv[0] 是否为 "export",如果是则执行 export 命令
    else if(strcmp("export", argv[0]) == 0)
    {
        ret = 1;
        // 如果有参数,则将其设置为环境变量
        if(argv[1])
        {
            strcpy(env, argv[1]);
            putenv(env);
        }
    }
    // 检测 argv[0] 是否为 "echo",如果是则执行 echo 命令 
    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);
                }
            }
            else{
                 // 如果参数不是以 '$' 开头,则直接输出该参数 
                printf("%s\n", argv[1]);
            }
        }
    }
    return ret;
}

int main()
{
    while(1)
    {
        char commandline[SIZE];
        // 1. 打印命令行提示符,获取用户输入的命令字符串
        int n = Interactive(commandline, SIZE);
        if(n == 0) continue;
        // 2. 对命令行字符串进行切割
        Split(commandline);
        // 3. 处理内建命令
        n = BuildinCmd();
        if(n) continue;
        // 4. 执行这个命令
        Execute();
    }
   // for(int i=0; argv[i]; i++)
   // {
   //     printf("argv[%d]: %s\n", i, argv[i]);
   // }
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值