使用叉子和管道模仿linux pipe命令 [英] using a fork and pipe to mimic linux pipe command

查看:65
本文介绍了使用叉子和管道模仿linux pipe命令的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的目标是模仿linux pipe命令,例如ls |.种类.而不是简单地排序.但不要输入|用户键入:例如./program ls:sort";==" ls |sort" ,我需要使用fork()和pipe()完成此任务.我有一个MRE设置,一次只允许我运行一个命令,但是我不知道如何使stdout成为第二个命令的stdin所在的位置.每当我尝试在父级中执行 dup()close()和exec()时,似乎出现了问题?我有一个解析用户给定输入的设置,我为参数A获取了argA,其中包含了ls或sort之类的命令,对于参数A参数,则获取了ArgAP,以防用户想要指定-lh或-r等.用于argB.

My goal is to mimic the linux pipe command for example ls | sort. instead of simply sort. but instead of typing | the user types : for example "./program ls : sort" == "ls | sort" I need to complete this task using a fork() and a pipe(). i have a MRE setup that only allows me to run one command at a time but i have no idea as to how to make it to where the stdout is the stdin for the second command. whenever i attempt to dup() close() and exec() in the parent something seems to be going wrong? I have a setup where parse the input given by the user and i acquire argA for argument A which contains the command like ls or sort, and ArgAP for argument A parameter in case the user wants to specify -lh or -r etc. same thing for argB.

我目前已将此程序设置为执行bc命令的硬编码,但可以通过在周围分配一些参数来轻松解决该问题.请帮助我,因为我坚定地坚持了这一点!

I have this program currently setup to be hardcoded to execute the bc command but that can be easily fixed by assigning some parameters around. please help me as i am firmly stuck on this!

 //################  #-for include
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <dirent.h>
 //################

int main(int b, char ** locations) {

    int ok = 0;
    int dots = 0;
    char argA[1000];
    char argB[1000];
    char argAP[1000];
    char argBP[1000];
    while (locations[ok] != NULL) {
        //printf("%s \n", locations[ok]);
        if (strcmp(locations[ok], ":") == 0) {
            dots = 1;
        }
        ok++;
    } //printf("%s %d \n", locations[2], b);

    strcpy(argA, "");
    strcpy(argB, "");
    strcpy(argAP, "");
    strcpy(argBP, "");

    if (dots == 0) {
        int x = 1;
        strcat(argA, locations[x]);
        strcat(argA, " ");
        x++;
        while (locations[x] != NULL) {
            strcat(argAP, locations[x]);
            strcat(argAP, " ");
            x++;
        }
        printf("%s%s \n", argA, argAP);
    }

    if (dots == 1) {
        int x = 1;
        int compare = strcmp(locations[x], ":");

        if (strcmp(locations[1], ":") == 0) {
            printf("one arg\n");
            strcat(argA, locations[x]);
            strcat(argA, " ");
            x++;
            while (locations[x] != NULL) {
                strcat(argAP, locations[x]);
                strcat(argAP, " ");
                x++;
            }
            printf("%s%s \n", argA, argAP);

        } else {
            printf("two args\n");
            strcat(argA, locations[x]);
            strcat(argA, " ");
            compare = strcmp(locations[x], ":");
            x++;
            compare = strcmp(locations[x], ":");
            while (compare != 0) {
                printf("%d \n", x);
                strcat(argAP, locations[x]);
                strcat(argAP, " ");
                compare = strcmp(locations[x], ":");
                x++;
            }
            printf("argA: %s%s \n", argA, argAP);
            x++;
            strcat(argB, locations[x]);
            strcat(argB, " ");
            x++;
            while (locations[x] != NULL) {
                strcat(argBP, locations[x]);
                strcat(argBP, " ");
                x++;
            }
            printf("argB: %s%s \n", argB, argBP);
        }

    }

    // fork/piping

    int i, n;
    int fd[2];
    pipe(fd);
    int rd = fd[0]; // rd points to 0 (read) in pipe
    int wt = fd[1]; // wt points to 1 (write) in pipe

    if (fork()) {

        close(rd);
        write(wt, "2*1*9*1", strlen("2*1*9*1"));

        write(wt, "\n", 1);
        close(wt);
        exit(0);

    } else {

        close(wt);
        close(0); // close zero
        dup(rd); // dup rd into lowest possible orbit
        close(rd);
        execlp("bc", "bc", 0, NULL); // reading from zero means reading from rd!

        exit(1);
    }

    return 0;
}

