在 C 中设计 shell 以执行由逻辑和连接的命令的问题

Problem in designing a shell in C to execute commands connected by logical and

我正在用 C 编写一个简单的 shell 来执行外部命令,例如 command1 & command2,这是我的代码:

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

static char line[1024];
char *and, *or, *col;
char *arg[1024];

static char* skipwhite(char* s)
{
        while (isspace(*s)) ++s;
        return s;
}
void run(char *cmd)
{
        *and='[=11=]';
        int stat;
        cmd= skipwhite(cmd);
        char *next= strchr(cmd, ' ');
        int i=0;
        printf("%s \n", cmd);

        while(next!=NULL)
        {
                *next='[=11=]';
                arg[i]=cmd;:
                i++;
                cmd= skipwhite(next+1);
                next= strchr(cmd, ' ');
        }
        //arg[i]='[=11=]';

        int shmid =  shmget(66, sizeof(int), IPC_CREAT | 0777);
        int *status=(int *)shmat(shmid, NULL, 0);
        *status=1;

        int pid=fork();
        if(pid==0)
        {
                int *v=(int *)shmat(shmid, NULL, 0);
                if(execvp(arg[0], arg)==-1)
                {
                        *v=0;
                        _exit(EXIT_FAILURE);
                }
        }
        else
        {
                        waitpid(pid, &stat, 0);
                        if(*status==0)
                                exit(0);
                        //printf("Trying to execute 2nd command\n");
                        cmd=and+1;
                        printf("%s \n", cmd);
                        and = strchr(cmd, '&');
                        if(and==NULL)
                        {
                                cmd= skipwhite(cmd);
                                next = strchr(cmd, ' ');
                                i=0;
                                while(next!=NULL)
                                {
                                        *next='[=11=]';
                                        arg[i]=cmd;
                                        i++;
                                        cmd= skipwhite(next+1);
                                        next= strchr(cmd, ' ');
                                }
                                arg[i]=cmd;
                                //arg[i+1]='[=11=]';

                                if(execvp(arg[0], arg)==-1)
                                        _exit(EXIT_FAILURE);
                                return;
                        }
                        else
                                run(cmd);
        }
}

int main()
{

        int status, pid;
        printf("SIMPLE SHELL made by me. Type 'exit' or send EOF to exit.\n");

        while(1)
        {
                printf("$> ");
                fflush(NULL);

                if(!fgets(line, 1024, stdin))
                        return 0;

                char *cmds= line;
                if(strcmp(cmds, "exit")==0)
                        exit(0);
                and= strchr(cmds, '&');
                or= strchr(cmds, '|');
                col= strchr(cmds, ';');

                if(and!=NULL)
                {
                        pid=fork();
                        if(pid==0)
                                run(cmds);
                        else
                                waitpid(pid, &status, 0);
                }
        }
        return 0;
}

我的动力是:

每当shell接收到外部命令时,它会自己分叉,假设新进程为1,命令为command1 & command2。 1 再次 fork 自己执行 command1 和 command2。如果 command1 成功执行,则仅创建 command2 的新进程,否则不会。

问题是,即使最后一个命令是正确的,也永远不会执行。我无法找出代码中的问题。

如果需要任何信息,请在下面发表评论。

在这个循环中

        while(next!=NULL)
        {
                *next='[=10=]';
                arg[i]=cmd;:
                i++;
                cmd= skipwhite(next+1);
                next= strchr(cmd, ' ');
        }
        //arg[i]='[=10=]';

您将字符串的所有部分复制到 arg 除了最后一个。

对于命令行中的最后一个标记,如果标记后没有额外的 space,您将获得指向 cmd 的有效指针,但对于 next,则为 NULL,留下未处理的令牌。

您还注释掉了 arg 数组的标记,这是 execv 函数族所必需的。

您可以修改循环:

        while(cmd && *cmd) {
            arg[i++]=cmd;
            if (next) {
                *next='[=11=]';
                cmd = skipwhite(next+1);
                next = strchr(cmd, ' ');
            }
            else {
                cmd = NULL;        
            }
        }
        arg[i]='[=11=]';

关于额外问题的更新:

你的命令末尾有一个'\n',因为你在阅读命令行后没有砍掉它。 函数 fgets 将整行读入缓冲区,直到(包括)'\n' 如果缓冲区足够大。

您只需要在调用 fgets:

后立即将其删除
               size_t len = strlen(line);
               if (line[len-1] == '\n')
                        line[len-1] = 0;

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

如果您为命令输入 1023 个字符,其中 '\n' 无法存储在缓冲区中,第一个版本也可以正常工作。

第二个版本只有在您输入少于 1023 个字符时才能正常工作。对于最大长度命令,它将删除最后一个字符。