使用叉子和管道来模仿 linux 管道命令

如何解决使用叉子和管道来模仿 linux 管道命令

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

我目前将此程序设置为硬编码以执行 bc 命令,但可以通过分配一些参数轻松修复。请帮助我,因为我一直坚持这个!

 //################  #-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",argAP);

        } else {
            printf("two args\n");
            strcat(argA," ");
            compare = strcmp(locations[x],":");
            x++;
            compare = strcmp(locations[x],":");
            while (compare != 0) {
                printf("%d \n",x);
                strcat(argAP," ");
                compare = strcmp(locations[x],":");
                x++;
            }
            printf("argA: %s%s \n",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",NULL); // reading from zero means reading from rd!

        exit(1);
    }

    return 0;
}

我已经注释掉了需要帮助的部分。我如何使用一个命令的管道将结果通过 fork 输入第二个命令?我认为这几乎是不可能的,因为通过我的尝试,我只能通过我在此处编写的此设置获得一个命令。

解决方法

您应该始终尝试将问题拆分为可以先解决和验证的较小子问题。

例如,请考虑是否为您提供了以下 run.h

#ifndef   RUN_H
#define   RUN_H
#include <sys/types.h>

/** Create a close-on-exec pipe with descriptors not standard streams
 *
 *  Both read and write end of the pipe will be close-on-exec,*  and neither of them will be 0 (stdin),1 (stdout),or 2 (stderr).
 *
 * @fd      Read ([0]) and write ([1]) ends of the pipe
 * @return  0 if success,-1 with errno set if error
*/
int safe_pipe(int fd[2]);

/* pgid: */
enum {
    NEW_SESSION = -2,NO_CHANGE   = -1,NEW_PGROUP  = 0,};

/* Descriptors: */
enum {
    DEV_NULL = -1,STANDARD_INPUT = 0,STANDARD_OUTPUT = 1,STANDARD_ERROR = 2,};

/** Execute a binary in a child process
 *
 * This function uses a control pipe to check if the specified binary could be executed
 * (started; not completed!),and to provide the errno number if not.
 * It is careful to ensure even oddball descriptor configurations work.
 * It is up to the parent to close any pipe descriptors specified after the call.
 *
 * @pathname    Name or path to the binary to be executed.
 * @args        NULL-terminated array of command-line arguments.
 *              Note that args[0] is the command name itself.
 * @pgid        Process group to use: NEW_SESSION,NO_CHANGE,NEW_PGROUP,*              or a process group ID.  NO_CHANGE runs the child process
 *              in the same session and process group as the current process.
 * @infd        Standard input descriptor.  DEV_NULL,or a pipe descriptor.
 * @outfd       Standard output descriptor. DEV_NULL,1,or a pipe descriptor.
 * @errfd       Standard error descriptor.  DEV_NULL,2,or a pipe descriptor.
 * @return      PID of the child process,or -1 with errno set if an error occurs.
*/
pid_t run(const char *pathname,const char *args[],pid_t pgid,int infd,int outfd,int errfd);

/** Wait for all child processes to finish,and reap them
 *
 * errno is always set when this function returns:
 *      ECHILD  if there are no more child processes
 *      EINTR   if wait was interrupted by signal delivery
 * or any other positive value returned by reaped().
 *
 * @reaped  NULL,or a function called for each reaped child process.
 *          First parameter is the PID of the child process that exited,*          the second parameter is the "packed" exit status code; see man 2 wait.
 *          If reaped() returns a negative value,wait_all_children() will
 *          immediately return with that value,keeping the same errno value.
 *          If reaped() returns a positive value,wait_all_children() will
 *          immediately return with the reaped process count,and that value in errno.
 * @return  Number of processes reaped,or negative if an error occurs.
*/
int wait_all_children(int (*reaped)(pid_t,int));

#endif /* RUN_H */

及其知识共享零许可(随意使用!)实施,run.c

// SPDX-License-Identifier: CC0-1.0
#define  _POSIX_C_SOURCE  200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "run.h"

