用C语言实现一个shell

3
我正在尝试用C语言实现一个shell。
我已经完成的部分:我有一个指向用户输入的所有命令的指针数组,例如:
args[0]: sort
args[1]: <
args[2]: txtFile
args[3]: |
args[4]: grep
args[5]: key

然后,我每次找到“<”、“>”、“>>”或“|”时,就设置标志。然后,根据它们的使用方式,使用这些标志来重定向、间接引用、追加或管道这些命令。我的问题是,在处理类似以下命令时:
SHELL$: sort < txtFile | grep key

我应该每次发现 '<', '>', '>>' 或 '|' 就创建多个进程吗? 还是在处理完所有命令后再创建这些进程? 有人能指点我一下吗?
这是我目前的代码:
else if(pid > 0) 
{ 
    /* parent process */
    if(!async) 
        waitpid(pid, NULL, 0);
    else 
        printf("this is an async call\n");

    if(PIPE_FLAG)
    {
        pid2 = fork();
        if( pid2 == 0)
        {
            waitpid(pid, NULL, 0);   
            close(c2p[1]);
            if(dup2(c2p[0], STDIN_FILENO) == -1){
                perror("dup2() failed");
                exit(-1);
            }
            execvp(nxt_args[0], nxt_args);
            perror("exec failed 2. ");          
            exit(-1);
        }
        else if( pid2 > 0)
        {
            close(c2p[0]);
            close(c2p[1]);
        }
        else
        {
            /* error occurred */
            perror("fork failed");
            exit(1);
        }
    }
}

我的第一个孩子处理了sort < txtFile,然后将输出发送到一个管道。
3个回答

1
让我们以你的例子为例:
sort < txtFile | grep key

您需要创建一个进程来执行sort,将其标准输入更改为读取txtFile,将其标准输出更改为管道的写入部分,然后执行程序sort

您还需要创建第二个进程来执行grep,该进程将使用管道的读取部分并执行程序grep

因此,您需要2个进程。但是,为了设置第一个进程的stdout,您需要解析管道。

编辑:

首先,您需要创建管道。您必须第一次fork以创建第一个进程,并为第一个进程正确设置stdin和stdout(重定向和管道的写入部分)。然后,您再次fork父进程以创建第二个进程,并将stdin更改为管道的读取部分。在设置标准I/O之后,您在子进程中执行sortgrep


感谢您的回答@Maxime,那么我应该在哪里创建第二个进程?我应该在正在运行的子进程中创建它,还是在我的第一个进程的父进程中创建它? 即: else if(pid > 0) 在此处创建新进程 - user2913269
@user2913269,这两个进程都是您Shell的子进程。第二个进程不是第一个进程的子进程。在Bash中尝试这个命令:cat | less,然后使用ps -ef自行查看。 - Maxime Chéramy
换句话说,第二个进程应该是父进程的一个子进程?@maxime - user2913269
我能够两次分叉相同的PID吗?很抱歉,但我似乎无法想象你试图告诉我的内容。你能提供一些伪代码让我看看你的意思吗? - user2913269
是的。首先,您需要创建管道。您必须首先进行一次分叉以创建第一个进程,并为第一个进程正确设置stdinstdout(重定向和管道的写入部分)。然后,您再次将父进程分叉以创建第二个进程,并将stdin更改为管道的读取部分。在子进程中执行sortgrep之前,通过设置标准I/O来执行exec。 - Maxime Chéramy
显示剩余2条评论

1
在将命令行分解成离散的命令和/或重定向之后,再不要启动任何进程。否则,如果命令行格式错误,就会出现问题。例如,假设您有一个名为“destroy_files”的文件销毁程序,它可以删除文件并可选地显示已删除的文件,还有一个名为“fix_files”的修复程序。用户将这些命令串联在一起,例如“destroy_files -v | fix_files < >”。Shell 应该会说“嘿,这里语法错误”,而不是运行任何东西...但是如果你在遇到分隔符时立即运行命令,那么对于用户来说就太糟糕了。您已经启动了销毁其文件的进程。至于重定向,您无需单独设置处理重定向的进程。只需适当设置 FD 0、1 和/或 2,操作系统将处理字节调整(根据需要进行阻止或缓冲)。您所需要做的就是等待进程结束。

好的,所以在处理完整个命令参数后,我应该肯定创建任何需要的进程。 - user2913269
我所做的是,我的child1在管道之前执行命令的第一部分,然后父进程创建另一个进程,而我的child2在管道之后运行剩余的命令。 现在我注意到它能够工作,但在运行完命令后会停在那里,我按下回车键后它又能继续工作... 你有任何想法为什么会发生这种情况吗? - user2913269
@user2913269:这里有些猜测,因为你没有提供任何代码……但听起来好像其中一个进程成功获取了真正的stdin。这暗示着可能你的操作顺序不正确。我建议你检查一下fork、重定向、exec、输入转发、关闭流/管道、等待和/或清理的相对顺序。 - cHao
我添加了我的代码的一部分...当我创建另一个进程时,程序就会卡在那里...然后如果我使用另一个命令,它会运行它,然后再次显示提示符。 - user2913269

0
创建进程时,必须相应地设置文件描述符。启动大量进程却发现命令行出错是没有意义的,因为分叉进程是昂贵的。
因此,首先解析参数,然后您将知道需要多少个进程以及如何设置每个进程的文件描述符,然后才能运行它们。
因此,在您的示例中,您将需要两个进程,一个管道和一个重定向。

好的,那么我应该先在创建新进程时运行sort,然后将输出dup2到一个管道中,在父进程中创建另一个进程,在该进程中运行grep命令,对吗? - user2913269
首先解析所有内容,如果没有问题则创建管道,然后创建进程并将它们与管道连接起来。操作系统应该处理其余部分,因为当进程想要从管道中读取或写入数据时,如果下一个进程没有准备好,则会被阻塞。不确定,但是按相反的顺序创建进程可能是一个好主意,因为每个连续的进程都将从前面的进程读取(因此会被阻塞)。 - Devolus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接