shell1

路径解析转换

路径有绝对路径和相对路径之分,在平常使用linux的过程中,我们在输入命令和参数的时候,往往使用的都是相对路径。相对路径是基于当前的工作路径的出的。

当前工作路径 + 相对路径 = 绝对路径

比如说当前工作路径为 /home/work 此时输入 ls file,那么file所在的绝对路径就是 /home/work/file 了。

因为这个kernel比较简单,如果路径以根目录/ 开头,就会被认为是绝对路径,其他的路径都会被认为是相对路径,会将其转换成绝对路径来使用。

以下是路径转换函数

/* 将路径old_abs_path中的..和.转换为实际路径后存入new_abs_path */
static void wash_path(char *old_abs_path, char *new_abs_path)
{
    assert(old_abs_path[0] == '/');
    char name[MAX_FILE_NAME_LEN] = {0};
    char *sub_path = old_abs_path;
    sub_path = path_parse(sub_path, name);
    if (name[0] == 0)
    { 
        // 若只键入了"/",直接将"/"存入new_abs_path后返回
        new_abs_path[0] = '/';
        new_abs_path[1] = 0;
        return;
    }
    new_abs_path[0] = 0; // 避免传给new_abs_path的缓冲区不干净
    strcat(new_abs_path, "/");
    while (name[0])
    {
        /* 如果是上一级目录“..” */
        if (!strcmp("..", name))
        {
            char *slash_ptr = strrchr(new_abs_path, '/');
            /*如果未到new_abs_path中的顶层目录,就将最右边的'/'替换为0,
     这样便去除了new_abs_path中最后一层路径,相当于到了上一级目录 */
            if (slash_ptr != new_abs_path)
            { // 如new_abs_path为“/a/b”,".."之后则变为“/a”
                *slash_ptr = 0;
            }
            else
            {   // 如new_abs_path为"/a",".."之后则变为"/"
                /* 若new_abs_path中只有1个'/',即表示已经到了顶层目录,就将下一个字符置为结束符0. */
                *(slash_ptr + 1) = 0;
            }
        }
        else if (strcmp(".", name))
        { // 如果路径不是‘.’,就将name拼接到new_abs_path
            if (strcmp(new_abs_path, "/"))
            { // 如果new_abs_path不是"/",就拼接一个"/",此处的判断是为了避免路径开头变成这样"//"
                strcat(new_abs_path, "/");
            }
            strcat(new_abs_path, name);
        } // 若name为当前目录".",无须处理new_abs_path

        /* 继续遍历下一层路径 */
        memset(name, 0, MAX_FILE_NAME_LEN);
        if (sub_path)
        {
            sub_path = path_parse(sub_path, name);
        }
    }
}

/* 将path处理成不含..和.的绝对路径,存储在final_path */
void make_clear_abs_path(char *path, char *final_path)
{
    char abs_path[MAX_PATH_LEN] = {0};
    /* 先判断是否输入的是绝对路径 */
    if (path[0] != '/')
    { 
        // 若输入的不是绝对路径,就拼接成绝对路径
        memset(abs_path, 0, MAX_PATH_LEN);
        if (getcwd(abs_path, MAX_PATH_LEN) != NULL)
        {
            if (!((abs_path[0] == '/') && (abs_path[1] == 0)))
            { // 若abs_path表示的当前目录不是根目录/
                strcat(abs_path, "/");
            }
        }
    }
    strcat(abs_path, path);
    wash_path(abs_path, final_path);
}

路径转换完成之后接下来就来实现几个常用的shell。

ls

ls的命令支持以下形式 ls -l, ls -h, ls filename,它们的功能自不必多说,函数的前半部分对其参数进行解析,后半部分根据参数对应的功能构造好路径,将对应的信息输出出来。