/* Create a close-on-exec pipe whose read and write ends do not
 * overlap with standard descriptors (stdin,stdout,stderr).
 * Returns 0 if success,-1 with errno set if error.
*/
int  safe_pipe(int fd[2])
{
    unsigned int  close_mask = 0;
    int           err = 0;
    int           pfd[2];

    fd[0] = -1;
    fd[1] = -1;

    if (pipe2(pfd,O_CLOEXEC) == -1) {
        /* errno set by pipe2() */
        return -1;
    }

    do {
        /* Make sure read end does not shadow standard descriptors */
        while (pfd[0] >= 0 && pfd[0] <= 2) {
            close_mask |= 1 << pfd[0];
            pfd[0] = fcntl(pfd[0],F_DUPFD_CLOEXEC,3);
            if (pfd[0] == -1) {
                err = errno;
                break;
            }
        }
        if (err)
            break;

        /* Make sure write end does not shadow standard descriptors */
        while (pfd[1] >= 0 && pfd[1] <= 2) {
            close_mask |= 1 << pfd[1];
            pfd[1] = fcntl(pfd[1],3);
            if (pfd[1] == -1) {
                err = errno;
                break;
            }
        }
        if (err)
            break;

        /* Close any temporarily used descriptors. */
        if (close_mask & (1<<0))
            close(0);
        if (close_mask & (1<<1))
            close(1);
        if (close_mask & (1<<2))
            close(2);

        /* Success! */
        fd[0] = pfd[0];
        fd[1] = pfd[1];
        return 0;

    } while (0);

    /* Failed. Close all related descriptors. */
    if (pfd[0] != -1)
        close(pfd[0]);
    if (pfd[1] != -1)
        close(pfd[1]);
    if (close_mask & (1<<0))
        close(0);
    if (close_mask & (1<<1))
        close(1);
    if (close_mask & (1<<2))
        close(2);

    errno = err;
    return -1;
}


/* Open the null device to a specific descriptor.
*/
static int  dev_null(int to_descriptor)
{
    int  fd;

    if (to_descriptor == -1) {
        errno = EBADF;
        return -1;
    }

    close(to_descriptor);

    fd = open("/dev/null",O_RDWR | O_NOCTTY);
    if (fd == -1)
        return -1; /* errno set by open() */
    if (fd == to_descriptor)
        return  0; /* We got lucky! */

    if (dup2(fd,to_descriptor) == -1) {
        const int  saved_errno = errno;
        close(fd);
        errno = saved_errno;
        return -1;
    }

    close(fd);
    return 0;
}


