管道,execvpe和Shell脚本之间的交互 pipe17.c test file.txt 样本输出

问题描述

我有以下代码,用于分叉和执行execvpe的shell脚本,并将其STDERR和STDOUT重定向到父进程。

#define _GNU_SOURCE

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

#define BUFLEN 65536

int main () {
  int pipes[2];
  pipe(pipes);
  pid_t pid = fork();
  if (pid == -1) {return 1;}
  else if (pid > 0) {
    close(pipes[1]);
    char buf[BUFLEN];
    size_t n = read(pipes[0],buf,sizeof(buf));
    int stat = 0;
    waitpid(pid,&stat,0);
    printf("%.*s",n,buf);
  } else {
    dup2(pipes[1],STDOUT_FILENO);
    dup2(pipes[1],STDERR_FILENO);
    close(pipes[0]);
    char *const argv[] = {"sh","test",NULL};
    execvpe(argv[0],argv,environ);
  }

  return 0;
}

作为一个最小的工作示例,"test"是:

#!/bin/bash
cat file.txt
echo "Hello!"
echo "Goodbye!"

C程序的输出file.txt内容,然后回波的输出将丢失。如果是三个echo语句,那么所有这些语句都会被看到。

我最好的猜测是echo是内置的shell,该shell将为cat派生,我的管道将丢失。在我的项目中,脚本中调用的第一个命令似乎丢失了,其余的都丢失了。

如果我的假设是正确的,我如何收集execvpe产生的任何子代的所有输出

解决方法

我认为问题仅是计时的结合,而不是在停止从管道读取之前不检查EOF。如果将read()调用包装到循环中以读取所有内容,则将读取所有数据。 cat完成后,read()返回可用的所有内容。 echo命令的输出随后会添加到管道中,但根本不会读取。

我相信这段代码演示了。请注意,execvpe()不是POSIX标准(并且不适用于macOS),因此我使用了自己的代理标头#include "execvpe.h"和实现execvpe.c来获得它的实现。另外,POSIX没有定义声明environ的标头,因此我也声明了它。您可能正在使用Linux,并且那里的系统标头修复了POSIX留下的一些漏洞。

这里是工作代码和数据。

pipe17.c

/* SO 6412-3757 */
#define _GNU_SOURCE

#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>

#define BUFLEN 65536

#include "execvpe.h"    /* execvpe() is not in POSIX */
extern char **environ;  /* No POSIX header declares environ */

int main(void)
{
    int pipes[2];
    pipe(pipes);
    pid_t pid = fork();
    if (pid == -1)
    {
        return 1;
    }
    else if (pid > 0)
    {
        close(pipes[1]);
        char buffer[BUFLEN];
        char *b_str = buffer;
        size_t b_len = sizeof(buffer);
        size_t t_len = 0;
        ssize_t n_len;
        while (b_len > 0 && (n_len = read(pipes[0],b_str,b_len)) > 0)
        {
            b_str += n_len;
            b_len -= n_len;
            t_len += n_len;
        }
        close(pipes[0]);
        int status = 0;
        int corpse = waitpid(pid,&status,0);
        printf("%d 0x%.4X: [[%.*s]]\n",corpse,status,(int)t_len,buffer);
    }
    else
    {
        dup2(pipes[1],STDOUT_FILENO);
        dup2(pipes[1],STDERR_FILENO);
        close(pipes[0]);
        close(pipes[1]);
        char *argv[] = {"sh","test",NULL};
        execvpe(argv[0],argv,environ);
        fprintf(stderr,"failed to execute '%s'\n",argv[0]);
        exit(1);
    }

    return 0;
}

test

#!/bin/bash
cat file.txt
echo "Hello!"
echo "Goodbye!"
echo "Errors go to standard error" >&2

file.txt

Line 1 of file1.txt
The very last line of file1.txt

样本输出

14171 0x0000: [[Line 1 of file1.txt
The very last line of file1.txt
Hello!
Goodbye!
Errors go to standard error
]]

请注意,代码在调用execvpe()之前关闭了管道的两端。在这里并不重要,但这样做通常很重要。您的原始代码将size_t的值n传递给printf(),供*使用,格式为。您可能会在sizeof(int) == sizeof(size_t)的64位计算机上解决该问题,但是在sizeof(int) < sizeof(size_t)的64位计算机上会产生编译警告。


经验法则:如果您 dup2() 管道的一端到标准输入或标准输出,同时关闭两个 由返回的原始文件描述符 pipe() 尽早。 特别是,在使用任何 exec*() 功能族。

如果您将描述符与任何一个重复,则该规则也适用 dup() 要么 fcntl()F_DUPFDF_DUPFD_CLOEXEC