Linux | 实现mybash、mybash优化、Linux调试

目录

1. 编码实现bash功能

(1)简要框架

(2)详细知识准备

(3)代码部分

2. 优化升级mybash

(1)知识准备

 (2)ls命令代码实现

 (3)mybash代码扩充(完整代码)

3. Linux中的调试功能


1. 编码实现bash功能

内置命令(cd,exit...):内置命令只能对当前进程操作,不能通过命令解释器fork+exec产生子进程实现。

普通命令(pwd,ps,ls...):均为命令解释器bash自身先fork()再exec()替换为新进程实现。

实现命令解释器bash的功能(实现mybash.c):

(1)简要框架

(2)详细知识准备

  • fgets()会读入最后输入的\n。buff[strlen(buff)-1]=0;
  • strtok()【字符串分割函数】第一个参数为字符数组,即要被分隔的字符数组 。第二个参数为分隔符(字符串常量)。 

                                        

        第一次调用:第一个参数为待分割的字符数组,第二个参数为分隔符(字符串常量)。

        第二次调用:同一个函数中会记录上一次分割的位置,从其后开始分割。故第一个参数传NULL。

  • strcmp():字符串的比较应使用字符串比较函数。
  • 子进程完成exec替换后会先结束变为僵死进程。因此在父进程中执行wait(NULL); 不用获取退出码,唯一功能是保证子进程先结束不变为僵死进程。
  • perror("error");//打印函数perror可以打印"error"和错误原因。

        

  • 如何得到当前自己的身份$:普通用户;#:管理员root用户

        用户uid=0为root管理员用户,调用getuid()获取用户id判断。

        

  • 如何得到当前用户名:调用struct passwd* ptr=getpwuid(uid);

        

        

  • 如何获得主机名

        

  • 如何获得当前工作目录

        

  • 提示符格式:用户名@主机名 当前位置 用户id

        printf()实现变颜色:以\033[0m 代表结束颜色显示

 

 

  • cd~通过chdir()切换文件路径实现:

        

  • Linux系统中执行命令ls其实为ls --color的别名,执行自己的mybash时输入ls  --color可以达到有颜色的效果。

(3)代码部分

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pwd.h>
#define ARG_MAX 10

char* get_cmd(char* buff,char*myargv[])
{
    if(buff==NULL||myargv==NULL) return NULL;

    int i = 0;
    char *cmd = strtok(buff, " ");
    while(cmd!=NULL)
    {
        myargv[i++] = cmd;
        cmd = strtok(NULL, " ");
    }
    return myargv[0];//myargv中第一个参数为命令名
}

void run_cmd(char* cmd,char*myargv[])
{
    //cmd为命令名,myargv存命令名+参数
    if(cmd==NULL||myargv==NULL)
        return;

    pid_t pid = fork();
    if(pid==-1) 
    {
        printf("fork err\n");
        return;
    }
    else if(pid==0)//子进程~exec替换
    {
        execvp(cmd, myargv);
        perror("exec err");//perror自动输出具体错误信息
        exit(0);//替换失败~子进程退出!!(否则父子进程均会执行接下来的代码)
    }
    wait(NULL);//子进程结束,阻塞(避免产生僵死进程)
}

void Print_Info()
{
    char *user_str = "$";//$ 代表普通用户
    int uid = getuid();
    if(uid==0)
        user_str = "#";//uid==0~"#" 代表管理员用户root

    struct passwd *ptr = getpwuid(uid);
    if(ptr==NULL)
     {
        printf("mybash1.0>> ");//获取用户名失败~打印bash版本,再退出
        fflush(stdout);
        return;
     }
    char *user_name = ptr->pw_name; // pw_name:用户名

    char hostname[128] = {0};
    if(gethostname(hostname,128)==-1)
    {
        printf("mybash1.0>> ");//获取主机名失败~打印bash版本,再退出
        fflush(stdout);
        return;
    }

    char cur_dir[256]={0};
    if(getcwd(cur_dir,256)==NULL)
    {
        printf("mybash1.0>> ");//获取当前所在工作目录失败~打印bash版本,再退出
        fflush(stdout);
        return;
    }

    //用户名@主机名:当前工作目录 提示符  \033[0m代表结束颜色显示
    printf("\033[1;32m%s@%s\033[0m:\033[1;34m%s\033[0m%s ", user_name, hostname, cur_dir, user_str); 
    fflush(stdout);
}

int main()
{
    while(1)
    {
        Print_Info();

        char buff[128] = {0};
        fgets(buff, 128, stdin);
        buff[strlen(buff) - 1] = 0;//由于fgets()或得到的字符串末尾为"\n",将其赋为"\0"

        char *myargv[ARG_MAX] = {0};
        char *cmd = get_cmd(buff, myargv);
        if(cmd==NULL)
            continue;//若此次未输入命令~continue进入下一次循环

        else if(strcmp(cmd,"exit")==0)//exit 退出
            break;
            
        else if(strcmp(cmd,"cd")==0)//cd为内置命令 使用chdir()实现
        {
            //必须存在第二个参数~myargv[1]!=NULL
            if(myargv[1]!=NULL)
            {
                if(chdir(myargv[1])==-1)
                {
                    perror("cd error");
                }
            }
        }
        else
        {
            //普通命令~fork+exec
            run_cmd(cmd, myargv);
        }
    }
    exit(0);
}

2. 优化升级mybash

优化升级mybash即 使其可以执行mybin目录下自己写的命令

(1)知识准备

  • clear命令的实现:清屏+光标移至屏幕左上角   

        printf("\033[2J");//清屏   

        printf("\033[0H");//光标移到屏幕左上角

  • pwd命令的功能可以通过getcwd()函数实现(获取当前目录/直接路径)
  •  opendir()函数作用:打开一个目录并建立一个目录流。参数为目录名,返回值为指向该目录流的指针DIR*

    每次opendir()后别忘记最后closedir(DIR *dirp)

    readdir()函数返回一个结构体指针struct dirent*,参数为目录流指针DIR*。当函数返回值为NULL时,代表读到目录末尾。

  • Linux系统中以.开头的文件为隐藏文件,ls -a可以查看。
  • strncmp()函数可以比较字符串前n位是否相同。
  •  查看文件类型和权限(区分类型是为了实现打印不同颜色):

    lstat()函数:得到文件属性信息(第一个参数为文件名,第二个参数为要写入文件信息的结构体变量地址)。

     man inode:判断文件类型file type,参数m为struct stat{}中的成员st_mode。

    判断文件权限(判断文件是否为可执行文件):struct stat st;   if(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))

  • ls   /usr:ls+路径 可以查看该路径下的所有目录及文件
  • strcat()函数:字符串拼接函数,在第一个字符串'\0'位置替换拼接第二个字符串。

 (2)ls命令代码实现

