批处理技术内幕:重定向与管道

标签: , , , , , ,

曾经写过一篇《批处理技术内幕:重定向与句柄》,说的是一般的重定向。批处理中的管道也属于重定向的一种,但是由于其实现方式跟一般重定向不同,故分开介绍。

简单的说一下什么是管道。管道是一个虚拟的I/O层,程序可以用来向另一个程序传递信息。管道和文件类似,有文件指针和文件描述符,可以被读取,也可以被写入。但是,管道并不代表某一个文件或者设备,它代表的是内存中的一块临时区域,该区域独立于程序自己的内存,并由操作系统来控制的。

@echo off
echo https://demon.tw|set /p url=

CMD中实现管道的核心函数的_pipe函数:

_pipe

_pipe相当于的Linux系统pipe的Windows版,只不过是把API函数CreatePipe的包装了一下。_pipe函数如果调用成功的话,将从第一个参数phandles返回两个文件句柄(handle,文档上是这么写的,其实准确的说应该是文件描述符,本文中两者是同一个意思),其中phandles[0]是读句柄(read handle),phandles[1]是写句柄(write handle)。

_pipe函数成功之后,CMD会调用_dup函数复制一份标准输出STDOUT的拷贝,然后调用_dup2函数将_pipe返回的写句柄phandles[1]复制到STDOUT,并调用_close把写句柄phandles[1]关闭。也就是说,STDOUT句柄不再指向控制台Console,而是指向内存的一块缓冲区。

再然后不管左边的是内部命令还是外部命令,CMD都会调用CreateProcess函数创建一个新的进程:

_left

可以看到,如果是内部命令的话,将会以cmd.exe /S /D /c" command"的形式启动新的CMD进程。还可以看到CreateProcess的bInheritHandles参数是TRUE,也就是说子进程会继承父进程的句柄STDOUT(父进程的STDOUT已经是_pipe返回的写句柄的拷贝,而_pipe创建的句柄是可继承的)。你应该还记得上面已经把STDOUT指向了内存的缓冲区,所以子进程的输出不是到控制台而是到内存中。

创建完进程之后还要做一些收尾工作,调用_dup2将STDOUT从刚才的拷贝中恢复为原来的值,并调用_close关闭拷贝的句柄。

之后发生的事情和上面差不多,只不过STDOUT换成了STDIN。

调用_dup函数复制一份标准输入STDIN的拷贝,然后调用_dup2函数将_pipe返回的读句柄phandles[0]复制到STDIN,并调用_close把读句柄phandles[0]关闭。也就是说,STDIN句柄不再指向控制台Console,而是指向内存的一块缓冲区。

然后不管右边的是内部命令还是外部命令,调用CreateProcess创建一个新进程:

_right

子进程会继承父进程的句柄STDIN,而STDIN指向了内存缓冲区,所以子进程将从内存中读取管道左边进程的STDOUT输出,这样就完成了把管道左边命令的输出重定向到右边命令的输入。

最后用_dup2把STDIN恢复为原来的拷贝,再用_close把拷贝的句柄关闭掉。

管道需要创建两个子进程,这是比较耗时的,而且虽然子进程可以继承父进程的环境变量,却无法改变父进程的环境变量。

@echo off
echo https://demon.tw|set /p url=
set url
pause

虽然在子进程中设置了url环境变量为https://demon.tw,但是只限于子进程中,当子进程退出后就不存在了,父进程仍然没有设置url环境变量。

注:本文的调试环境为Windows XP SP3而非Windows 7。

赞赏

微信赞赏支付宝赞赏

随机文章:

  1. “鲜为人知”的SendWindowMessage
  2. VBS字符串的内部实现
  3. jQuery ajax返回parsererror错误的解决方法
  4. JavaScript 类型的包装对象(Typed Wrappers)
  5. ass2srt.vbs(ass/ssa批量转换srt)

2 条评论 发表在“批处理技术内幕:重定向与管道”上

  1. 微笑的小鱼说道:

    bat系列解析帖子写得很好,深入浅出,讲到点子上了,非常感谢!

  2. amwfjhh说道:

    管道创建子进程,不会对父进程环境变量改变,这个特点倒可以好好利用一下。

留下回复