/* ls命令的内建函数 */
void buildin_ls(uint32_t argc, char **argv)
{
    char *pathname = NULL;
    struct stat file_stat;
    memset(&file_stat, 0, sizeof(struct stat));
    bool long_info = false;
    uint32_t arg_path_nr = 0;
    uint32_t arg_idx = 1; // 跨过argv[0],argv[0]是字符串“ls”
    while (arg_idx < argc)
    {
        if (argv[arg_idx][0] == '-')
        { // 如果是选项,单词的首字符是-
            if (!strcmp("-l", argv[arg_idx]))
            { // 如果是参数-l
                long_info = true;
            }
            else if (!strcmp("-h", argv[arg_idx]))
            { // 参数-h
                printf("usage: -l list all infomation about the file.\n-h for help\nlist all files in the current dirctory if no option\n");
                return;
            }
            else
            { // 只支持-h -l两个选项
                printf("ls: invalid option %s\nTry `ls -h' for more information.\n", argv[arg_idx]);
                return;
            }
        }
        else
        { // ls的路径参数
            if (arg_path_nr == 0)
            {
                pathname = argv[arg_idx];
                arg_path_nr = 1;
            }
            else
            {
                printf("ls: only support one path\n");
                return;
            }
        }
        arg_idx++;
    }

    if (pathname == NULL)
    { // 若只输入了ls 或 ls -l,没有输入操作路径,默认以当前路径的绝对路径为参数.
        if (NULL != getcwd(final_path, MAX_PATH_LEN))
        {
            pathname = final_path;
        }
        else
        {
            printf("ls: getcwd for default path failed\n");
            return;
        }
    }
    else
    {
        make_clear_abs_path(pathname, final_path);
        pathname = final_path;
    }

    if (stat(pathname, &file_stat) == -1)
    {
        printf("ls: cannot access %s: No such file or directory\n", pathname);
        return;
    }
    if (file_stat.st_filetype == FT_DIRECTORY)
    {
        struct dir *dir = opendir(pathname);
        struct dir_entry *dir_e = NULL;
        char sub_pathname[MAX_PATH_LEN] = {0};
        uint32_t pathname_len = strlen(pathname);
        uint32_t last_char_idx = pathname_len - 1;
        memcpy(sub_pathname, pathname, pathname_len);
        if (sub_pathname[last_char_idx] != '/')
        {
            sub_pathname[pathname_len] = '/';
            pathname_len++;
        }
        rewinddir(dir);
        if (long_info)
        {
            char ftype;
            printf("total: %d\n", file_stat.st_size);
            while ((dir_e = readdir(dir)))
            {
                ftype = 'd';
                if (dir_e->f_type == FT_REGULAR)
                {
                    ftype = '-';
                }
                sub_pathname[pathname_len] = 0;
                strcat(sub_pathname, dir_e->filename);
                memset(&file_stat, 0, sizeof(struct stat));
                if (stat(sub_pathname, &file_stat) == -1)
                {
                    printf("ls: cannot access %s: No such file or directory\n", dir_e->filename);
                    return;
                }
                printf("%c  %d  %d  %s\n", ftype, dir_e->i_no, file_stat.st_size, dir_e->filename);
            }
        }
        else
        {
            while ((dir_e = readdir(dir)))
            {
                printf("%s ", dir_e->filename);
            }
            printf("\n");
        }
        closedir(dir);
    }
    else
    {
        if (long_info)
        {
            printf("-  %d  %d  %s\n", file_stat.st_ino, file_stat.st_size, pathname);
        }
        else
        {
            printf("%s\n", pathname);
        }
    }
}

cd

/* cd命令的内建函数 */
char *buildin_cd(uint32_t argc, char **argv)
{
    if (argc > 2)
    {
        printf("cd: only support 1 argument!\n");
        return NULL;
    }

    /* 若是只键入cd而无参数,直接返回到根目录. */
    if (argc == 1)
    {
        final_path[0] = '/';
        final_path[1] = 0;
    }
    else
    {
        make_clear_abs_path(argv[1], final_path);
    }

    if (chdir(final_path) == -1)
    {
        printf("cd: no such directory %s\n", final_path);
        return NULL;
    }
    return final_path;
}

mkdir

/* mkdir命令内建函数 */
int32_t buildin_mkdir(uint32_t argc, char **argv)
{
    int32_t ret = -1;
    if (argc != 2)
    {
        printf("mkdir: only support 1 argument!\n");
    }
    else
    {
        make_clear_abs_path(argv[1], final_path);
        /* 若创建的不是根目录 */
        if (strcmp("/", final_path))
        {
            if (mkdir(final_path) == 0)
            {
                ret = 0;
            }
            else
            {
                printf("mkdir: create directory %s failed.\n", argv[1]);
            }
        }
    }
    return ret;
}

rmdir

/* rmdir命令内建函数 */
int32_t buildin_rmdir(uint32_t argc, char **argv)
{
    int32_t ret = -1;
    if (argc != 2)
    {
        printf("rmdir: only support 1 argument!\n");
    }
    else
    {
        make_clear_abs_path(argv[1], final_path);
        /* 若删除的不是根目录 */
        if (strcmp("/", final_path))
        {
            if (rmdir(final_path) == 0)
            {
                ret = 0;
            }
            else
            {
                printf("rmdir: remove %s failed.\n", argv[1]);
            }
        }
    }
    return ret;
}

rm

/* rm命令内建函数 */
int32_t buildin_rm(uint32_t argc, char **argv)
{
    int32_t ret = -1;
    if (argc != 2)
    {
        printf("rm: only support 1 argument!\n");
    }
    else
    {
        make_clear_abs_path(argv[1], final_path);
        /* 若删除的不是根目录 */
        if (strcmp("/", final_path))
        {
            if (unlink(final_path) == 0)
            {
                ret = 0;
            }
            else
            {
                printf("rm: delete %s failed.\n", argv[1]);
            }
        }
    }
    return ret;
}

ps

