通过管道写入外部程序(C)
writing to external program through pipe (C)
我想使用外部程序来处理内存中的数据。像外部压缩器、编码器,任何处理我的数据并获得结果的东西。我读了很多关于管道的文章,但它仍然没有用。所以我最终得到了一个简单的程序,它试图像这样通过管道写入外部程序,让它打印到标准输出:
stdout
(w) pipeA (r) $prog +---+
+-----------+ /~~~~~~~~~~~\ |{1}|
|[1] [0]| ----> |{0} {1}| ----> | |
+~~> +-----------+ \~~~~~~~~~~~/ | |
| +---+
|
+-+
write() |
+-+
而且我仍然一无所获。
我的代码是这样的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pipA[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
ssize_t n_written;
if ((pipe(pipA) == -1)) {
perror("pipe failed");
exit(1);
}
if ((pid = fork()) < 0) {
perror("fork failed");
exit(2);
}
/*****************************/
if (pid == 0)
{ /* in child */
dup2(0, pipA[0]); // pipA[read(0)-end]->$prog[write{0}-end]
close(pipA[1]); // $prog won't write to this pipe(A)
// external ``$prog''ram
execlp("wc", "wc", (char *) 0); // out should be: ' 1 2 12'
//execlp("base64", "base64", (char *) 0); // out should be: 'SGVsbG8gcGlwZSEK'
;///if we're here something went wrong
perror("execlp() @child failed");
exit(3);
}
else
{ /* in parent */
//dup2(pipA[1], 0); // STDIN -> pipA // that supposed to connect STDIN->pipA; just in case I needed it
close(pipA[0]); // we won't read it, let $prog write to stdout
//perror("execlp() @parent failed");
//exit(4);
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
close(pipA[1]); // I guess this will close the pipe and send child EOF
// base64: read error: Input/output error
// wc: 'standard input': Input/output error
// 0 0 0
}
return 0;
}
评论显示我在做什么。我不得不承认我没有在管道中得到这些 dup()s,这就是我认为在这里造成问题但不知道的原因。
你能帮忙解决这个看似简单的问题吗?任何帮助表示赞赏。
诊断
你有 dup2()
从后到前的参数。您需要:
dup2(pipA[0], 0);
关闭文件描述符
您没有关闭子项中足够的文件描述符:
经验法则:如果你
dup2()
管道的一端连接到标准输入或标准输出,同时关闭
返回的原始文件描述符
pipe()
尽早。
特别是,您应该在使用任何
exec*()
函数族。
如果您使用以下任一方式复制描述符,则该规则也适用
dup()
要么
fcntl()
F_DUPFD
处方
您的代码中也有一些未使用的定义和未使用的变量。除去您的所有评论(但有一些我的评论来解释正在发生的事情)和适当的修复,我最终得到:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pipA[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
ssize_t n_written;
if ((pipe(pipA) == -1))
{
perror("pipe failed");
exit(1);
}
if ((pid = fork()) < 0)
{
perror("fork failed");
exit(2);
}
if (pid == 0)
{
/* Child: Connect pipA[0] (read) to standard input 0 */
dup2(pipA[0], 0);
close(pipA[1]); /* Close write end of pipe */
close(pipA[0]); /* Close read end of pipe */
execlp("wc", "wc", (char *)0);
perror("execlp() @child failed");
exit(3);
}
else
{
close(pipA[0]); /* Close read end of pipe */
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
if (n_written != (ssize_t)strlen(buf_IN))
{
perror("short write");
exit(4);
}
close(pipA[1]); /* Close write end of pipe — EOF for child */
}
/* Optionally wait for child to die before exiting */
// #include <sys/wait.h> // With other #include lines
// int corpse;
// int status;
// while ((corpse = wait(&status)) > 0)
// printf("Child %d exited with status 0x%.4X\n", corpse, status);
return 0;
}
当 运行 时,生成:
1 2 12
看起来不错。
如果没有 wait()
循环,您可能会在 shell 提示后看到 wc
的输出(因此程序看起来好像是等待您的输入,但实际上,它将是 shell 等待输入);在等待循环中,您将从 shell 提示中正确分离输出。您不必在循环体中打印任何内容,但这样做是令人放心的。
为了不重复,也为了在不熟悉的领域扮演专家我post作为答案。
我完成了任务
(w) pipeA (r) child (w) pipeB (r)
+-----------+ /~~~~~~~~~~~~~\ +-----------+
+~~~>|[1] [0]| ----> |{0} $prog {1}| ----> |[1] [0]| ~~~+
| +-----------+ \~~~~~~~~~~~~~/ +-----------+ |
| \/
+-+ +--+
write() | | read()
+-+ +--+
dup(pA[0],0) dup(pB[1],1)
close(pA[1]) close(pA[0])
close(pA[0]) close(pA[1])
第二个管道可以读取。一切以此类推。如果有什么重大的问题或者我应该知道的事情,请说出来。
(抱歉 python 样式缩进,希望您不要介意)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte);
int main(void)
{
int pipA[2], pipB[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
char buf_OUT[1024];
char *bptr;
ssize_t n_written, n_read = 0, a_read = 0, to_read = sizeof(buf_OUT);
if ((pipe(pipA) == -1) || (pipe(pipB) == -1))
{
perror("pipe failed");
exit(1);
}
if ((pid = fork()) < 0)
{
perror("fork failed");
exit(2);
}
if (pid == 0)
{
/* in child */
dup2(pipA[0], 0); // connect pipe A (read end/exit) to stdin (write end/input)
close(pipA[1]); // close unused pipe A end
close(pipA[0]); // close - " - //
;
dup2(pipB[1], 1); // connect stdout (read end/output) to pipe B (write end/entry)
close(pipB[0]); // close unused pipe B ends
close(pipB[1]); // close - " - //
execlp("lzip", "lzip", "-c", (char *)0);
;
perror("execlp() @child failed");
exit(3);
}
else
{
/* in parent */
close(pipA[0]); // close pipe A read end - will only write to this one
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
if (n_written < 0)
perror("error: read_whole_file(pipA[1], ...) failed miserably\n");
close(pipA[1]); // close write end which subsequently signals EOF to child
;
close(pipB[1]); // close pipe B write end - will only read form this one
a_read = read_whole_file(pipB[0], buf_OUT, sizeof(buf_OUT));
if (a_read < 0)
perror("error: read_whole_file(pipB[0], ...) failed miserably\n");
close(pipB[0]); // close read end after reading
;
write(STDOUT_FILENO, buf_OUT, a_read); // dump it to parent's stdout - equivalent of processing data received from external program/plugin
}
return 0;
}
ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte) // read whole file
{
ssize_t n_read, a_read = 0, to_read = nbyte;
char *bptr = (char*)buf;
size_t BUF_SIZE = 4096;
do
{
if(to_read < BUF_SIZE)
BUF_SIZE = to_read;
n_read = read(fildes, bptr, BUF_SIZE);
if (n_read < 0) // recover from temporarily failed read (got it from git wrapper)
{
if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))
continue;
}
bptr += n_read;
to_read -= n_read;
a_read += n_read;
} while ((n_read>0) && (to_read>0));
;
if (n_read < 0)
a_read = n_read;
return a_read;
}
注意不是很明显 - 我仍然没有在 child 中得到 close(pipA[0]);
和 close(pipB[1]);
。为什么他们不再使用了?
另外 dup2(pipB[1], 1);
,我认为它会是另一种方式,但它没有用,所以由于试用结束错误,我想到了这个。
我想使用外部程序来处理内存中的数据。像外部压缩器、编码器,任何处理我的数据并获得结果的东西。我读了很多关于管道的文章,但它仍然没有用。所以我最终得到了一个简单的程序,它试图像这样通过管道写入外部程序,让它打印到标准输出:
stdout
(w) pipeA (r) $prog +---+
+-----------+ /~~~~~~~~~~~\ |{1}|
|[1] [0]| ----> |{0} {1}| ----> | |
+~~> +-----------+ \~~~~~~~~~~~/ | |
| +---+
|
+-+
write() |
+-+
而且我仍然一无所获。 我的代码是这样的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pipA[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
ssize_t n_written;
if ((pipe(pipA) == -1)) {
perror("pipe failed");
exit(1);
}
if ((pid = fork()) < 0) {
perror("fork failed");
exit(2);
}
/*****************************/
if (pid == 0)
{ /* in child */
dup2(0, pipA[0]); // pipA[read(0)-end]->$prog[write{0}-end]
close(pipA[1]); // $prog won't write to this pipe(A)
// external ``$prog''ram
execlp("wc", "wc", (char *) 0); // out should be: ' 1 2 12'
//execlp("base64", "base64", (char *) 0); // out should be: 'SGVsbG8gcGlwZSEK'
;///if we're here something went wrong
perror("execlp() @child failed");
exit(3);
}
else
{ /* in parent */
//dup2(pipA[1], 0); // STDIN -> pipA // that supposed to connect STDIN->pipA; just in case I needed it
close(pipA[0]); // we won't read it, let $prog write to stdout
//perror("execlp() @parent failed");
//exit(4);
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
close(pipA[1]); // I guess this will close the pipe and send child EOF
// base64: read error: Input/output error
// wc: 'standard input': Input/output error
// 0 0 0
}
return 0;
}
评论显示我在做什么。我不得不承认我没有在管道中得到这些 dup()s,这就是我认为在这里造成问题但不知道的原因。 你能帮忙解决这个看似简单的问题吗?任何帮助表示赞赏。
诊断
你有 dup2()
从后到前的参数。您需要:
dup2(pipA[0], 0);
关闭文件描述符
您没有关闭子项中足够的文件描述符:
经验法则:如果你
dup2()
管道的一端连接到标准输入或标准输出,同时关闭
返回的原始文件描述符
pipe()
尽早。
特别是,您应该在使用任何
exec*()
函数族。
如果您使用以下任一方式复制描述符,则该规则也适用
dup()
要么
fcntl()
F_DUPFD
处方
您的代码中也有一些未使用的定义和未使用的变量。除去您的所有评论(但有一些我的评论来解释正在发生的事情)和适当的修复,我最终得到:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pipA[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
ssize_t n_written;
if ((pipe(pipA) == -1))
{
perror("pipe failed");
exit(1);
}
if ((pid = fork()) < 0)
{
perror("fork failed");
exit(2);
}
if (pid == 0)
{
/* Child: Connect pipA[0] (read) to standard input 0 */
dup2(pipA[0], 0);
close(pipA[1]); /* Close write end of pipe */
close(pipA[0]); /* Close read end of pipe */
execlp("wc", "wc", (char *)0);
perror("execlp() @child failed");
exit(3);
}
else
{
close(pipA[0]); /* Close read end of pipe */
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
if (n_written != (ssize_t)strlen(buf_IN))
{
perror("short write");
exit(4);
}
close(pipA[1]); /* Close write end of pipe — EOF for child */
}
/* Optionally wait for child to die before exiting */
// #include <sys/wait.h> // With other #include lines
// int corpse;
// int status;
// while ((corpse = wait(&status)) > 0)
// printf("Child %d exited with status 0x%.4X\n", corpse, status);
return 0;
}
当 运行 时,生成:
1 2 12
看起来不错。
如果没有 wait()
循环,您可能会在 shell 提示后看到 wc
的输出(因此程序看起来好像是等待您的输入,但实际上,它将是 shell 等待输入);在等待循环中,您将从 shell 提示中正确分离输出。您不必在循环体中打印任何内容,但这样做是令人放心的。
为了不重复,也为了在不熟悉的领域扮演专家我post作为答案。
我完成了任务
(w) pipeA (r) child (w) pipeB (r)
+-----------+ /~~~~~~~~~~~~~\ +-----------+
+~~~>|[1] [0]| ----> |{0} $prog {1}| ----> |[1] [0]| ~~~+
| +-----------+ \~~~~~~~~~~~~~/ +-----------+ |
| \/
+-+ +--+
write() | | read()
+-+ +--+
dup(pA[0],0) dup(pB[1],1)
close(pA[1]) close(pA[0])
close(pA[0]) close(pA[1])
第二个管道可以读取。一切以此类推。如果有什么重大的问题或者我应该知道的事情,请说出来。 (抱歉 python 样式缩进,希望您不要介意)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte);
int main(void)
{
int pipA[2], pipB[2];
int pid;
char buf_IN[32] = "Hello pipe!\n";
char buf_OUT[1024];
char *bptr;
ssize_t n_written, n_read = 0, a_read = 0, to_read = sizeof(buf_OUT);
if ((pipe(pipA) == -1) || (pipe(pipB) == -1))
{
perror("pipe failed");
exit(1);
}
if ((pid = fork()) < 0)
{
perror("fork failed");
exit(2);
}
if (pid == 0)
{
/* in child */
dup2(pipA[0], 0); // connect pipe A (read end/exit) to stdin (write end/input)
close(pipA[1]); // close unused pipe A end
close(pipA[0]); // close - " - //
;
dup2(pipB[1], 1); // connect stdout (read end/output) to pipe B (write end/entry)
close(pipB[0]); // close unused pipe B ends
close(pipB[1]); // close - " - //
execlp("lzip", "lzip", "-c", (char *)0);
;
perror("execlp() @child failed");
exit(3);
}
else
{
/* in parent */
close(pipA[0]); // close pipe A read end - will only write to this one
n_written = write(pipA[1], buf_IN, strlen(buf_IN));
if (n_written < 0)
perror("error: read_whole_file(pipA[1], ...) failed miserably\n");
close(pipA[1]); // close write end which subsequently signals EOF to child
;
close(pipB[1]); // close pipe B write end - will only read form this one
a_read = read_whole_file(pipB[0], buf_OUT, sizeof(buf_OUT));
if (a_read < 0)
perror("error: read_whole_file(pipB[0], ...) failed miserably\n");
close(pipB[0]); // close read end after reading
;
write(STDOUT_FILENO, buf_OUT, a_read); // dump it to parent's stdout - equivalent of processing data received from external program/plugin
}
return 0;
}
ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte) // read whole file
{
ssize_t n_read, a_read = 0, to_read = nbyte;
char *bptr = (char*)buf;
size_t BUF_SIZE = 4096;
do
{
if(to_read < BUF_SIZE)
BUF_SIZE = to_read;
n_read = read(fildes, bptr, BUF_SIZE);
if (n_read < 0) // recover from temporarily failed read (got it from git wrapper)
{
if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))
continue;
}
bptr += n_read;
to_read -= n_read;
a_read += n_read;
} while ((n_read>0) && (to_read>0));
;
if (n_read < 0)
a_read = n_read;
return a_read;
}
注意不是很明显 - 我仍然没有在 child 中得到 close(pipA[0]);
和 close(pipB[1]);
。为什么他们不再使用了?
另外 dup2(pipB[1], 1);
,我认为它会是另一种方式,但它没有用,所以由于试用结束错误,我想到了这个。