Linux 管道代码样例(一)

给出几个使用Linux管道的代码样例

环境

  • Ubunt 16.04 64 位
  • Kernel 4.10.0-28-generic
  • gcc v5.5.0
  • go v1.12.4

pipe

使用系统接口 pipe ,接口声明如下

1
2
3
#include <unistd.h>

int pipe(int pipefd[2]);

返回值若为 -1 则表示创建失败,成功创建则返回 0 ,数组 pipefd 第一个元素为读管道的文件句柄,第二个为写管道的文件句柄,多进程情况下图示如下

fork 之后的半双工管道

注意,管道只能在具有公共祖先的两个进程之间使用

example1

代码实例取自 http://man7.org/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "common.h"

int main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
if (argc != 2)
{
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe(pipefd) == -1)
{
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) // child
{
close(pipefd[1]);
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
}
else // parent
{
close(pipefd[0]);
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]);
wait(NULL);
exit(EXIT_SUCCESS);
}
}

编译运行 ./a.out 2233 ,父进程读入用户的输入,通过管道交给子进程输出至标准输出流


example2

代码样例取自 APUE 15.2 节,有删改。父进程复制读取用户的文件路径,读取文件,将内容通过管道传入给子进程,子进程从管道中管道中读取数据,传入给 /bin/more 输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#define __USE_POSIX2
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

void handle_err_todo(char *msg)
{
fprintf(stderr, msg, "");
exit(EXIT_FAILURE);
}

#define DEF_PAGER "/bin/more"
#define MAXLINE 1000

int main(int argc, char *argv[])
{
int8_t n;
int fd[2];
pid_t pid;
char *pager, *argv0;
char line[MAXLINE];
FILE *fp;

if (argc != 2)
handle_err_todo("argc");
if ((fp = fopen(argv[1], "r")) == NULL)
handle_err_todo("fopen");
if (pipe(fd) < 0)
handle_err_todo("pipe");
if ((pid = fork()) < 0)
handle_err_todo("fork");

if (pid > 0) // parent, 负责打开文件,读数据,写入管道
{
close(fd[0]);
while (fgets(line, MAXLINE, fp) != NULL)
{
n = strlen(line);
if (write(fd[1], line, n) != n)
handle_err_todo("parent write");
}
if (ferror(fp))
handle_err_todo("fgets");
close(fd[1]);
if (waitpid(pid, NULL, 0) < 0)
handle_err_todo("waitpid");
exit(EXIT_SUCCESS);
}
else // child 负责从管道中读数据,输出至console
{
close(fd[1]);
if (fd[0] != STDIN_FILENO)
{
if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
handle_err_todo("dpu2");
close(fd[0]);
}
pager = DEF_PAGER;
if ((argv0 = strrchr(pager, '/')) != NULL)
argv0++;
else
argv0 = pager;

if (execl(pager, argv0, (char *)0) < 0)
handle_err_todo("execl");
}
exit(EXIT_SUCCESS);
}

编译运行: ./a.out filename.txt


popen,pclose

popen 简化了 pipe 的操作,接口如下

1
2
3
4
5
#include <stdio.h>

FILE *popen(const char *command, const char *type);

int pclose(FILE *stream);

popen 的第二参数,如果为 r ,则父进程从管道中读取子进程写的数据,如果为 w ,则父进程向管道中写数据提供给子进程读取

以下例子为对之前 example2 代码的改进,代码样例取自 APUE 15.3 节,有删改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define __USE_POSIX2
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

void handle_err_todo(char *msg)
{
fprintf(stderr, msg, "");
exit(EXIT_FAILURE);
}

// #define PAGER "${PAGER:-more}"
#define PAGER "/bin/more"
#define MAXLINE 1000

int main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout;

if (argc != 2)
handle_err_todo("argc");
if ((fpin = fopen(argv[1], "r")) == NULL)
handle_err_todo("fopen");
if ((fpout = popen(PAGER, "w")) == NULL)
handle_err_todo("popen");

while (fgets(line, MAXLINE, fpin) != NULL)
{
if (fputs(line, fpout) == EOF)
handle_err_todo("fputs");
}
if (ferror(fpin))
handle_err_todo("fgets");
if (pclose(fpout) == -1)
handle_err_todo("pclose");

exit(EXIT_SUCCESS);
}

Go语言使用管道

以下代码为go语言中使用管道,由父进程向子进程中写入数据,最终由子进程输出的例子,代码改编自《自己动手写Docker》第3章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
)

func main() {
if os.Args[0] == "/proc/self/exe" { // child process
childProcess()
return
}

var (
cmd *exec.Cmd
err error
readPipe, writePipe *os.File
)
cmd = exec.Command("/proc/self/exe") // 执行自己
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if readPipe, writePipe, err = os.Pipe(); err != nil {
log.Fatalf("os.Pipe() failed: %v", err)
}
cmd.ExtraFiles = []*os.File{readPipe} // 为子进程添加pipe的读句柄
if err = cmd.Start(); err != nil {
log.Fatalf("cmd.Start() failed: %v", err)
}
log.Printf("start child process %d", cmd.Process.Pid)
writePipe.WriteString("hey child process!")
writePipe.Close()

cmd.Wait()
}

func childProcess() {
var (
pipe *os.File
msgByteArr []byte
err error
)
log.Println("Child process start")
pipe = os.NewFile(uintptr(3), "pipe") // 默认三个文件句柄,此为之后添加的第四个
if msgByteArr, err = ioutil.ReadAll(pipe); err != nil {
log.Fatalf("init read pipe error %v", err)
}
fmt.Printf("get data from main process: %s\n", string(msgByteArr))
}