我已在需要帮助的部分中进行了评论.我如何使用一个命令的管道将结果通过fork传递给第二个命令?我认为这几乎是不可能的,因为通过我的尝试,我只能通过我在此处编写的此设置获得一个命令.

I have commented off the section where i need the help. how do i feed the results through the fork using the pipe of one command into the second? I deem this as nearly impossible as through my attempts i have only been able to get only one command working through this setup that i have written here.

推荐答案

原始

一旦要替换子进程的 stdin ,则需要使用 dup2()函数.

这里是手册部分,解释了为什么 dup()函数永远无法满足您的目的:

Here is the manual section that explains why the dup() function will never work for your purposes:

dup()系统调用创建文件描述符oldfd的副本,对新的文件使用编号最小的未使用文件描述符描述符.

The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.

这是手册部分,解释了 dup2()函数为何可以解决您的问题:

Here is the manual section that explains why the dup2() function can solve your problem:

dup2()系统调用执行与dup()相同的任务,但是使用编号最小的未使用文件描述符的方法,它使用该文件在newfd中指定的描述符编号.

The dup2() system call performs the same task as dup(), but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor number specified in newfd.

要解决您的问题,请将 dup(rd)调用替换为 dup2(rd,STDIN_FILENO).一旦 dup2()函数关闭了 newfd (如果已在使用中),您也可以删除 close(0)调用.

To solve your problem, replace the dup(rd) call for dup2(rd, STDIN_FILENO). You may also remove the close(0) call, once the dup2() function closes the newfd if it is already in use.

如果文件描述符newfd先前已打开,则它处于静默状态在重新使用之前关闭.

If the file descriptor newfd was previously open, it is silently closed before being reused.

编辑#1

一旦 close(0);我以前写的内容不能解决问题.dup(rd); dup2(rd,0)具有相同的效果,就像下面提到的该用户.因此,我按原样编译了您的代码,运行后,我结果如下:

EDIT #1

What I previously wrote does not fix the problem, once close(0); dup(rd); will have the same effect as dup2(rd, 0), as this user mentioned below. So, I compiled your code as it is and, after running, I had this result:

$ gcc -std=c99 -o program program.c
$ ./program ls : sort
two args
argA: ls  
argB: sort  
18
$

如您所见,最后一行显示 18 ,即 2 * 1 * 9 * 1 的结果.现在,请注意父进程在写入描述为 wt 的文件后立即退出-在 bc 命令中执行的新 stdin 子进程.这意味着父进程可能在子进程完成之前退出.我强烈建议您在父进程退出之前使用 wait() waitpid()调用测试代码.例如:

As you can see, the last line shows 18, the result of 2*1*9*1. Now, notice that the parent process exits right after it writes to the file described as wt - the new stdin of bc command being executed in the child process. This means that the parent process may exit before the child process is done. I highly recommend you to test your code using a wait() or waitpid() call right before the parent process exits. For example:

// (...)

if (fork()) {
    close(rd);
    write(wt, "2*1*9*1", strlen("2*1*9*1"));
    write(wt, "\n", 1);
    close(wt);
    wait(NULL);
    exit(0);
} else {
    close(wt);
    close(0); // close zero
    dup(rd); // dup rd into lowest possible orbit
    close(rd);
    execlp("bc", "bc", NULL);
    exit(1);
}

我还将行 execlp("bc","bc",0,NULL); 替换为行 execlp("bc","bc"),NULL); .我删除的零等效于 NULL ,并且表示使用 execlp()执行的命令的参数列表的末尾.

I also replaced the line execlp("bc", "bc", 0, NULL); with the line execlp("bc", "bc", NULL);. The zero I removed is equivalent to NULL and means the end of the argument list for the command being executed with execlp().