/* Execute a binary with the specified command-line arguments
 * (args[] array terminated by a NULL pointer),* and redirecting standard input,output,and error to the specified descriptors
 * (which may,and should be,close-on-exec),or -1 to redirect to /dev/null.
 * pgid can be -2 for a new session,-1 for no change,0 for new process group,or >0 for a specific process group.
 * Returns the PID of the new child process,or -1 with errno set if error.
*/
pid_t  run(const char *pathname,int errfd)
{
    if (!pathname || !*pathname || !args || !args[0] || !args[0][0]) {
        /* NULL or empty pathname (path or name) to the executable. */
        errno = EINVAL;
        return -1;
    }

    /* Create the control pipe we use between parent and child to monitor exec() success/failure. */
    int  ctrlfd[2];
    if (safe_pipe(ctrlfd) == -1) {
        /* errno set by safe_pipe(). */
        return -1;
    }

    pid_t  child = fork();
    if (child == -1) {
        /* Cannot fork a child process. */
        const int  saved_errno = errno;
        close(ctrlfd[0]);
        close(ctrlfd[1]);
        errno = saved_errno;
        return -1;

    } else
    if (!child) {
        /* Child process. */

        unsigned int  close_mask = 0;
        int           err = 0;
        do {

            /* Close parent (read) end of the control pipe. */
            close(ctrlfd[0]);

            /* Adjust process group. */
            if (pgid == NEW_SESSION) {
                if (setsid() == -1) {
                    err = errno;
                    break;
                }
            } else
            if (pgid == NEW_PGROUP) {
                if (setpgid(0,0) == -1) {
                    err = errno;
                    break;
                }
            } else
            if (pgid > 0) {
                if (setpgid(0,pgid) == -1) {
                    err = errno;
                    break;
                }
            } else
            if (pgid != NO_CHANGE) {
                err = EINVAL;
                break;
            }

            /* Make sure infd does not shadow standard output or standard error. */
            while (infd == 1 || infd == 2) {
                close_mask |= 1 << infd;
                infd = fcntl(infd,3);
                if (infd == -1) {
                    err = errno;
                    break;
                }
            }
            if (err)
                break;

            /* Make sure outfd does not shadow standard input or standard error. */
            while (outfd == 0 || outfd == 2) {
                close_mask |= 1 << outfd;
                outfd = fcntl(outfd,3);
                if (outfd == -1) {
                    err = errno;
                    break;
                }
            }
            if (err)
                break;

            /* Make sure errfd does not shadow standard input or standard output. */
            while (errfd == 0 || errfd == 1) {
                close_mask |= 1 << errfd;
                errfd = fcntl(errfd,3);
                if (errfd == -1) {
                    err = errno;
                    break;
                }
            }
            if (err)
                break;

            /* Close unneeded descriptors. */
            if ((close_mask & (1<<0)) || infd == -1)
                close(0);
            if ((close_mask & (1<<1)) || outfd == -1)
                close(1);
            if ((close_mask & (1<<2)) || errfd == -1)
                close(2);

            /* Redirect standard input. */
            if (infd == DEV_NULL) {
                if (dev_null(0) == -1) {
                    err = errno;
                    break;
                }
                infd = 0;
            } else
            if (infd != 0) {
                if (dup2(infd,0) == -1) {
                    err = errno;
                    break;
                }
                close(infd);
                infd = 0;
            }

            /* Redirect standard output. */
            if (outfd == DEV_NULL) {
                if (dev_null(1) == -1) {
                    err = errno;
                    break;
                }
                outfd = 1;
            } else
            if (outfd != 1) {
                if (dup2(outfd,1) == -1) {
                    err = errno;
                    break;
                }
                close(outfd);
                outfd = 1;
            }

            /* Redirect standard error. */
            if (errfd == DEV_NULL) {
                if (dev_null(2) == -1) {
                    err = errno;
                    break;
                }
                errfd = 2;
            } else
            if (errfd != 2) {
                if (dup2(errfd,2) == -1) {
                    err = errno;
                    break;
                }
                close(errfd);
                errfd = 2;
            }

            /* Make sure the standard descriptors are not close-on-exec. */
            if (fcntl(0,F_SETFD,0) == -1 ||
                fcntl(1,0) == -1 ||
                fcntl(2,0) == -1) {
                err = errno;
                break;
            }

            /* Close the unneeded temporary descriptors. */
            if (close_mask & (1<<0))
                close(0);
            if (close_mask & (1<<1))
                close(1);
            if (close_mask & (1<<2))
                close(2);
            close_mask = 0;

            /* Execute. */
            if (strchr(pathname,'/'))
                execv(pathname,(char *const *)args);   /* pathname has a slash,so it is a path to a binary */
            else
                execvp(pathname,(char *const *)args);  /* pathname has no slash,so it specifies the binary name only */

            /* Failed. */
            err = errno;
        } while (0);

        /* Send err to parent via the control pipe. */
        {
            const char        *ptr = (const char *)(&err);
            const char *const  end = (const char *)(&err) + sizeof err;
            while (ptr < end) {
                ssize_t  n = write(ctrlfd[1],ptr,(size_t)(end - ptr));
                if (n > 0) {
                    ptr += n;
                } else
                if (n != -1) {
                    /* Should never occur */
                    break;
                } else
                if (errno != EINTR) {
                    /* I/O error writing to the pipe too! */
                    break;
                }
            }
        }

        /* The kernel will close all open descriptors in the process. */
        exit(127);
    }

    /* Parent process. */

    /* Close read end of control pipe,so we detect if the child process exec() succeeded. */
    close(ctrlfd[1]);

    /* Read from the control pipe,to determine if child process exec succeeds or not. */
    {
        int          err = 0;
        char        *ptr = (char *)(&err);
        char *const  end = (char *)(&err) + sizeof err;

        while (ptr < end) {
            ssize_t  n = read(ctrlfd[0],(size_t)(end - ptr));
            if (n > 0) {
                ptr += n;
            } else
            if (n == 0) {
                break;
            } else
            if (n != -1) {
                err = EIO;
                ptr = end;
                break;
            } else
            if (errno != EINTR) {
                err = errno;
                ptr = end;
                break;
            }
        }

        close(ctrlfd[0]);

        /* Treat partially received errors and received zero as EIO. */
        if (ptr > (char *)(&err)) {
            if (ptr != end || !err)
                err = EIO;
        }

        if (err) {
            /* Child failed to exec; reap it. */

            /* Reap child process. Ignore errors,and retry if interrupted. */
            pid_t  p;
            do {
                p = waitpid(child,NULL,0);
            } while (p == -1 && errno == EINTR);

            errno = err;
            return -1;
        }
    }

    /* Success. */
    errno = 0;
    return child;
}


