精华内容
下载资源
问答
  • 实现一个简单的shell

    2018-04-05 20:44:00
    使用已学习的各种C函数实现一个简单的交互式Shell,要求:1、给出提示符,让用户输入一行命令,识别程序名和参数调用适当的exec函数执行程序,待执行完成后再次给出提示符。2、该程序可识别和处理以下符号:1) ...

    使用已学习的各种C函数实现一个简单的交互式Shell,要求:
    1、给出提示符,让用户输入一行命令,识别程序名和参数并调用适当的exec函数执行程序,待执行完成后再次给出提示符。
    2、该程序可识别和处理以下符号:
    1) 简单的标准输入输出重定向:仿照例 "父子进程ls | wc -l",先dup2然后exec。
    2) 管道(|):Shell进程先调用pipe创建管道,然后fork出两个子进程。一个子进程关闭读端,调用dup2将写端赋给标准输出,另一个子进程关闭写端,调用dup2把读端赋给标准输入,两个子进程分别调用exec执行程序,而Shell进程把管道的两端都关闭,调用wait等待两个子进程终止。
    实现步骤:
    1. 接收用户输入命令字符串,拆分命令及参数存储。(自行设计数据存储结构)
    2. 实现普通命令加载功能
    3. 实现输入、输出重定向的功能
    4. 实现管道
    5. 支持多重管道

       

    以上。

       

    出于简单,我假设我们输入的命令字符串是符合要求,没有错误的。

    我们要实现的有:普通命令;输入输出重定向;单个管道。有四种情况:ls -ahl 单个命令;ls -alh > a.txt 输出重定向;ls -ahl | grep root 管道;cat < a.txt输出重定向。其实更具体细分还有命令带参数和不带参数的情况。情况有这几种,我们应该用标志将他们区分,所以,储存命令的数据结构就很重要了。这是我设计的结构体:

    typedef struct My_order

    {

        char *argv[32]; //命令以及参数、文件

        int pipe;

        int right;

        int left;

    } My_order;

    我将其命名为My_order。现在我们要做的事是解析用户输入的命令字符串:ls -ahl | grep root 。理想情况下,我们应该将其拆分为 ls 、-ahl、|、grep、root这些字符串。该怎么拆分呢?观察命令字符串:命令参数之间用空格隔开的,我们可以利用这个特性。但是我们要自己造轮子么?不用,C库函数为我们提供了一个字符串分割函数strtok():

    原型:char *strtok(char *restrict s1,const char * restrict s2);

    描述:该函数把s1字符串分解为单独的记号。s2字符串包含了作为记号分隔符的字符。按顺序调用该函数。第一次调用时,s1应指向待分解的字符串。函数定位到非分隔符后的第一个记号分隔符,并用空字符替换它。函数返回一个指针,指向存储第一个记号的字符串。若未找到,返回NULL。再次调用strtok查找字符串中的更多记号。每次调用都返回指向下一个记号的指针。未找到返回NULL。

    于是,我们像下面这样调用该函数就可以完美的解决问题了。

    int resolve_order(My_order *my_order, char p[])

    {

        //先初始化

        my_order->pipe = my_order->left = my_order->right = 0;

        for (int i = 0; i != 32; i++)

        {

            my_order->argv[i] = NULL;

        }

     

        int i = 0;

        int option = 0;

     

        my_order->argv[i] = strtok(p, " ");

        while (my_order->argv[++i] = strtok(NULL, " "))

        {

            if (strcmp(my_order->argv[i], " | ") == 0)

            {

                my_order->pipe++;

            }

            else if (strcmp(my_order->argv[i], ">") == 0)

            {

                my_order->right++;

            }

            else if (strcmp(my_order->argv[i], "<") == 0)

            {

                my_order->left++;

            }

        }

        return 0;

    }

    当命令字符串中有管道,输入输出重定向符的时候,相应的值就要增加。但是最多也只能是1,再多的话,我这个简单的shell就不能胜任了。即像这样的命令:cat|cat|cat我是解决不了的。

    我们上面的示例命令有管道,所以我们要用到pipe函数,建立管道,使进程之间能够相互通讯。但是我们第一步是要创建进程,不多,一个就够了,使用fork()函数。但是在此之前我们还有问题要解决:是子进程解决管道前面的命令呢还是父进程先解决?子进程和父进程谁先执行?这里废话一点:以前有个牛人(抱歉不记得是谁了,若是知道请告知)做了个实验:观察父子进程谁先被执行,最后得出的结论是绝大部分情况下是父进程先抢到CPU资源。但是这并没有理论支撑。计算机科学没有理论来支持这个结论。(当故事听就好哈,不要较真,本人还是萌新。)虽然有大牛得出这样的结论来了,但是我还是没有遵循这个结论。^_^。所以我让子进程去执行管道前面的命令了,    哎。还好我写了这个博客,不然我会闹大笑话。必须要两个子进程,一个不行,除非我就执行这一个管道命令。exec族函数的一大特点是什么?执行完成指定程序之后根本就不回来!意味着这个进程死掉了,无论是父进程还是子进程都会被回收掉。所以还是要两个子进程,这里要注意的是,使用兄弟进程进行通讯的时候父进程应该使用waitpid函数进行非阻塞回收。但是在我的实现上依旧有那种阻塞情况发生,是在是不懂怎么回事。不过这不重要。(玛德,废话真多。)代码:

    void my_pipe(My_order *my_order)

    {

        int fd[2];

        int p_ret = pipe(fd);//fd[0]->r;fd[1]->w

        if (-1 == p_ret)

        {

            perror("pipe error ");

            exit(1);

        }

     

        int i = 0;

        int pid;

        for (; i != 2; i++)

        {

            if (!(pid = fork()))

            {

                break;

            }

        }

        if (0 == i)

        {

            if (strlen(my_order->argv[1]) > 1)

            {

                close(fd[1]);

                dup2(fd[0], STDIN_FILENO);

                execlp(my_order->argv[3], my_order->argv[3], my_order->argv[4], NULL);

            }

            else

            {

                close(fd[1]);

                dup2(fd[0], STDIN_FILENO);

                execlp(my_order->argv[2], my_order->argv[2], my_order->argv[3], NULL);

            }

        }

        else if (1 == i)

        {

            if (strlen(my_order->argv[1]) > 1)//有参数

            {

                close(fd[0]);

                dup2(fd[1], STDOUT_FILENO);

                execlp(my_order->argv[0], my_order->argv[0], my_order->argv[1], NULL);

            }

            else

            {

                close(fd[0]);

                dup2(fd[1], STDOUT_FILENO);

                execlp(my_order->argv[0], my_order->argv[0], NULL);

            }

        }

        else

        {

            waitpid(-1, NULL, WNOHANG);

            waitpid(-1, NULL, WNOHANG);

        }

     

        return 0;

    }

    我写的不够严谨,都没有什么错误检查。别像我这样写,要检查错误,检查函数返回值。

    比如就是万一有用户这样写 ls -alh | a.txt 虽然这样在真正的shell也不能通过,但是别人有错误提示啊。

    其实有管道这个是整个程序中最难的部分。接下来的重定向其实很简单的。进过我的测试(用我那点可怜的知识)发现,重定向无非三种正确(的简单的)情况:命令>命令;命令>文件;命令<文件。前面的部分全是命令,后面的就稍微有点不同。那么问题来了:如何判断后面的是文件还是命令?以有无后缀区分?但是在Linux中后缀是方便我们识别的而不是系统的刚需啊。我也经常看到gcc main.c  -o a这样的命令啊。(别喷别喷)没事,大部分的Linux命令都在/bin目录下呢。简单的实现也无需考虑那么多,现在就是我们需要去查看目录中有无对应字符串内容的命令。读目录也很简单啊,我的博客前几篇(忘了哪一篇了)介绍了读取指定目录获取文件数目内容。我们稍微变换一下就可以用来区分文件or命令了:

    int get_dirfile(char *name) //命令存在返回0;不存在返回-1;

    {

        DIR *dir = opendir(" / bin");

        struct dirent *di;

     

        while ((di = readdir(dir)) != NULL)

        {

            if (strcmp(di->d_name, name) == 0)

            {

                return 0;

                break;

            }

        }

        return -1;

    }

    //是命令就执行。是文件就打开(创建)。打开文件也很简单嘛:

    int open_file(char p[])

    {

        int o_ret = open(p, O_RDWR | O_CREAT | O_TRUNC, 0644);

        if (o_ret == -1)

        {

            perror("open file error ");

            exit(1);

        }

     

        return o_ret;

    }

     

    相关的函数、宏若不知道意思,请参阅前几篇(也忘了是哪一篇了)的介绍。

    接下来,就要解决重定向了。dup2函数一定是需要的(我也在前几篇介绍了的),这里就不介绍了。接下来就很简单了,就是每个命令就要确定一下参数有无。

    int exec_order(My_order *my_order)

    {

        if ((my_order->pipe == 0) && (my_order->left == 0) && (my_order->right == 0))

        {

            if (!fork())

            {

                if (my_order->argv[1] != NULL)

                    execlp(my_order->argv[0], my_order->argv[0], my_order->argv[1], NULL);

                else

                    execlp(my_order->argv[0], my_order->argv[0], NULL);

            }

            else

            {

                wait(NULL);

            }

        }

        else if ((my_order->pipe == 1) && (my_order->left == 0) && (my_order->right == 0))

        {

            my_pipe(my_order);

        }

        else if ((my_order->pipe == 0) && (my_order->left == 1) && (my_order->right == 0))

        {

            if (!fork())

            {

                execlp(my_order->argv[0], my_order->argv[0], my_order->argv[2], NULL);

            }

            else

            {

                wait(NULL);

            }

        }

        else if ((my_order->pipe == 0) && (my_order->left == 0) && (my_order->right == 1))

        {

            if (!fork())

            {

                if (strlen(my_order->argv[1]) > 1)

                {

                    int fd = open_file(my_order->argv[3]);

                    dup2(fd, STDOUT_FILENO);//执行之后,标准输入就指向了fd

                    execlp(my_order->argv[0], my_order->argv[0], my_order->argv[1], NULL);

                    close(fd);

                }

                else

                {

                    int fd = open_file(my_order->argv[2]);

                    dup2(fd, STDOUT_FILENO);

                    execlp(my_order->argv[0], my_order->argv[0], NULL);

                    close(fd);

                }

     

            }

            else

            {

                wait(NULL);

            }

        }

    }

    其实这里有个小问题,就是像ps这样的命令参数是没有-的,直接就是ps a这样。为了简便,先这样吧。

    main函数就很简单了。

    int main(void)

    {

        while (1)

        {

            My_order my_order;

            char p[32] = { '\0' };

            puts("GYJ_LoveDanDan@desktop:—————————————— - ");

            gets(p);

            //char p[8] = { "ls -alh | grep lovedan " };

            resolve_order(&my_order, p);

            exec_order(&my_order);

        }

        return 0;

    }

    写个这程序,真的是,感觉到了自己真是菜鸡。最开始的任务其实有这个:

    你的程序应该可以处理以下命令:
    ls-l-R>file1
    cat<file1|wc-c>file1
    注:表示零个或多个空格,表示一个或多个空格

    5. 支持多重管道:类似于cat|cat|cat

    我最开始为了解析字符串,操碎了心,眼看就要成功了,但是因为我用来储存的数据结构不好用于执行execlp函数,就放弃了,几经波折,我看透了。自己砍了要求,很勉强的实现了这个四不像shell。我想问人,没人回答我,我想查资料,没找到。这也许就是小说中散修和宗门的区别吧。

    转载于:https://www.cnblogs.com/love-DanDan/p/8724223.html

    展开全文
  • 一个简单的Linux Shell

    2017-04-26 22:48:32
    程序描述此程序实现了一个简单的Linux壳,支持输入各类命令参数,为其创建进程等待子进程结束。 程序实现思路这个程序大致可以分为两部分 ,其一为获取用户输入的命令及参数将其整理为数组,其二为创建子进程...

    程序描述

    此程序实现了一个简单的Linux壳,支持输入各类命令参数,并为其创建进程并等待子进程结束。

    程序实现思路

    这个程序大致可以分为两部分 ,其一为获取用户输入的命令及参数并将其整理为数组,其二为创建子进程并调用execvp将子进程用于执行输入的命令,其中拆分得到的用户输入的字符串为难点

    重难点解析

    接收用户输入

    接收用户的命令输入并不能简单的用scanf来实现,因为单纯的scsnf是不支持空格和tab的,而gets方法虽然能够接收空格却不被Linux系统所支持,所以我们选择了Linux所支持的gets的替代品——fgets。(更好的方法是scanf(“%[^\n]”, buffer);定义收到换行符才结束,其余的都接收。)

    函数原型

    char *fgets(char *buf, int bufsize, FILE *stream);
    其中buf代表用于接收用户输入的字符串指针,bufsize代表接收的数据大小,stream用于指定读取流。

    代码演示

    fgets(cmdline,CMDLEN,stdin);

    CMDLEN是我宏定义的cmdline的长度,stdin代表从键盘读取。

    注意

    fgets方法接收到字符串的最后带有换行符\n

    字符串解析

    接收到用户输入的字符串后需要根据空格将其解析,在这里我们用strtok方法来实现。

    使用示例

    char *args[PARAM];
    for(i=0;i<PARAM;i++){
        args[i]=(char*)malloc(CMDLEN);
    }
    char delims[]=" ";
    args[0]=strtok(cmdline,delims);
    //printf("%s",arg[0]);
    for(i=1;i<PARAM;i++){
        args[i]=strtok(NULL,delims);
        if(!args[i]){
            break;
        }
    }

    其中PARAM是我宏定义的最多参数个数,首先为char*开辟空间(一定要开辟不然无法使用),然后将分隔符存入一个字符数组中,我们要用空格分割所以存的是空格。strtok第一次使用要加第一个参数为需要分割的字符串指针,以后使用则设为NULL。每次返回一段字符,最后结束返回NULL(恰好execvp最后一个参数需要为NULL,所以很方便)
    戳这里了解更多strtok的知识

    创建子进程

    pid_t pid;
    pid=fork();
    if(pid==0){
        if(execvp(args[0], args)<0){
            printf("执行失败, errno is: %d\n", errno); 
        }
        //errno 2 代表文件找不到,如果没有去掉fgets读回来的换行符\n就会发生这种错误;
        //errno 14 代表参数最后不是空指针,注意要直接赋值为NULL而不是"NULL""";
        exit(0);
    }
    else{
        wait(NULL);
    }

    完整代码

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h> 
    #define CMDLEN 100 //命令参数最多接收100个字符
    #define PARAM 10  //最多支持参数个数
    
    /**
     * [deletEnter description]
     * 用于删除字符串后面的换行符
     * @param str [description]
     */
    void deletEnter(char *str){
        int i=0;
        while(1){
            if(str[i]=='\n'){ 
                str[i]='\0'; //查到有换行符就换成\0结束
                break;
            }else if(str[i]=='\0'){
                break;
            }
            i++;
        }
    }
    
    int main(){
        char cmdline[CMDLEN];
        char *args[PARAM];
        while(1){
            printf("[tyshell]# ");
            fflush(stdout);
            fgets(cmdline,CMDLEN,stdin);//不能用svanf(收不到空格)和gets(linux不支持)
            /**
             * 如果接收到exitq就退出,否则就解析命令创建子进程执行
             */
            if(strcmp(cmdline,"exit\n")==0){
                //fgets读取到的内容末尾有换行符故判断结束需要加上\n
                return 0;
            }else{
                int i=0;
                /**
                 * 调用方法删去字符串最后的换行符
                 */
                deletEnter(cmdline);
                //检验接收是否有误
                //printf("arglen=%d\n",arglen);
                //printf("%s\n",cmdline);
                //char *arg[arglen+1];  
                /**
                 * 指针初始化
                 */
                for(i=0;i<PARAM;i++){
                    args[i]=(char*)malloc(CMDLEN);
                }
                /**
                 * 使用strtok解析,拆分字符串,用法如下
                 */
                char delims[]={" "};
                args[0]=strtok(cmdline,delims);
                //printf("%s",arg[0]);
                for(i=1;i<PARAM;i++){
                    args[i]=strtok(NULL,delims);
                    if(!args[i]){
                        break;
                    }
                    //printf("%s",arg[i]);
                }
    
                //开始多线程的执行
                pid_t pid;
                pid=fork();
                if(pid==0){
                    if(execvp(args[0], args)<0){
                        printf("执行失败, errno is: %d\n", errno); 
                    }
                    //errno 2 代表文件找不到,如果没有去掉fgets读回来的换行符\n就会发生这种错误;
                    //errno 14 代表参数最后不是空指针,注意要直接赋值为NULL而不是"NULL""";
                    exit(0);
                }
                else{
                    wait(NULL);
                }
            }
        }
        return 0;
    }
    展开全文
  • 实验一 实现带参数的简单shell 一.... 利用课本第9页程序1-5框架,实现带参数的简单shell...fork()函数创建一个进程。新进程就是所谓子进程,它是执行fork()函数进程(父进程)“克隆”,也就是说,子进程

    实验一 实现带参数的简单shell


    一.实验要求

    利用课本第9页程序1-5的框架,实现带参数的简单shell,实现允许输入命令带参数的简单shell。

    (1)正确理解并使用系统调用fork(),execve()和waitpid(),特别是execve()函数。fork()函数创建一个新的进程。新进程就是所谓的子进程,它是执行fork()函数的进程(父进程)的“克隆”,也就是说,子进程执行的程序与父进程的完全一样。当fork()函数返回值为0时表示处于子进程中;而返回值大于0时表示处于父进程中,此时的返回值是子进程的进程id。因此,fork()的返回值可以用来划分仅仅适合父进程和子进程执行的程序段。fork()函数返回值为-1时表示出错。如果子进程只是运行与父进程完全一样的程序,那用处是很有限的。要让子进程运行不同于父进程的程序,就必须调用execve函数,它是所有其他exec函数的基础。execve函数把调用它的进程的程序,替换成execve函数的参数所指定的程序。运行execve函数成功后,进程将开始运行新的程序,也就是execve函数的参数所指定的程序。

    execve函数原型:int execve(const char *path, const char*argv[],const char *envp[]);

    其中:

    path:要执行的程序路径名,比如“/bin/ls”,“cd”,“/usr/bin/gcc”等等。

    argv:参数表,比如ls命令中可带的命令行参数-l,-a等。注意,argv的第一个元素必须是要执行的程序(命令)的路径名。

    envp:环境变量表,供要执行的命令使用。实参数用NULL或系统环境变量environ均可。注意,因为environ由系统提供,属于外部变量,所以说明时必须用“extern”修饰。

    例子:

    char *argv[] ={“gcc”, “-g”, “-c”, “hello.c”, NULL};

    char *argv1[] = {“/bin/ls”,“-l”, “-a”, NULL};

    execve(“/usr/bin/gcc”,argv, environ);      // 编译程序“hello.c”

    execve(“/bin/ls”,argv1, NULL);            // 执行命令“ls –l –a”

    execve(“/usr/ls”, argv1, NULL);            // 出错,因为目录/usr/下没有ls程序。

    // 注意,在argv1 的第一个字符串“/bin/ls”中,只有ls是有用的。

    系统调用waitpid()用于等待子进程结束、获取子进程的运行状态,详细说明在第八章。本实验仅仅用它使父进程等待子进程结束,因此维持程序1-5的用法即可。

    (2)根据简单shell的输入,构造execve函数的参数。

    根据程序1-5,数组buf保存用户的输入,包括命令和参数。由于shell命令的命令名和各参数之间是用空格分开,因此可以用空格作为分界符。通过一个循环可以把buf数组中的命令和各个参数依次分离开来,并赋给数组argv的各元素适当的指针值。argv数组的最后一个指针必须是NULL。接着就可以调用execve(argv[0],argv, environ)来执行用户输入的命令。

    提示:argv数组中各指针所指向的字符串,可以直接利用buf的存储空间,不需要另外分配内存。

    二.设计和实现的主要原理、构思、算法

     

    #include "apue.h"

    #include <sys/wait.h>

    #include <unistd.h>

    int main()

    {

           charbuf[MAXLINE]; //输入行的缓存

        pid_t pid;

           char*envp[]={NULL};

           char*argv[]={NULL};//存储命令的数组指针

           char str[1024][256];//二维数组用来分离输入行命令和参数

           int i,j,k,status;

          

           char path[256];//存储路径的数组

           printf("%%");

           while(fgets(buf,MAXLINE,stdin)!=NULL){

                  memset(path,0,sizeof(path));//路径数组初始化

                  if(buf[strlen(buf)-1]=='\n')

                         buf[strlen(buf)-1]=0;

                  j=i=k=0;

                  while(buf[j]!='\0')//判断输入行是否结束

                     {

                     if(buf[j]!=' ') //判断当前命令或参数是否结束,进入下一个

                        {

                        for(i=j;buf[i]!=''&&buf[i]!='\0';i++)

                         {

                           str[k][i-j]=buf[i];//将一个完整的命令或参数放入一个行不变的二维数组中

                          }

                         str[k][i-j]='\0';//存储完一个完整的命令或参数时,加上’\0’来表示结束

                         k++;

                         j=i;

                      }

                       else

                          {

                         j++;

                        }

                   }

           for(i=0;i<k;i++)

              {

               argv[i]=str[i];//将分离出来的命令或参数按顺序一一传递给argv[]数组

              }

           argv[k]=NULL;

           strcat(path,argv[0]);//确定argv[]数组第一个存储是路径

           if((pid=fork())<0)

              {

               err_sys("fork error");

             }

            else if(pid==0)

              {

              execve(path,argv,envp);

              err_ret("couldn'texecute:%s",argv[0]);

              exit(127);

             }

           if((pid=waitpid(pid,&status,0))<0)

             err_sys("waitpid error");

             printf("%% ");

           }

           exit(0);

    }

     

    三.实验结果

    源程序名: Experiment1.c

    可执行程序名:Experiment1

    编译方法:gcc Experiment1.c error.c –o Experiment1

    结束方法:ctrl+c

    运行示例:(1)./Experiment1

                         /bin/ls -l

                       

    (2)输入:pwd和man mkdir (提示错误,没有输入路径)

               /bin/pwd 和 /usr/bin/man mkdir (正确,说明有路径,输入的不管有参数和没参数的命令都能正常)

                       

       



    ③总 结


       a.fgets(由文件中读取一字符串,也可以从屏幕上输入一字符串。)

      表头文件

    include<stdio.h>

      定义函数

    char * fgets(char * s,int size,FILE *stream);

    s,数据存储位置;size,读取字符串的最大数量;stream,指向FILE结构的指针。

      函数说明

    fgets()用来从参数stream所指的文件内读入字符并存到参数s所指的内存空间,直到出现换行字符、读到文件尾或是已读了size-1个字符为止,最后会加上NULL作为字符串结束。

      返回值

    fgets()若成功则返回s指针,返回NULL则表示有错误发生。

      b.if(buf[strlen(buf)-1]=='\n')//输入不为换行

    buf[strlen(buf)-1]=0;

        因为fegts返回的每一行都是以换行符终止,后随一个null字节,故用strlen计算字符串的长度,然后用一个null字节替换换行符。这样做是因为execlp函数要求参数以null而不是以换行符结束。

      c.  execve(执行文件)

    在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execlexecleexeclpexecvexecvp)都是调用execve的库函数。

    execve(执行文件)

      在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execlexecleexeclpexecvexecvp)都是调用execve的库函数。

     表头文件

    #include<unistd.h>

      定义函数

    int execve(constchar * filename,char * const argv[ ],char * const envp[ ]);

      函数说明

    execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。

      返回值

     如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno

     

     

    展开全文
  • 用fork创建进程后执行的是和父进程相同的程序(但有可能执行不同代码分支),子进程往往要调用exec函数以执行一个程序。当进程调用一种exec函数时,该进程用户空间代码和数据完全被新程序替换。从新进程启动...

    替换原理:

    用fork创建进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换。从新进程的启动例程开始执行,调用exec函数并不创建新进程,所以调用exec函数前后进程的id并未改变。

    替换函数:

    有六种以exec开头的函数,统称exec函数:

    #include<unistd.h>

    int execl(const char*path,const char*arg)

    int execlp(const char*file,const char*arg)

    int execle(const char*path,const char*arg,...,char const envp[])

    int execv(const char*path,char *const argv[])

    int execvp(const char*file,char*const argv[])

    int execve(const char*path,char* const argv[],char*const envp[])

    这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。

    如果调用出错则返回-1。

    所以exec函数只有出错的返回值而没有成功的返回值。

    这些函数原型看起来很容易混淆,但只要掌握了规律就很好记。

    l(list):表示参数采用列表

    v(vector):参数用数组

    p(path):有p自动搜素环境变量path

    e(env):表示自己维护的环境变量

    事实上只有execve是真正的系统调用,其他几个函数最终都调用execve函数。

    下面使用exec函数来实现一个简单的shell

    直接上代码

      1 //模拟shell写一个咱们自己的微型shell
      2 //功能:myshell>ls
      3 //能够执行各种命令
      4 #include<stdio.h>
      5 #include<unistd.h>
      6 #include<stdlib.h>
      7 #include<errno.h>
      8 #include<string.h>
      9 //1.获取终端输入
     10 //2.解析输入(按空格解析到一个一个的命令参数)
     11 //3.创建一个子进程
     12 //          在子进程中经行程序替换,让子进程运行命令
     13 //4.等待子进程运行完毕,收尸,获取退出状态码
     14 int argc;
     15 char* argv[32];
     16 int param_parse(char *buff)
     17 {   
     18     if(buff==NULL)
     19     {   
     20         return -1;
     21     }
     22     char*ptr=buff;
     23     char*tmp=ptr;
     24     while((*ptr)!='\0')
     25     {   
     26         //当遇到空格,并且下一个位置不是空格的时候
     27         //将空格位置置‘\0’
     28         //不过我们将使用argv[argc]来保存这个字符串的位置
     29         if(*ptr==' '&&*(ptr+1)!=' ')
     30         {   
     31             *ptr='\0';
     32             argv[argc]=tmp;
     33             tmp=ptr+1;
     34             argc++;
     35         }
     36         ptr++;
     37     }
     38     argv[argc++]=tmp;
     39     argv[argc]=NULL;
     40 }
     41 int exec_cmd()
     42 {
     43     int pid=0;
     44     pid=fork();
     45     if(pid<0)
     46     {
     47         return -1;
     48     }
     49     else if(pid==0)
     50     {
     51         execvp(argv[0],argv);
     52         exit(0);
     53     }
     54     //父进程在这里必须等待子进程退出,来看看子进程为什么退出了
     55     //是不是出现了什么错误,通过获取状态吗,并且转换一下退出码所
     56     //对应的错误信息进行打印
     57     int statu;
     58     wait(&statu);
     59     //判断子进程是否是代码运行完毕退出
     60     if(WIFEXITED(statu))
     61     {
     62         //获取子进程的退出码,转换为文本信息打印
     63         printf("%s",strerror(WEXITSTATUS(statu)));
     64     }
     65     return 0;
     66 }
     67 int main()
     68 {
     69     while(1)
     70     {
     71         printf("myshell>");
     72         char buff[1024]={0};
     73         //%[^\n]获取数据直到遇到\n为止
     74         //%*c   清空缓冲区,数据都不要了
     75         scanf("%[^\n]%*c",buff);
     76        // printf("%s\n",buff);
     77         param_parse(buff);
     78         exec_cmd();
     79     }
     80 
     81 }
    

     

    展开全文
  • 1、给出提示符,让用户输入行命令,识别程序名和参数调用适当的exec函数执行程序,待执 行完成后再次给出提示符。 2、识别和处理以下符号: 简单的标准输入输出重定向(<和>):仿照例30.5 “wrapper”,先...
  • 在学习unix编程的过程中,发现系统还提供了一个popen函数,可以非常简单的处理调用shell,其函数原型如下: FILE *popen(const char *command, const char *type); 该函数的作用是创建一个管道,fork一个进程...
  • 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同代码分支),子进程往往要调用一种exec函数以执行一个程序。 当进程调用一种exec函数时,该进程用户空间代码和数据完全被新程序替换,从新程序的...
  • 自己实现一个shell

    2017-02-16 18:28:00
    用C实现一个简单的交互式shell,要求:当用户输入一行命令时,识别程序名和参数调用适当的exec函数执行程序,等待执行完成后给出提示符。  exec函数实际上是六种以exec开头的函数,统称exec函数。当进程调用exec...
  • 所以此时,子进程需要调用exec函数以执行一个程序,当进程调用一种exec函数时,该进程用户空间代码和数据完全被新程序替换,从新的程序的启动例程开始执行。调用exec创建新进程,所以调用exec前后该进程...
  • Linux Shell程序设计(1)

    千次阅读 2018-04-26 13:37:39
    实验十、Shell程序设计(1)一... 二、实验内容和实验步骤1、创建一个简单的列目录和日期的shell 脚本运行之。【操作步骤】⑴输入下列命令,创建一个新文件:cat &gt; new_script⑵输入下列行:echo “Your ...
  • 在Linux环境下,shell一个位于操作系统和用户应用程序之间工具,我们在shell终端下敲入命令运行,或者直接运行一个脚本文件,这背后都是shell在帮助我们解析命令并创建一个个子进程去执行。为了更深一层去探寻...
  • 在当前用户下创建一个文档(可以使用 vi/vim 命令来创建文件),新建一个文件 zkServer.sh,扩展名为 sh(sh代表shell),扩展名不影响脚本执行,见名知意就好. ***文档代码如下*** #!/bin/sh echo "s
  • 在本文中,我将研究如何创建 shell 脚本来运行简单的 .NET Core 应用程序。之所以有这研究议题,是因为本站(pzy.io)数据库需要每天定时备份,在备份完成后,将备份文件上传远程云存储,保证了数据安全。由于...
  • 实验三 实现带参数的简单Shell ​ 1. 实验内容 利用课本第9页程序1-5框架,实现允许输入命令带参数的简单shell。原来实现是不能够带参数...**fork()函数创建一个进程。新进程就是所谓子 进程,它是执行fork
  • 程序的执行过程

    2012-09-29 20:16:11
    其实不是那么简单就直接执行程序的main(Winmain)函数,双击打开相当于我们在cmd中使用命令打开该程序一样,是通过系统外壳Shell打开shell接收到程序启动请求,然后为程序做一些准备(包括分配内存、创建...
  • 简单的Linux脚本程序

    千次阅读 2013-11-04 16:03:32
    凡是使用Shell编程语言编写的程序都可以称为Shell脚本,通俗一点说,只要将一些Linux命令按顺序保存到一个文本文件中,给予这个文件可执行权限,那么这个文件就可以称为Shell脚本。当然,Shell脚本是为了完成...
  • 即使以最简单的形式,创建这样的扩展也是项艰巨的任务,其中涉及到文档记录薄且不断变化JavaScript API。 Argos使您可以使用每Linux用户已经非常熟悉的语言编写GNOME Shell扩展:Bash脚本。 更准确地说,...
  • 在此存储库中,您将找到我们的Shell版本:简单的Unix命令解释器,复制了简单外壳程序(sh)的基本功能,在这里我们将应用在学习编程语言C时获得的知识,与内核的系统调用。 :bookmark_tabs: 学习目标 外壳如何...
  • shell

    2016-12-14 23:43:30
    ###shell脚本编写###bash是GUN组织开发和推广的一个项目,bash脚本类似批处理,简单来说就是把许多指令集合在一起,提供循环,条件,判断等重要功能,语法简单实用,用以编写程序,大大简化管理员操作,可以完成...
  • 该存储库包含一个简单的Java应用程序,该应用程序输出字符串“ Hello world!”。 伴随一些单元测试,以检查主应用程序是否按预期工作。 这些测试的结果将保存到JUnit XML报告中。 jenkins目录包含您将在本教程...
  • 一个程序 1 Python Shell Shell (命令解释器):指提供交互式操作界面,能运行代码软件 Python Shell :安装Python后自带Python交互式解释器 进入:在终端中输入python , 回车 退出 : 在Python Shell中...
  • Shell编程规范与变量

    2020-10-25 23:56:57
    文章目录一.Shell脚本应用场景Shell编程规范**(1)shell的作用**(2)常见的Shell解释器程序有很多种(3)简单创建一个Shell脚本(4)执行脚本文件的三种方法二.更完善的脚本构成三.管道与重定向举例四.shell脚本变量详解...
  • Linux shell脚本基础

    2018-08-11 14:20:00
    shell脚本是包含一些命令或声明,符合一定格式的文本文件,通常用于自动化常用命令,执行系统管理和故障排除,创建简单的应用程序,处理文本或文件。shell脚本在一定程度上可以理解为是将各类命令预先放入到一个...
  • 1 Python ShellShell (命令解释器):指提供交互式操作界面,能运行代码软件Python Shell :安装Python后自带Python交互式解释器进入:在终端中输入python , 回车退出 :在Python Shell中输入exit(),回 车在 ...
  • 在我的程序中,我获取所有目录和文件(walk),然后将它们全部写入字典,将文件名作为键,路径作为值,然后从接口(tk.Entry)获取一个关键字,将所有匹配项返回到两个列表。我将显示它们(tk.Listbox)打开选定的一个...
  • 我们很高兴在此介绍我们新的开源方案,它能将Kubernetes中Operator的开发提升到一个全新的更简单的水平。它可以让你在15分钟内将你的小脚本变成完全成熟的Operator而吸引你。欢迎shell-operator[1]!目标shell-...
  • 该存储库包含一个简单的Java应用程序,该应用程序输出字符串“ Hello world!”。 伴随一些单元测试,以检查主应用程序是否按预期工作。 这些测试的结果将保存到JUnit XML报告中。 jenkins目录包含您将在本教程...
  • shell--重定向

    2020-02-20 23:20:51
    文章目录概念重定向文件描述符文件重定向相关例子从文件输入从文本或字符串输入创建空文件写入到/dev/null输出相关exec命令 概念 重定向 重定向指的是从文件、命令、程序、脚本获取输出...文件描述符是一个简单的非...
  • shell编程和unix命令

    2015-02-16 15:41:39
    3.1.4 创建一个crontab文件 24 3.1.5 列出crontab文件 24 3.1.6 编辑crontab文件 24 3.1.7 删除crontab文件 25 3.1.8 恢复丢失crontab文件 25 3.2 at命令 25 3.2.1 使用at命令提交命令或脚本 26...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 148
精华内容 59
关键字:

创建并执行一个简单的shell程序