阅读完整的代码,我们可以将您的实现分为两个部分: :

Reading the entire code, we can divide your implementation in two parts:

  1. 解析程序的参数以适合 execlp()函数的语法;
  2. 以第一个命令的结果作为输入来分叉执行第二个命令的过程.

如果您阅读 exec()函数族的手册页,您会注意到函数 execvp()在该程序中更有用,因为 execvp()函数的第二个参数与程序的参数类型相同:以 NULL 终止的字符串数组.按照此步骤,您可以轻松地解析程序的参数以适合 execvp():

If you read the man pages of the exec() function family, you will notice that the function execvp() is way more useful in this program, since the second argument of the execvp() function is the same type as the program's arguments: an array of strings NULL-terminated. Following this steps, you can easily parse the program's arguments to fit in the execvp():

  1. 遍历程序的参数;
  2. 找到管道符号的位置;
  3. 在该位置,放置 NULL 表示第一个命令参数的结尾;
  4. 将下一个位置的地址保存为第二个命令的参数的开头.
  1. Iterate through the program's arguments;
  2. Find the position of the pipe symbol;
  3. In that position, put NULL to signalize the end of the first command's arguments;
  4. Save the address of the next position as the start of the second command's arguments.

在解析了程序的参数之后,就该创建一个管道并派生该进程了.在子进程中,在执行第一个命令之前,将 stdout 替换为管道的写端.在父进程中,在执行第二条命令之前,将 stdin 替换为管道的读取端.

After parsing the program's arguments, it is time to create a pipe and fork the process. In the child process, replace the stdout with the write-end of the pipe before executing the first command. In the parent process, replace the stdin with the read-end of the pipe before executing the second command.

这是我编写,运行和测试的全部代码:

Here is the entire code I wrote, ran and tested:

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

#define PIPE_SYMBOL ":"

int main ( int argc , char **argv ) {
    /* Validates the usage. At least is needed the program's name, two commands and the pipe symbol */
    if ( argc < 4 ) {
        fprintf(stderr, "usage: command-1 [args-1...] : command-2 [args-2...]\n");
        return EXIT_FAILURE;
    }

    /* The start of the first comment is allways the start of the program arguments array */
    char **command1 = &argv[1];

    /* The start of the second command is undefined, once it depends where the pipe symbol is located */
    char **command2 = NULL;

    /* Finds the position of the pipe symbol */
    for ( int i = 0 ; argv[i] != NULL ; i++ ) {
        /* When found, ... */ 
        if ( strcmp(PIPE_SYMBOL, argv[i]) == 0 ) {
            /* ... replaces it for NULL, so the first command array is NULL terminated and... */
            argv[i] = NULL;
            /* ... the next position is the start of the second command */
            command2 = &argv[i+1];
            break;
        }
    }

    /* If the pipe symbol is missing or if there is no command after the pipe symbol, bad usage */
    if ( command2 == NULL || command2[0] == NULL ) {
        fprintf(stderr, "usage: command-1 [args-1...] : command-2 [args-2...]\n");
        return EXIT_FAILURE;
    }

    pid_t pid;
    int pipefd[2];

    if ( pipe(pipefd) == -1 ) {
        perror("creating pipe");
        return EXIT_FAILURE;
    }

    if ( (pid = fork()) == -1 ) {
        perror("creating child process");
        return EXIT_FAILURE;
    }
    
    /* Child process executes the first command */
    if ( pid == 0 ) {
        close(pipefd[0]);
        close(STDOUT_FILENO);
        dup(pipefd[1]);
        close(pipefd[1]);
        execvp(command1[0], command1);
        perror("executing first command");
        return EXIT_FAILURE;
    }

    /* Parent process executes the second command */
    close(pipefd[1]);
    close(STDIN_FILENO);
    dup(pipefd[0]);
    close(pipefd[0]);
    execvp(command2[0], command2);
    perror("executing second command");
    return EXIT_FAILURE;
}

这篇关于使用叉子和管道模仿linux pipe命令的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