/* ps命令内建函数 */
void buildin_ps(uint32_t argc, char **argv UNUSED)
{
    if (argc != 1)
    {
        printf("ps: no argument support!\n");
        return;
    }
    ps();
}

clear

/* clear命令内建函数 */
void buildin_clear(uint32_t argc, char **argv UNUSED)
{
    if (argc != 1)
    {
        printf("clear: no argument support!\n");
        return;
    }
    clear();
}

pwd

/* pwd命令的内建函数 */
void buildin_pwd(uint32_t argc, char **argv UNUSED)
{
    if (argc != 1)
    {
        printf("pwd: no argument support!\n");
        return;
    }
    else
    {
        if (NULL != getcwd(final_path, MAX_PATH_LEN))
        {
            printf("%s\n", final_path);
        }
        else
        {
            printf("pwd: get current work directory failed.\n");
        }
    }
}

上面的很多对文件和目录操作的命令都是在文件系统中实现的,这里只是对其进一步的封装。

接下来就要为这些命令提供统一的调用接口,也就是shell拉。

shell

shell在执行的时候肯定要对用户的输入进行解析,根据解析结果找到对应的处理程序,然后调用执行,首先上命令解析函数

该函数主要是对命令进行拆分,根据其拆分标志,一般是空格,将一个个分割好的参数存入argv中。

/* 分析字符串cmd_str中以token为分隔符的单词,将各单词的指针存入argv数组 */
static int32_t cmd_parse(char *cmd_str, char **argv, char token)
{
    assert(cmd_str != NULL);
    int32_t arg_idx = 0;
    while (arg_idx < MAX_ARG_NR)
    {
        argv[arg_idx] = NULL;
        arg_idx++;
    }
    char *next = cmd_str;
    int32_t argc = 0;
    /* 外层循环处理整个命令行 */
    while (*next)
    {
        /* 去除命令字或参数之间的空格 */
        while (*next == token)
        {
            next++;
        }
        /* 处理最后一个参数后接空格的情况,如"ls dir2 " */
        if (*next == 0)
        {
            break;
        }
        argv[argc] = next;

        /* 内层循环处理命令行中的每个命令字及参数 */
        while (*next && *next != token)
        { // 在字符串结束前找单词分隔符
            next++;
        }

        /* 如果未结束(是token字符),使tocken变成0 */
        if (*next)
        {
            *next++ = 0; // 将token字符替换为字符串结束符0,做为一个单词的结束,并将字符指针next指向下一个字符
        }

        /* 避免argv数组访问越界,参数过多则返回0 */
        if (argc > MAX_ARG_NR)
        {
            return -1;
        }
        argc++;
    }
    return argc;
}

接下来就上shell拉。

void my_shell(void)
{
    cwd_cache[0] = '/';
    while (1)
    {
        print_prompt();
        memset(final_path, 0, MAX_PATH_LEN);
        memset(cmd_line, 0, MAX_PATH_LEN);
        readline(cmd_line, MAX_PATH_LEN);
        if (cmd_line[0] == 0)
        { 
            // 若只键入了一个回车
            continue;
        }
        argc = -1;
        argc = cmd_parse(cmd_line, argv, ' ');
        if (argc == -1)
        {
            printf("num of arguments exceed %d\n", MAX_ARG_NR);
            continue;
        }
        if (!strcmp("ls", argv[0]))
        {
            buildin_ls(argc, argv);
        }
        else if (!strcmp("cd", argv[0]))
        {
            if (buildin_cd(argc, argv) != NULL)
            {
                memset(cwd_cache, 0, MAX_PATH_LEN);
                strcpy(cwd_cache, final_path);
            }
        }
        else if (!strcmp("pwd", argv[0]))
        {
            buildin_pwd(argc, argv);
        }
        else if (!strcmp("ps", argv[0]))
        {
            buildin_ps(argc, argv);
        }
        else if (!strcmp("clear", argv[0]))
        {
            buildin_clear(argc, argv);
        }
        else if (!strcmp("mkdir", argv[0]))
        {
            buildin_mkdir(argc, argv);
        }
        else if (!strcmp("rmdir", argv[0]))
        {
            buildin_rmdir(argc, argv);
        }
        else if (!strcmp("rm", argv[0]))
        {
            buildin_rm(argc, argv);
        }
        else
        {
            printf("external command\n");
        }
    }
}

在上一节中说了,执行这个shell的是init进程fork出的子进程,所以要将这个myshell函数放入init进程中,当fork返回值为0时,就执行myshell

void init()
{
    pid_t pid = fork();

    if(fork)
    {
        // ...
    }
    else (fork == 0)
    {
        myshell();
    }
}

这就是init进程目前所做的工作,它是在内核主线程创建之前就已经创建好了的进程。

接下来就运行一下,享受一下成果。

mark

可以看到确实能进行一定程度上的交互了,但是目前的命令都是内部命令,写死在程序中的,这样想新增命令时非常不方便,所以后面要实现加载用户程序,让shell支持外部命令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值