int wait_all_children(int (*reaped)(pid_t,int))
{
    int  count = 0;

    while (1) {
        int    status = 0;
        pid_t  pid;

        pid = wait(&status);
        if (pid == -1) {
            /* errno set by wait() */
            return count;
        } else
        if (pid < 1) {
            /* C library or Linux kernel bug! */
            errno = EIO;
            return count;
        }

        count++;

        if (reaped) {
            int  retval = reaped(pid,status);
            if (retval < 0) {
                /* errno set by reaped() */
                return retval;
            } else
            if (retval > 0) {
                errno = retval;
                return count;
            }
        }
    }
}

Makefile 以从 example.c 构建示例程序:

CC      := gcc
CFLAGS  := -Wall -Wextra -O2
LDFLAGS :=
TARGETS := example

.PHONY: all clean

all: $(TARGETS)

clean:
    rm -f $(TARGETS) *.o

%.o: %.c
    $(CC) $(CFLAGS) -c $^

example: example.o run.o
    $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

请注意,本论坛将Tabs 转换为空格,并且Makefile 缩进必须使用Tabs 而不是空格。幸运的是,通过将初始空格转换为制表符,运行 sed -e 's|^ *|\t|' -i Makefile 修复了上述问题以及大多数其他 Makefile。

上面的想法是为了帮助您开始正确的道路:

  • safe_pipe() 创建一个管道,当您执行任何 exec() 调用时,该管道的两端都会自动关闭。即使您关闭了一些标准描述符(标准输入、输出或错误),它也可以确保管道末端不会干扰标准描述符。

  • wait_all_children() 等待当前进程的所有直接子进程(即该进程已分叉的进程),直到没有更多子进程,等待被信号传递或可选报告功能中断(它作为参数)返回非零值。

  • run() 派生一个子进程,根据需要重定向标准描述符,甚至处理进程组。它使用控制管道检测exec()错误,不等待子进程退出,只等待子进程启动。

    进程组很有用,因为如果每个逻辑任务都在自己的进程组中,您可以使用否定的进程组 ID 向特定组中的每个进程发送信号(如 KILL 或 TERM)。这确保了对于复杂的任务,当整个任务需要被杀死时,无论它已经分叉了多少个进程,您都可以很好地清理它。不过,对于简单的命令,我们不必担心进程组。

在我们做一个简单的示例程序之后,检查上述内容会更好,比如运行ls -laF | tr a-z A-Z | cat,它列出了当前目录中的所有文件,但是将小写的 a 到 z 转换为大写,使用三个子进程:

// SPDX-License-Identifier: CC0-1.0
#define  _POSIX_C_SOURCE  200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "run.h"

int report(pid_t pid,int code)
{
    if (WIFEXITED(code)) {
        if (WEXITSTATUS(code))
            printf("Process %d exited with status %d.\n",(int)pid,WEXITSTATUS(code));
        else
            printf("Process %d exited with success (status 0).\n",(int)pid);
    } else
    if (WIFSIGNALED(code)) {
        printf("Process %d died from signal %d.\n",WTERMSIG(code));
    } else {
        printf("Process %d died from unknown causes.\n",(int)pid);
    }
    return 0;
}

int main(void)
{
    const char *cmd_a[] = { "ls","-laF",NULL };
    const char *cmd_b[] = { "tr","a-z","A-Z",NULL };
    const char *cmd_c[] = { "cat",NULL };
    int    a_to_b[2],b_to_c[2];
    pid_t  a,b,c;

    if (safe_pipe(a_to_b) == -1 || safe_pipe(b_to_c) == -1) {
        fprintf(stderr,"Cannot create pipes: %s.\n",strerror(errno));
        return EXIT_FAILURE;
    }

    c = run(cmd_c[0],cmd_c,b_to_c[0],STANDARD_OUTPUT,STANDARD_ERROR);
    if (c == -1) {
        fprintf(stderr,"%s: %s.\n",cmd_c[0],strerror(errno));
        return EXIT_FAILURE;
    }

    b = run(cmd_b[0],cmd_b,a_to_b[0],b_to_c[1],STANDARD_ERROR);
    if (b == -1) {
        fprintf(stderr,cmd_b[0],strerror(errno));
        kill(c,SIGKILL);
        wait_all_children(NULL);
        return EXIT_FAILURE;
    }

    a = run(cmd_a[0],cmd_a,DEV_NULL,a_to_b[1],STANDARD_ERROR);
    if (a == -1) {
        fprintf(stderr,cmd_a[0],strerror(errno));
        kill(b,SIGKILL);
        kill(c,SIGKILL);
        wait_all_children(NULL);
        return EXIT_FAILURE;
    }

    close(a_to_b[0]);
    close(a_to_b[1]);
    close(b_to_c[0]);
    close(b_to_c[1]);

    wait_all_children(report);

    return EXIT_SUCCESS;
}

