C语言实现shell

本文详细介绍了使用C语言实现Shell的过程,包括获取用户名、切换工作目录、重定位功能相关函数如fork(),以及命令执行的相关函数。通过示例和程序流程图,帮助读者理解如何在Linux环境下创建和执行进程。

涉及到的相关函数

获取用户名 :

uid_t getuid(void);

struct passwd *getpwuid(uid_t uid);

//getpwuid 返回一个结构体指针,可以用来获取下列结构
struct passwd {
               char   *pw_name;       /* username */
               char   *pw_passwd;     /* user password */
               uid_t   pw_uid;        /* user ID */
               gid_t   pw_gid;        /* group ID */
               char   *pw_gecos;      /* user information */
               char   *pw_dir;        /* home directory */
               char   *pw_shell;      /* shell program */
           };

获取当前工作目录 :

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

getcwd可以将当前工作的绝对路径填充进buf指向的空间;
大小不超过size; 失败返回NULL指针, 并设置errno;

切换工作路径 :

int chdir(const char *path);

改变当前进程的工作路径,成功返回 0, 失败返回-1;
并设置errno;
The current working directory is the starting point for interpreting relative pathnames (those not starting with '/').

切换路径后修改命令行的提示

void swim_dir() {
    if (chdir(vec[1]) < 0){
        perror("chdir()");
        return;
    }
    has_ch_dir = 1;
}

重定位功能相关函数

复制文件描述符

int dup(int oldfd);
int dup2(int oldfd, int newfd);
//dup用未使用的最小文件file descriptor 复制当前oldfd,并返回;
//dup2将之前打开的newfd用来复制oldfd, 它会前关闭之前的newfd再
//指向oldfd,如果oldfd是无效的,那么它啥事也不做;
失败返回:-1,并设置errno;

啥是文件描述符 ?

文件描述符是数组下标, 数组里存放了打开文件结构体的指针, 指向的结构体里又有指向inode的指针; 该数组存在在每个进程空间里, 系统会默认打开默认stream: stdin/stdout/stderr;

在这里插入图片描述

fork() 复制产生子进程

fork可以产生一个新进程,通过duplicate 当前进程

fork后父子进程的区别:

  1. fork的返回值不一样, pid不一样, ppid不一样;
  2. 未决信号和文件锁不继承
  3. 资源利用量清零;

注意 : fork()进行写时复制(谁改变原内容, 谁复制)

在这里插入图片描述

pipe() 实现IPC机制
    调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信, 有一个读端和写端, 通过 filedes参数传给用户程序的两个文件描述符, filedes[0]指向管道的读端, filedes[1]指向管道的写端;
 /* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES */
struct fd_pair {
    long fd[2];
};
struct fd_pair pipe();

/* On all other architectures */
int pipe(int pipefd[2]);

在这里插入图片描述

管道用法的栗子

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){    
    pid_t pid;
    int fd[2];
    
    if (pipe(fd) < 0) {
        perror("pipe()");
        exit(1);
    } 
    
    if ((pid = fork()) < 0){
        perror("fork()");
        exit(1);
    }
    int n;
    char buf[20];
    if (pid > 0) {
        close(fd[0]);
        write(fd[1], "hello pipe\n", 11);
        wait(NULL);
    }
    else{
        close(fd[1]);
        sleep(1);
        n = read(fd[0], buf, 20);
        write(1, buf, n);
    }    

	return 0;
}

命令的执行相关函数

当进程调用一种exec函数时, 改函数的用户空间代码和数据完全被新程序替代, 从新程序的启动例程开始执行

//exec函数族
int execl(const char *pathname, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

调用exec函数的调用规则:

​ 一般父进程fork()后, 子进程调用exec函数族执行新的进程, 父进程调用wait()可以等待子进程退出

// fork()/ wait() / execl()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
    pid_t pid;

    puts("Begin!");
    fflush(NULL);
//注意在fork前刷新缓冲区;
    pid = fork();
    if (pid < 0) {
        perror("fork()");
        exit(1);
    }
    
    if (pid == 0) {
        //子进程执行replace成其他进程映像;
        execl("/bin/date", "date", "+%s", NULL);
        perror("execl()");
        exit(1);
    }

    wait(NULL);
//父进程隔这儿等着收尸
    puts("End!");
	return 0;
}

程序流程图

在这里插入图片描述

代码总览

/***************************************************************
	> File Name: fish.c
	> Author: Feiger
	> Mail:1162006607@qq.com 
	> Created Time: Mon 24 May 2021 09:15:26 PM CST
 **************************************************************/
#include <pwd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <glob.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#include <time.h>

#define MAX_N 1024
#define N 64
#define RED(msg) "\033[31m"#msg"\033[0m"
#define GOLD(msg) "\033[33m"#msg"\033[0m"
#define BLUE(msg) "\033[32m"#msg"\033[0m"
char *vec[MAX_N] = {0};
char *cmd[N][N] = {0};
char talk[][MAX_N] = {
    {"Blue... Blue!! Blue ?\n"},
    {"Are you teaching me how to do the job?\n"},
    {"I am a fish, not a shell, try some simple cmd!\n"},
    {"How dare you let me do so many homework?\n"},
    {"Can you say the language of fish ??\n"},
    {"Let me do some simple work!\n"}
};
int has_ch_dir = 1;
char path[MAX_N] = {0};
struct passwd *usr;