#include<stdio.h>
#include<string.h>
#include<sys/stat.h>
#include<dirent.h>
#include<stdlib.h>
#include<unistd.h>

int main(int argc,char* argv[])
{
    char curr_dir[256]={0};//当前目录
    if(getcwd(curr_dir,256)==NULL)//getcwd得到当前路径
    {
        perror("ls error");
        exit(1);
    }

    DIR* p=opendir(curr_dir); //传参当前路径opendir打开目录流
    struct dirent* s=NULL;
    struct stat st;
    while((s=readdir(p))!=NULL)
    {
        if(strncmp(s->d_name,".",1)==0)//. ..均为隐藏文件~首字母为.的不打印
        {
            continue;
        }

        lstat(s->d_name, &st);//将文件相关信息写入结构体st中

        if (S_ISDIR(st.st_mode))//为目录文件~蓝色
        {
            printf("\033[1;34m%s\033[0m  ",s->d_name);
        }

        else if(S_ISFIFO(st.st_mode))//为管道文件~黄色+黑色背景
        {
            printf("\033[33;40m%s\033[0m  ",s->d_name);
        }

        else if(S_ISREG(st.st_mode))//普通文件~要判断是否为可执行文件
        {
            if(st.st_mode&(S_IXUSR|S_IXGRP|S_IXOTH))//可执行权限X
            {
                printf("\033[1;32m%s\033[0m  ",s->d_name);//绿色
            }
            else
            {
                 printf("%s  ",s->d_name);//普通文件
            }
        }
    }
    printf("\n");

    closedir(p);
    exit(0);
}

 (3)mybash代码扩充(完整代码)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pwd.h>
#define ARG_MAX 10
#define PATH_MYBIN "/home/stu/Desktop/Linuxstudy/Linux-6/mybin/"
//复制mybin文件夹的路径,在最后加上/