report() 函数只是报告哪个子进程退出以及如何退出。您可以省略它,使用 wait_all_children(NULL); 而不是 wait_all_children(report);,但报告函数对于调试和检查您可以在不同情况下观察到的退出状态非常有用。

我们必须使用管道:a_to_bb_to_c。该对的第一个描述符(a_to_b[0]b_to_c[0])始终是读端,第二个(a_to_b[1]b_to_c[1])是写端。我们的三个子进程是abc,所以我们希望a的标准输出是a_to_b[1]b' s 标准输入为 a_to_b[0]b 的标准输出为 b_to_c[1]c 的标准输入为 b_to_c[1]

我们需要先创建管道。

然后,我们需要运行(fork 和 exec)子进程。因为子进程会阻塞直到他们得到输入(或者他们看到输入结束),所以我们在管道中从最后到第一个创建子进程:首先是“消费者”,然后是“生产者”。这样,如果我们在整个链都没有问题之前失败,生产者应该仍在等待输入。

如果我们无法创建一些子进程,我们确实需要杀死已经启动的进程,最好等待它们退出(通常称为,收割它们)。

当子进程启动后,父进程需要关闭它的管道描述符副本,这样当“生产者”(管道列表中的第一个进程)退出并关闭管道的写端时,读取结束报告输入结束。

如果父进程不关闭管道(写结束)描述符的副本,“消费者”(从管道读取的子进程)将永远不会检测到输入结束——因为理论上,parent 进程仍然可以写入管道! – 一切都会像“挂起”一样。

此时,管道中的进程开始工作。此时父进程可以做其他事情。 (一个有趣的场景是当人们希望使用过滤器:一系列进程,通常是一个脚本,将数据从一种格式转换为另一种格式。过滤器输出管道的读取端很有可能(或管道链)和输入管道的写端要父级可以访问,这样父级在写端写入要转换的数据,从读端读取转换后的数据。有点棘手,因为我们不能假设我们可以在读取任何东西之前写出所有东西,所以通常使用带有 select()poll()非阻塞 I/O。没有什么困难 本身,只需要正确完成即可。)

由于父进程除了等待管道进程完成它们的工作外,无事可做,因此父进程只是等待它们退出。

因为有Makefile,所以只需要运行make all && ./example即可编译运行示例程序。

因为整个问题被拆分为子问题(以对标准流造成最少问题的方式创建管道,并且不会意外泄漏到错误的子进程;并且分叉并启动子进程重定向其标准流,以稳健可靠的方式),示例程序简短且在概念层面易于理解。

在玩过那个例子之后,是时候探索和解释 run.c 如何实现这些功能,以及为什么做出它做出的选择,但我已经被否决了因没有回答上述问题而给负分,所以我将就此打住。尽管如此,我仍然相信以这种方式一步一步地解决问题,在经过测试和理解的部分之上构建解决方案,而不是试图“修复”OP的当前代码,是正确的“答案”。 (再说一次,这正是我只以访客身份发帖,从不注册的原因。)

,

原件

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

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

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

以下手册部分解释了为什么 dup2() 函数可以解决您的问题:

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

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

如果文件描述符 newfd 之前打开过,它会被静默 重用前关闭。

编辑 #1

我之前写的并没有解决问题,一旦 close(0); dup(rd); 将与 dup2(rd,0) 具有相同的效果,如下面提到的 this user。所以,我按原样编译了你的代码,运行后,我 得到了这个结果:

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

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

// (...)

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",NULL); 行替换了 execlp("bc",NULL); 行。我删除的零相当于 NULL,表示使用 execlp() 执行的命令的参数列表的末尾。

编辑 #2(实现)

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

  1. 解析程序的参数以适应 execlp() 函数的语法;
  2. 分叉进程以执行第二个命令,并将第一个命令的结果作为输入。

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

  1. 遍历程序的参数;
  2. 找到管道符号的位置;
  3. 在那个位置,放 NULL 来表示第一个命令的参数结束;
  4. 将下一个位置的地址保存为第二个命令参数的开始。

解析完程序的参数后,是时候创建管道并 fork 进程了。在子进程中,在执行第一个命令之前,将 stdout 替换为管道的写端。在父进程中,在执行第二个命令之前,将 stdin 替换为管道的读端。

这是我编写、运行和测试的完整代码:

#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;
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res