您的位置:首页 > 理论基础 > 计算机网络

从windows到*nix(五) IO重定向与管道

2009-12-28 09:59 141 查看
标准IO

 

stdin,stdout,stderr,分别是标准输入,标准输出,标准错误。windows和*nix下都有的。

默认情况下,标准输入相当于从键盘输入,标准输出和标准错误相当于向屏幕输出。

C语言中,这三个是FILE*类型(文件指针),例如你用printf,那么内容直接输出到标准输出,等价于fprintf(stdout,...);你用scanf,直接从标准输入读取,等价于fscanf(stdin,...)。

 

它们有什么用呢?对windows来说,好像只能用来写实验性的小程序。

 

实际上,*nix中,它们是用户界面的重要组成部分,另外,还可以用来做重定向。

 

 

IO重定向

 

重定向的基本用法,在*nix的shell中如下使用:

~/test $echo Hello
Hello
~/test $echo Hello >a.txt
~/test $cat a.txt
Hello
~/test $
 

这里echo命令用来原样输出,>号把标准输出重定向到文件,随后用cat命令显示那个文件的内容。

再看一个重定向标准输入的例子:

~/test $wc <a.txt
1 1 6
~/test $
 

这里用<号把文件的内容送给wc程序去统计字符数

 

管道

 

前面shell中的的输入输出重定向都是只限于文件,管道则提供一种通用的方式。

 

~/test $cat a.txt | wc
1       1       6
~/test $
 

 

这里管道符|把两个进程的输出和输入连接起来了。同样实现了统计字符数的目的。

 

类似的用法,在windows的cmd中也一样的,用兴趣的同学可以去发掘一下。

但是这两者之间有一个重大的区别在于,*nix下有一种软件模式,叫做过滤器,是windows软件所不具备的。

 

过滤器

 

正是由于过滤器模式的存在,使得*nix可以用大量小工具集合,做到“一行shell胜过万行C”。

 

过滤器是指这样一种程序行为,它顺序读取命令行指定的一个或多个文件,如果没有指定文件名,则从标准输入读取,然后把处理结果写入标准输出,如果这中间有任何错误发生,则把错误写入标准错误中。

 

从上面的描述,我们很容易想象,多个遵循过滤器模式的程序可以用管道符串成一个链式处理。信息的传递通过标准输入和标准输出来传递,而错误信息则送到标准错误中,以免干扰到真正的信息流。

 

 

日常经常用到的通用目的过滤器有下面这些:

 

cat     把输入拷贝到输出

head  截取开始数行

tail     截取结尾数行

cut     截取列

 

grep  执行按行搜索功能

wc     执行计数功能

sort   执行排序

 

一些利用过滤器的实例(这些例子大家可以分段去用,自然就知道每一部分含义):

ps -ef | grep httpd | grep -v grep | wc -l

输出当前系统中httpd进程的数目

 

cut -d' ' -f 1 access.log | sort | uniq -c|sort -nr

从apache日志文件中截取一列,并按照出现次数排序

 

 

即使不是严格遵循过滤器模式,只要程序有着规范的标准输出,仍然可以用做管道的起始位置。并且,它也可以被其他程序轻松的复用。

 

举个例子,windows下要获取文件列表,只好用操作系统提供的API,或者C库函数。

但是*nix下可以直接调用ls命令获取结果

$ls -FLa >~/dir.log
$cat ~/dir.log
./
../
bin/
boot/
dev/
etc/
home/
lib/
lost+found/
mnt/
opt/
proc/
root/
sbin/
sys/
tmp/
tony.tgz
usr/
var/
$
 

作为一个反例,我们看看windows中的dir命令是怎么设计的

 

C:/>dir
驱动器 C 中的卷没有标签。
卷的序列号是 B0B5-18AD
C:/ 的目录
2009-12-21  12:47    <DIR>          360Rec
2004-09-08  10:54                 0 AUTOEXEC.BAT
2004-09-08  10:54                 0 CONFIG.SYS
2009-11-18  09:19    <DIR>          dell
2006-06-22  17:27    <DIR>          Documents and Settings
2009-11-17  14:04    <DIR>          Downloads
2009-11-17  17:44    <DIR>          i386
2009-07-07  14:31             4,128 INFCACHE.1
2009-12-29  17:37    <DIR>          Program Files
2009-07-07  14:37    <DIR>          Temp
2009-12-21  12:31    <DIR>          WINDOWS
3 个文件          4,128 字节
8 个目录  9,779,306,496 可用字节
C:/>
 

这样的结果,要从中提取出一个文件列表来,非常的困难,其难度远大于使用API或者库函数。(dir /w 的结果看起来好一点,但是它的问题更多)

应该说,从设计之初,windows的命令行工具就没想要与其他程序交互。

 

 

 

管道在程序中的应用

 

前面我们看到,输入输出不仅仅可以重定向到文件,它还可以通过管道传递到其他进程中去,借助这个特性,我们可以让它重定向到网络,用C写过CGI的应该可以理解这一点,设想一下,我们不需要socket,仅仅用scanf,printf等等标准IO函数,就可以实现一个网络协议的服务端,而网络连接的实际建立,是通过通用的网络服务程序来建立的。例如tcpd建立通用的基于tcp的网络服务,每当有连接进来,tcpd启动你的程序,它接收到得数据将写入你的标准输入,而你的标准输出将被tcpd发送到客户端。

 

类似的开发思想,我们可以不用修改任何代码,就为你的服务器程序增加SSL的支持。

 

管道在windows中也有实现,但是由于开发氛围,或者其他技术原因,应用的不是很普遍。

 

管道编程上,windows和*nix大体流程一致,
windows:
1.创建管道对
CreatePipe(&hReadPipe,&hWritePipe...)
2.创建新的进程
CreateProcess
这个函数需要的参数STARTUPINFO结构体,有hStdOutput,hStdInput,hStdErr三个成员,根据需要,把前面的hReadPipe或者hWritePipe传入。
如果需要,步骤1可以创建多对管道,反正你要写,就把读的给子进程,反之你要读的,就把写的给子进程。
3.往新进程的标准输入中写数据,或从新进程的标准输出中读数据。
在读或写之前,用CloseHandle把传入子进程的句柄(hReadPipe,hWritePipe之一)关掉。
然后用ReadFile或者WriteFile开始读写。
*nix:
1. 创建管道对
pipe(int pipefd[2])
2. 创建进程
首先fork,然后子进程用dup2把pipefd[0]复制到STDIN_FILENO(就是0),然后关闭pipefd[1],或者把pipefd[1]复制到STDOUT_FILENO(就是1),然后关闭pipefd[0]。
之后调用exec去载入要创建的进程。
3. 读或写
用read或者write。注意读写之前要把子进程使用的那一个用close函数关闭掉。
区别在*nix创建进程分两步,fork然后exec。windows只一步。
区别2,*nix要手工dup来复制文件描述符,windows在创建进程的API内部做好了这件事。
*nix步骤多一些,但是灵活,可以变化出多种模式来,例如fork之后,不仅仅子进程可以exec,父进程也可以exec,完完全全实现像“ls|wc -l”这样的功能。也就是子进程用wc去替代,父进程用ls去替代。
windows步骤少,模式固定,它要实现上述功能,反而更加繁琐,需要创建两个进程,并把它们对接,然后等待两个进程结束。
 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息