【问题标题】:Use strtok() and execute UNIX command in background [closed]使用 strtok() 并在后台执行 UNIX 命令 [关闭]
【发布时间】:2013-09-26 05:36:49
【问题描述】:

我正在尝试编写一个创建 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);
}

此代码用于我的家庭作业。

【问题讨论】:

  • 使用strtok的第一条规则:不要使用strtok。
  • @Eugene 比我用什么代替 strtok ?
  • strtok_r 是可重入版本
  • 你能用我的代码解释一下吗?
  • 此代码中有一些严重的编译错误,您应该在将其提交给 SO 之前修复这些错误。如果文件名为 fork.c,则 GCC 4.8.1 会给出错误,例如:fork.c:33:20: error: incompatible types when assigning to type ‘char *[100]’ from type ‘char *’ if(cmd = strtok(line, DELIMS))fork.c:46:21: warning: passing argument 1 of ‘strcmp’ from incompatible pointer type [enabled by default] if(strcmp(cmd,"exit")==0) 并附注 /usr/include/string.h:87:6: note: expected ‘const char *’ but argument is of type ‘char **’ int strcmp(const char *, const char *);

标签: c unix background strtok


【解决方案1】:

这是从问题中的代码派生的代码,它发出提示,获取输入行,将其拆分为标记,检测到最后一个标记是 &amp;,并检测到第一个单词是 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留下了一个公平的号码。

【讨论】:

  • 我对 fork 和 waitpid 工作非常陌生。你能帮我吗?基本上,如果键入“ls”,则该命令应通过显示目录中存在的文件列表在前台运行。如果键入“ls &”,则该命令应在后台执行,并应立即显示提示符“myshell >”
  • 我更喜欢toysh 名称(以“my”开头的名称让我很烦恼,无论它们是文档文件夹、DBMS 还是外壳程序)。查看更新。
  • 非常感谢@JonathanLeffler。您更新的代码在后台运行良好。但是当我输入“ls &”时,它正在打印“ls 正在后台执行”并打印 ls 输出。我不希望它打印 ls 的输出。我该怎么做?
  • 你要给命令提供标准输出,所以fork之后可以关闭标准输出,然后open()空设备/dev/null,并使用dup2()保证它连接到STDOUT_FILENO(又名文件描述符1)(和close()文件描述符,如果它最初不是在1上打开的),以便子进程将写入/dev/null。当然,如果您正在执行 I/O 重定向 (&gt; some.file),那么您打开该文件而不是 /dev/null
  • 很抱歉,那里的很多东西我都听不懂。 :(因为我是新手
【解决方案2】:

阅读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
}

【讨论】:

  • 非常感谢您回复我。我有点不习惯用 C 编程。你介意详细说明/编辑我的代码吗?程序应在 shell 中解析用户输入以获取 &,然后在后台执行。
  • 查看更新的评论
  • 那么后台进程的代码适合放在哪一部分呢?以及如何解析最后一个标记,即 & ?
  • 1.您的后台进程在“使用 exec*() 启动子命令”行中启动。 2. 要解析'&',您需要标记命令(而不仅仅是空格,因为 foo& 也是有效命令)和 strcmp(tokens[ntoks - 1], "&")
  • 对不起,我还是不明白 :( 你介意提供一个例子或编辑我的代码吗?
猜你喜欢
  • 2018-05-08
  • 1970-01-01
  • 1970-01-01
  • 2013-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-11
  • 1970-01-01
相关资源
最近更新 更多