【问题标题】:C Fork Usage With User InputC Fork 使用用户输入
【发布时间】:2018-04-14 19:08:57
【问题描述】:

我正在尝试编写一个 shell 脚本 C语言。简而言之,如果用户输入一个命令,我将其发送到 system() 调用,如果用户输入多个命令,例如“ls;whoami”,我会对其进行解析并创建子进程来执行所有这些命令。现在它可以工作了,但是我的方法(例如gets()和获取用户输入)似乎不太好,当我输入多个命令时,提示文本变得不可见。您有什么建议,或者如果您发现任何错误或错误用法,因为我不是 C 人,那么我将不胜感激。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define RESET   "\033[0m"
#define BOLDGREEN   "\033[1m\033[32m"      /* Bold Green */
#define BOLDMAGENTA "\033[1m\033[35m"      /* Bold Magenta */

char input[50];
char command[50];
char *inputget;
char *dotcomma;
char *p;
pid_t pid;

void welcome(){
   char* welcomestr = "\n\TEST\n\n";
   printf("%s%s%s",BOLDMAGENTA,welcomestr,RESET);
}

void prompt(){
   char *username = getenv("USER");
   char hostname[1024];
   gethostname(hostname, 1024);
   char currentDirectory[256];
   getcwd(currentDirectory, 256);
printf("%s%s@%s%s%s ~%s $ %s",BOLDGREEN,username,hostname,RESET,BOLDBLUE,currentDirectory,RESET);
}

int main(int argc, char* argv[]){

    inputget=input;

    welcome();
    prompt();

    gets(inputget);

    if(argc == 1) {

    while(strcmp(inputget, "quit")!=0){
            p = strsep(&inputget, ";\n");

        while(p != NULL){
            pid_t parent = getpid();
            pid = fork();

            if (pid==-1){
                perror("failed to fork");
            }
            else if (pid==0){
                system(p);                                    
                exit(0);       
            }else{  
                p = strsep(&inputget, ";\n");
            }
        }
                    wait(NULL);     

        prompt();
        scanf("%s",input);

        inputget=input;
    }

    exit(0);
    }else{
        //get argc 2 and and read-run commands from text.file
    }
}

【问题讨论】:

  • 永远不要在 2018 年再次使用 gets,这是一个危险的弃用功能,请改用 fgets
  • 提示仍然看不到,因为prompt 中的printf 行不以\n 结尾,它会刷新标准输出。在prompt 中的printf 之后添加fflush(stdout);
  • 您必须在else { p = strsep(&amp;inputget, ";\n"); } 块中执行wait(NULL)。做else { wait(NULL); p = strsep(&amp;inputget, ";\n"); }
  • @Pablo 是的,就是这样。能否顺便说明一下故障,非常感谢。编辑:当我这样做时,进程不会同时运行,它按顺序运行
  • 正如承诺的那样,我已经写了一个答案来更详细地解释这个问题。

标签: c shell input process fork


【解决方案1】:

让我们从最糟糕的部分开始:使用gets。不要再使用这个功能了 它很危险且已被弃用。你应该改用fgets

fgets(input, sizeof input, stdin);

这些变量中的任何一个都没有理由

char input[50];
char command[50];
char *inputget;
char *dotcomma;
char *p;
pid_t pid;

必须是全局变量,在main中声明。

stdout 这样的流缓冲区被缓冲,内容被物理写入 一旦缓冲区已满或您调用fflush 刷新缓冲区,则设备。 一个例外是stdout 连接到终端时,在这种情况下printf 打印换行符时将立即刷新。这就是为什么你几乎总是 看到printf语句的格式以\n结尾,比如

printf("Your age is %d\n", age);

当您不想打印换行符时,因为您正在打印类似 提示,那么你应该自己刷新stdout

void prompt(){
    char *username = getenv("USER");
    char hostname[1024];
    gethostname(hostname, 1024);
    char currentDirectory[256];
    getcwd(currentDirectory, 256);
    printf("%s%s@%s%s%s ~%s $ %s",BOLDGREEN,username,hostname,RESET,BOLDBLUE,currentDirectory,RESET);
    fflush(stdout);  // <-- you need this here
}

最后一件事是你在哪里执行wait。问题是 子进程和父进程之间的同步。一旦一个孩子 创建后,它立即开始运行。如果孩子也打印到stdout 并且您不与父同步,则无法保证哪个 输出将首先打印。

在您的情况下,如果用户输入cmd1;cmd2;cmd3,您将分叉 3 次 但是在你分叉所有孩子之后,你只会做 一个 wait。这意味着 三个孩子将同时运行,他们的输出顺序是 不明确的。在所有孩子都分叉之后,你终于做到了wait(NULL),但是这个 只等待一个孩子,然后你执行prompt() 但记住,另一个 孩子们可能仍在运行,因此提示的输出可能会出现 在其他正在运行的孩子的输出之前。这也许是什么 你一直在观察。

如果你想模拟shell,那么cmd2只能在cmd1之后启动 完成和 cmd3 仅在 cmd2 完成之后。只有当三个命令 完成后可以执行prompt()。这意味着你必须等待 在下一个孩子可以分叉之前,每个孩子都结束了。这就是为什么你必须 在调用下一个fork 之前移动父块中的wait

// "quit\n" because fgets does not remove the newline
while(strcmp(inputget, "quit\n") != 0) {
    p = strsep(&inputget, ";\n");

    while(p != NULL) {
        if(p[0] == 0)
        {
            // handles "empty fields", when
            // two delimiters come one after
            // the other 
            p = strsep(&inputget, ";\n");
            continue;
        }

        pid = fork();

        if (pid==-1) {
            perror("failed to fork");
        }
        else if (pid==0) {
            system(p);
            exit(0);
        } else {
            wait(NULL);  // <-- here the parent waits
                         //     until the child is finished
            p = strsep(&inputget, ";\n");
        }
    }

    prompt();
    fgets(input, sizeof input, stdin);

    inputget = input;
}

另外请注意,我在这里没有使用scanfscanf("%s"... 读取到第一个 非白色字符,所以像cat /etc/fstab 这样的命令只会读取cat 和 你的 shell 只会执行 cat 并且它会阻塞直到你关闭 stdin (通过按 Ctrl+D)。下一次,scanf 等不及了 用于用户输入,将改为读取/etc/fstab 并尝试执行 /etc/fstab,这将失败,因为 /etc/fstab 不是脚本或二进制文件。 这就是为什么最好使用fgets

我还会为用户输入使用更长的缓冲区,具体取决于命令 长度,49 字节太短了。我会使用 1024 或更多。或者你可以使用getline 获取整行而不用担心缓冲区大小。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    • 2020-05-08
    • 1970-01-01
    • 2021-07-23
    • 2022-01-21
    • 2014-06-26
    相关资源
    最近更新 更多