0

我正在尝试编写一个创建 UNIX shell 的 C 程序。在此 shell 中,当键入 UNIX 命令时,shell 应在前台或后台(指定 & 时为后台)执行它。我正在获取在前台运行的命令,但我无法在后台运行它。

这是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#define MAX_LENGTH 1024
#define DELIMS " \t\r\n"

int main(int argc, char *argv[])
{
    char *cmd, *bg;
    char line[MAX_LENGTH];
    pid_t fpid,bpid;
    int status;
    while (1)
    {
           fpid=10;
           bpid=10;
            printf("myshell > ");
            if (!fgets(line, MAX_LENGTH, stdin))
                    break;
             int j=0;

            if(cmd = strtok(line, DELIMS))
            {

                    bg = strtok(line," ");
                    while(bg!=NULL)
                    {
                            printf("%s",bg);
                            bg = strtok(NULL, " ");
                            if(strcmp(bg, "&") == 0)
                                    break;
                    }

                    printf("%s", bg);
                    if(strcmp(cmd,"exit")==0)
                            break;

                    else if(strcmp(bg,"&")==0)
                    {
                            bpid=fork();
                            //waitpid(bpid,&status,0);
                            system(line);
                            exit(0);
                    }
                    else
                   {
                            //fpid=fork();
                            //if(fpid==0)
                            //{
                                    system(line);
                            //      exit(0);
                            //}
                            //else 
                            //{
                            //      waitpid(fpid,&status,0);
                            //}
                    }
            }
    }

  return(0);
}

此代码用于我的作业。

4

2 回答 2

1

这是从问题中的代码派生的代码,它发出提示,获取输入行,将其拆分为标记,检测到最后一个标记是&,并检测到第一个单词是exit并退出循环。它会仔细打印出它发现的东西。你现在需要处理 fork、exec、wait 等代码。

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAX_LENGTH 1024
#define DELIMS " \t\r\n"

int main(void)
{
    char line[MAX_LENGTH];
    char *ps1 = "toysh> ";

    while (fputs(ps1, stdout) > 0 && fgets(line, sizeof(line), stdin) != NULL)
    {
        char *cmd[100];
        char *bg = NULL;
        int j = 0;
        char *tokens = line;

        while ((cmd[j++] = strtok(tokens, DELIMS)) != NULL)
            tokens = NULL;

        assert(j < 100);
        /* The line has been tokenized into j-1 tokens */
        /* Print the tokens found */
        for (int i = 0; i < j; i++)
        {
            if (cmd[i] != 0)
                printf("%d: <<%s>>\n", i, cmd[i]);
            else
                printf("%d: NULL pointer\n", i);
        }

        assert(j > 0);
        if (j == 1)
            continue;   // No command

        j--;

        assert(j > 0);
        if (strcmp(cmd[j-1], "&") == 0)
        {
            printf("== Found &\n");
            bg = cmd[j-1];
            cmd[--j] = 0;
            if (j == 0)
            {
                puts("Syntax error: cannot have & on its own");
                continue;
            }
        }

        if (strcmp(cmd[0], "exit") == 0)
        {
            printf("== Found exit command\n");
            if (bg != NULL)
            {
                puts("Can't run exit in background");
                continue;
            }
            break;
        }

        /*
        ** Now you can do your fork, exec, waitpid work.  Note that the
        ** command is already split into words with the null pointer at
        ** the end.  This is what execv(), execve() and execvp() want
        */

    }
    putchar('\n');

    return(0);
}

请注意,该代码不会阻止您在一行中输入太多标记。它最终会通过assert. 你需要在某个时候做到这一点。

请求进一步协助

我对 fork 和 waitpid 工作很陌生。你能帮我吗?

您在另一个答案中得到了很好的建议。

添加:

#include <sys/wait.h>

添加:

static void run_command(char **argv, int bg_flag);

添加:

        /*
        ** Now you can do your fork, exec, waitpid work.  Note that the
        ** command is already split into words with the null pointer at
        ** the end.  This is what execv(), execve() and execvp() want
        */
        run_command(cmd, (bg != NULL));

新功能:

static void run_command(char **argv, int bg_flag)
{
    pid_t pid;

    fflush(0);   // Flush pending output

    if ((pid = fork()) < 0)
        printf("Fork failed\n");
    else if (pid > 0)
    {
        /* Parent shell */
        if (bg_flag == 0)
        {
            int status;
            int corpse;
            while ((corpse = waitpid(-1, &status, WNOHANG)) >= 0)
            {
                if (corpse != 0)
                    printf("Process %d exited with status 0x%.4X\n",
                           corpse, status);
                if (corpse == 0 || corpse == pid)
                    break;
            }
        }
        else
            printf("%d: %s running in background\n", pid, argv[0]);
    }
    else
    {
        /* Child process */
        execvp(argv[0], argv);
        fprintf(stderr, "%d: failed to execute %s (%d: %s)", (int)getpid(), argv[0], errno, strerror(errno));
        exit(1);
    }
}

你可以决定你的 shell 应该有多冗长,但是当你调试它时,更多的信息总比少的好。

此外,错误消息都应该转到stderr; 我留下了一个公平的数字stdout

于 2013-09-21T18:58:25.003 回答
1

阅读手册页fork()。返回码 0 表示您在孩子中,非零(非负)表示您是父母。您应该基于此具有不同的逻辑并使用system()(或exec*()在子分支中更好。

这是您应该拥有的典型逻辑:

tokenize(line)

if (last token is '&') {
    rc = fork();

    if (rc < 0)
        handle error;

    else if (rc > 0) {  /* in parent, rc = child pid */
        do whatever you planned to do in the parent process
    }
    else {  /* in child */
        use exec*() to start the child command
    }
}
else {  /* foreground execution */
    use system() to run command
}
于 2013-09-20T18:27:01.637 回答