//执行自己的mybin目录下的命令(clear ls pwd ps)
char* get_cmd(char* buff,char*myargv[])
{
    if(buff==NULL||myargv==NULL) return NULL;

    int i = 0;
    char *cmd = strtok(buff, " ");
    while(cmd!=NULL)
    {
        myargv[i++] = cmd;
        cmd = strtok(NULL, " ");
    }
    return myargv[0];//myargv中第一个参数为命令名
}

void run_cmd(char* cmd,char*myargv[])
{
    //cmd为命令名,myargv存命令名+参数
    if(cmd==NULL||myargv==NULL)
        return;

    pid_t pid = fork();
    if(pid==-1) 
    {
        printf("fork err\n");
        return;
    }
    else if(pid==0)//子进程~exec替换
    {
        //execvp(cmd, myargv);

        char path[128] = {0};//路径+命令名
        //如果要执行的命令为/usr/ls这种含直接路径的 或 ./ls~可不用再拼接mybin路径
        if(strncmp(cmd,"./",2)==0||strncmp(cmd,"/",1)==0)
        {
            strcpy(path, cmd);
        }
        else
        {
            strcpy(path, PATH_MYBIN);//拷贝mybin目录路径到path
            strcat(path, cmd);//字符串拼接命令名称
        }
        execv(path,myargv);// 参数1:路径+名称;参数二:传参的数组
        perror("exec err"); // perror自动输出具体错误信息
        exit(0);//替换失败~子进程退出!!(否则父子进程均会执行接下来的代码)
    }
    wait(NULL);//子进程结束,阻塞(避免产生僵死进程)
}

void Print_Info()
{
    char *user_str = "$";//$ 代表普通用户
    int uid = getuid();
    if(uid==0)
        user_str = "#";//uid==0~"#" 代表管理员用户root

    struct passwd *ptr = getpwuid(uid);
    if(ptr==NULL)
     {
        printf("mybash1.0>> ");//获取用户名失败~打印bash版本,再退出
        fflush(stdout);
        return;
     }
    char *user_name = ptr->pw_name; // pw_name:用户名

    char hostname[128] = {0};
    if(gethostname(hostname,128)==-1)
    {
        printf("mybash1.0>> ");//获取主机名失败~打印bash版本,再退出
        fflush(stdout);
        return;
    }

    char cur_dir[256]={0};
    if(getcwd(cur_dir,256)==NULL)
    {
        printf("mybash1.0>> ");//获取当前所在工作目录失败~打印bash版本,再退出
        fflush(stdout);
        return;
    }

    //用户名@主机名:当前工作目录 提示符  \033[0m代表结束颜色显示
    printf("\033[1;32m%s@%s\033[0m:\033[1;34m%s\033[0m%s ", user_name, hostname, cur_dir, user_str); 
    fflush(stdout);
}

int main()
{
    while(1)
    {
        Print_Info();

        char buff[128] = {0};
        fgets(buff, 128, stdin);
        buff[strlen(buff) - 1] = 0;//由于fgets()或得到的字符串末尾为"\n",将其赋为"\0"

        char *myargv[ARG_MAX] = {0};
        char *cmd = get_cmd(buff, myargv);
        if(cmd==NULL)
            continue;//若此次未输入命令~continue进入下一次循环

        else if(strcmp(cmd,"exit")==0)//exit 退出
            break;
            
        else if(strcmp(cmd,"cd")==0)//cd为内置命令 使用chdir()实现
        {
            //必须存在第二个参数~myargv[1]!=NULL
            if(myargv[1]!=NULL)
            {
                if(chdir(myargv[1])==-1)
                {
                    perror("cd error");
                }
            }
        }
        else
        {
            //普通命令~fork+exec
            run_cmd(cmd, myargv);
        }
    }
    exit(0);
}

3. Linux中的调试功能

调试的是可执行程序,也可以说是在跟踪调试进程。

  • gcc -o main main.c -g  //编译时加-g可以调试
  • gdb main  //调试
  • l //部分显示代码       l 1 //展示代码第一行
  • b+行号 //加断点     info break //展示已有断点    delete+断点编号 //删除加的断点      b+函数名 //在函数中加断点
  • r 启动程序
  • n 单步执行
  • p buff //打印buff中的内容  p~展示内容
  •  s //进入函数 
  • display+变量名 //显示变量值
  • set follow-fork-mode child //规定fork后跟踪调试子进程(默认跟踪父进程)​​​​​​​
  • q //退出调试 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值