void prompt() {
    char buf[MAX_N] = {0};
    if (has_ch_dir) {
        has_ch_dir = 0;
        if (getcwd(buf, MAX_N) == NULL) {
            perror("getcwd()");
            exit(1);
        }
        int cnt = 0;
        for (int i = 0; buf[i]; i++) {
            if (buf[i] == '/') {
                cnt = 0;
                continue;
            }
            path[cnt++] = buf[i];
        } 
        path[cnt] = '\0';
    }
    fprintf(stdout, RED(%s) "@" GOLD(fish:), usr->pw_name);
    fprintf(stdout, BLUE(%s $), path);
}

void analysis(char *str, char **vec) {
    char argu[MAX_N][MAX_N] = {0};
    memset(vec, 0, sizeof(char *) * MAX_N);
    int cnt = 0;
    for (int i = 0,j = 0,t = 1; str[i] && str[i] !='\n'; i++){
        if (str[i] == ' ') {
            if (t) {cnt++,j = 0, t = 0;}
            continue;
        }
        t = 1;
        argu[cnt][j++] = str[i];
    }
    if (argu[0][0] == 0) return;
    for (int i = 0; i <= cnt; i++) {
        vec[i] = strdup(argu[i]);
    }
    return;
}


void my_execv() {
    pid_t cpid;
    if ((cpid = fork()) < 0) {
        perror("fork()");
        return;
    }
        
    if (cpid == 0) {
        execvp(vec[0], vec); 
        perror("myfish");
        exit(1);
    }
    else{
        int t;
        if (wait(&t) < 0){
            exit(1);
        }
    }
}

int redirctB() {
    int fd = open(cmd[1][0], O_WRONLY|O_CREAT, 0644);
    if (fd < 0) {
        perror("open()");
        return 1;
    }
    fflush(stdout);
    
    int out = dup(STDOUT_FILENO);
            
    if (dup2(fd, STDOUT_FILENO) < 0 || out < 0){
        perror("dup()");
        return 1;
    }
        
    pid_t pid = fork();
    if(pid < 0) {
        perror("fork()");
        return 1;
    }
        
    if (pid == 0) {
        execvp(cmd[0][0], cmd[0]);
        perror("execvp()");
        exit(1);
    } 
    dup2(out, STDOUT_FILENO);
    close(fd); 
    wait(NULL);
    return 0;
}

int redirctL() {
    int fd = open(cmd[1][0], O_RDONLY);
    if (fd < 0) {
        perror("open()");
        return 1;
    }
    fflush(stdin);

    int infd = dup(STDIN_FILENO);
    if (dup2(fd, STDIN_FILENO) < 0 || infd < 0){
        perror("dup()");
        return 1;
    }
    
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork()");
        return 1;
    }
    if (pid == 0) {
        execvp(cmd[0][0], cmd[0]);
        perror("execvp()");
        exit(1);
    }
    
    dup2(infd, STDIN_FILENO);
    close(infd);
    wait(NULL);
    return 0;
}

int redirPip() {
    int fd[2];
    pid_t pid1, pid2;

    if (pipe(fd) < 0) {
        perror("pipe()");
        return 1;
    }
     
    fflush(stdout);
    pid1 = fork();
    if (pid1 < 0) {
        perror("fork()");
        return 1;
    }
    
    if (pid1 == 0) {
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        execvp(cmd[0][0], cmd[0]);
        perror("execvp()");
        exit(1);
    }

    pid2 = fork();
    if (pid2 < 0){
        perror("fork()");
        return 1;
    }
    if (pid2 == 0){
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execvp(cmd[1][0], cmd[1]);
        perror("execvp()");
        exit(1);
    }
    close(fd[0]);
    close(fd[1]);
    waitpid(pid1, NULL, 0);
    waitpid(pid2, NULL, 0);
    return 0;
}

void swim_dir() {
    if (chdir(vec[1]) < 0){
        perror("chdir()");
        return;
    }
    has_ch_dir = 1;
}

int redirect() {
    char *flag = NULL, cnt = 0;
    
    if (vec[0] == NULL) return 0; 
    if (!strcmp(vec[0], "cd")) {
        swim_dir();
        return 0;
    }

    memset(cmd, 0, sizeof(cmd));
    for (int i = 0,t = 0; vec[i]; i++) {
        if (!strcmp(vec[i], "|")||!strcmp(vec[i], "<") || !strcmp(vec[i], ">")) {
            flag = strdup(vec[i]);
            cnt++;
            t = 0;
            continue;
        }
        cmd[cnt][t++] = vec[i];
    }
    if (strcmp(cmd[0][0], "blue") == 0) {
        printf("%s", talk[0]);
        return 0;
    }

    srand(time(0));
    if (cnt > 1) {
        printf("%s", talk[rand() % 6]);
        return 0;
    }

    if (flag == NULL) {
        my_execv();
        return -1;
    }
    
    if(strcmp(flag, ">") == 0) {
        redirctB();
    }
    else if (strcmp(flag, "<") == 0){
        redirctL();
    }
    else if (strcmp(flag, "|") == 0){
        redirPip();
    }
    return 0;
}

int main(int argc, char *argv[]){
    char *buff = NULL;
    size_t buff_size = 0;
    
    fprintf(stdout, BLUE(  Blue. Blue. Blue...\n));
    fprintf(stdout, "I'm a fish, nice to meet you!\n\n");
    
    usr = getpwuid(getuid());
    if (usr == NULL) {
        perror("getpwuid()");
        exit(1);
    }

    while (1) {
        prompt();
        if (getline(&buff, &buff_size, stdin) < 0){
            perror("getline()");
            break;
        }
        analysis(buff, vec);
        
        redirect();
    }

	return 0;
}

运行截图:

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值