管道 (Unix)在类Unix操作系统(以及一些其他借用了这个设计的操作系统,如Windows)中,管道(英語:Pipeline)是一系列将标准输入输出链接起来的进程,其中每一个进程的输出被直接作为下一个进程的输入。 每一个链接都由匿名管道实现[來源請求]。管道中的组成元素也被称作过滤程序。 这个概念是由道格拉斯·麥克羅伊为Unix 命令行发明的,因与物理上的管道相似而得名。 例子简单样例ls -l | less
在这个例子中, 以 复杂样例以下是一個管線的範例,執行由一URL標示的全球資訊網資源的一種拼寫檢查器。之後是關於這個其作用的說明。注意「\」是用來把這六行轉為一個命令列。 curl "https://en.wikipedia.org/wiki/Pipeline_(Unix)" | \
sed 's/[^a-zA-Z ]/ /g' | \
tr 'A-Z ' 'a-z\n' | \
grep '[a-z]' | \
sort -u | \
comm -23 - /usr/share/dict/words | \
less
这个特殊的“|”字符告诉命令行解释器(Shell)将前一个命令的输出通过“管道”导入到接下来的一行命令作为输入。也就是说,curl命令的输出被作为sed命令的输入,后面的命令也是这样。 命令行界面中的管线所有广泛应用于UNIX和Windows中的shell程序都有特殊的语法构建管线。典型语法是使用ASCII中的垂直线“|”(正是由于这个原因,这个符号常被称为管道符)。当出现这样的语法时shell会启动各个进程,并调整各个进程的标准流之间的连接(还包括安排一些缓存)。 错误流通常,管线中的进程的标准错误流("stderr")不会通过管道传输;它们被合并输出到控制台。然而,很多Shell提供一些扩充的语法去改变这一行为。比如在csh Shell和bash中,使用“|&”代替“|”来表示错误流也需要被合并进入标准输出,并传递给下一个进程。Bourne shell也可以合并错误流,通过 2>&1 也可以将错误流重定向到一个不同的文件。 Pipemill在一些常用的简单管线中,shell仅仅只是用管道来连接每个子进程,然后在子进程中执行外部命令。因此shell本身没有通过管线来处理数据。 然而,shell也有可能直接处理管线数据。构建这样的语法像这样: command | while read var1 var2 ...; do
# process each line, using variables as parsed into $var1, $var2, etc
# (note that this is a subshell: var1, var2 etc will not be available
# after the while loop terminates)
done
... 这样的语法叫 "pipemill" 。 在程序中创造管道匿名管道使用C语言在UNIX中使用pipe(2)系统调用时,这个函数会让系统构建一个匿名管道,这样在进程中就打开了两个新的,打开的文件描述符:一个只读端和一个只写端。管道的两端是两个普通的,匿名的文件描述符,这就让其他进程无法连接该管道。
为了避免死锁并利用进程的并行运行的好处,有一个或多个管道的UNIX进程通常会调用
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.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 reads from pipe */
close(pipefd[1]); /* Close unused write end */
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 writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}
具名管道具名管道可以通过调用 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char filename[] = "test_fifo";
if (!mkfifo(filename,S_IRUSR | S_IWUSR| S_IRGRP|S_IWGRP)){
pid_t pid = fork();
if (pid == 0){ //child
int fd = open(filename, O_WRONLY);
if (fd < 0)
perror("child open()");
else{
if (strlen(argv[1]) != write(fd, argv[1], strlen(argv[1])))
perror("child write error");
else
close(fd);
}
}
else if (pid > 0){ //father
int fd = open(filename, O_RDONLY);
if (fd < 0)
perror("father open()");
else{
char buffer[200];
int readed = read(fd, buffer, 199);
close(fd);
buffer[readed] = '\0';
printf("%s\n",buffer);
}
}
else
perror("fork()");
}
else
perror("mkfifo() error:");
}
以上代码在编译后运行时给出一个参数,子进程会将该参数内容写入管道(该管道在当前目录下,文件名为“test_fifo”),父进程从管道中读取内容并显示出来 实现在大多数类UNIX操作系统中,管线上的所有进程同时启动,输入输出流也已经被正确地连接,并且这些进程被调度程序所管理。最为重要的一点就是,所有的UNIX管道和其他管道实现不一样的地方就是缓存的概念:输出进程可能会以每秒5000 byte的速度输出,但是接收进程也许每秒只能接收100 byte,但不会有数据丢失。原因就是管道上游的进程的所有输出都会被放入一个队列中。当下游进程开始接收数据时,操作系统就会将数据从队列传至接收进程,并将传完的数据从队列中移除。当缓存队列空间不足时,上游进程会被终止,直到接收进程读取数据为上游进程腾出空间。在Linux中,缓存队列的大小是65536 byte。 网络管线根据Unix哲学——“一切都是文件”, 歷史管道的概念以及垂直线的记号(|)都是由道格拉斯·麥克羅伊发明的,他是早期命令行外壳的作者。他发现他常常将一个程序的输出作为另一个程序的输入,于是便发明了“管道。它的想法在1973年被实现,Ken Thompson将管道添加到了UNIX操作系统。[1]这个点子最终被移植到了其他的操作系统,比如DOS、OS/2、Microsoft Windows和BeOS,而且常常使用相同的记号(垂直线)。 虽然管道概念是独立发展的,但是 Unix 管道相似于、也确实晚于由Ken Lochner在20世纪60年代为Dartmouth Time Sharing System开发的'communication files'。[2][3] 在苹果Automator(类似管道一样将多个重复的命令链接起来)的那个机器人拿着一根管子的图标也是对于最初Unix管道概念的纪念。 其他作業系統其他作業系統的這個特色源自於Unix,例如 Taos 和 MS-DOS,最終成為軟體工程的管道与过滤器设计模式。 参见
引用
外部链接
|