问题描述
我有以下代码,用于分叉和执行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_DUPFD
或F_DUPFD_CLOEXEC
。