- 帖子
- 766
- 积分
- 1451
- 技术
- 117
- 捐助
- 0
- 注册时间
- 2010-5-12
|
本帖最后由 Demon 于 2012-8-26 17:26 编辑
标题: 批处理技术内幕:重定向与句柄
作者: Demon
链接: http://demon.tw/reverse/cmd-internal-redirect.html
版权: 本博客的所有文章,都遵守“署名-非商业性使用-相同方式共享 2.5 中国大陆”协议条款。
虽然很多人在批处理中经常用到重定向操作符和句柄,但是能理解重定向与句柄高级用法的寥寥无几,知道重定向与句柄内部实现的更是凤毛麟角。
按照惯例,先从简单的说起:
>blog.txt echo http://demon.tw
重定向输出操作符>的默认句柄为1,即标准输出(STDOUT),该代码将句柄1的输出重定向到blog.txt文件。
严格的说,句柄这个说法并不准确,正确的称呼应该是文件描述符(file descriptor),但是鉴于帮助文档ntcmds.chm里面用的也是句柄(handle)这个概念,并且一个文件描述符对应于一个文件句柄,所以本文沿用句柄这个说法。
在进行重定向时,CMD会先调用_get_osfhandle函数获取文件描述符对应的文件句柄。
如果_get_osfhandle函数的返回值不是-1,即该文件描述符存在对应的文件句柄,则调用_dup函数为该文件描述符所代表的文件创建一个新的文件描述符。
_dup函数返回的是下一个可以使用的文件描述符,由于标准输入(STDIN)、标准输出(STDOUT)、标准错误(STDERR)已经使用了0、1、2文件描述符,所以不出意外的话,_dup函数应该返回3,这时3和1都代表了标准输出。这就是那些批处理教程中的所谓的“句柄备份”。
“备份”完句柄之后,CMD会调用_close函数将原来的文件句柄(描述符)关闭。
关闭之后调用CreateFile函数打开重定向操作符后的文件,这里是blog.txt,因为是重定向输出,所以以只读模式(GENERIC_WRITE)打开。
成功打开文件之后,又调用_open_osfhandle函数将CreateFile函数返回的文件句柄转成文件描述符。
因为刚才已经把1给关闭了,所以不出意外的话,_open_osfhanle返回的应该也是1,即句柄(描述符)1指向了blog.txt文件,而不是原来的STDOUT。
到这里为止重定向已经设置好了,剩下的工作就交给echo来处理。echo命令会先判断句柄1是否指向控制台(Console),这里显然不是,所以echo命令调用_os_getfhandle函数来获取句柄1所对应的文件句柄,然后调用WriteFile函数来把http://demon.tw写入blog.txt文件。(这部分不是本文的重点,就不截图了,详见《批处理技术内幕:Unicode》。
命令运行完之后,就要恢复重定向的设置,刚才已经把句柄1“备份”到了句柄3,所以CMD调用_dup2函数将句柄1指向句柄3。这就是批处理教程中所谓的“句柄取回”。
从句柄3中取回之后,句柄3就没用了,于是调用_close函数关闭之。
上面的过程用C代码简单的模拟一下,大概是这样(非常简略的代码,CMD的实现要复杂得多):
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <windows.h>
int main()
{
int fd;
HANDLE hFile;
char buf[] = "http://demon.tw";
DWORD cb;
/* 重定向句柄1到blog.txt */
fd = _dup(1);
_close(1);
hFile = CreateFileW(L"blog.txt", GENERIC_WRITE, FILE_SHARE_READ,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
_open_osfhandle((long)hFile, _O_WRONLY);
/* echo命令 */
WriteFile((HANDLE)_get_osfhandle(1), buf, sizeof(buf) - 1, &cb, NULL);
/* 重置重定向 */
_dup2(fd, 1);
_close(fd);
return 0;
}
重定向输入<与追加模式的重定向输出>>跟上面差不多,只不过重定向输入>的话文件是以只读模式(GENERIC_READ)打开的,而>>在打开文件后还要调用SetFilePointer将文件指针指向文件的末尾。
再简单说说>&和<&,帮助文档里的描述让人摸不着头脑:<&从后一个句柄读取输入并写入到前一个句柄输出中;>&将前一个句柄的输出写成后一个句柄的输入。其实除了默认的句柄不同之外,>&和<&是完全一样的,也就是说2>&1和2<&1的效果相同,都是复制句柄1到句柄2,或者说句柄2指向句柄1所指向的文件。
这样说还是有些拗口,让我们看看CMD内部是如何实现的吧。
cd _ 1>error.txt 2>&1
1>error.txt和上面的例子一样,都不多说了,只讲2>&1的部分,先_dup(2)得到一个新的文件描述符4(因为重定向1>error.txt时占用了3),然后_close(2),到这里为止跟上面也是一样的。
在CMD内部,>&或者<&并不会被当成新的重定向操作符,2>&1中的重定向操作符仍然是>,&1会被认为的重定向到的文件,只不过当文件的第一个字符为&的时候CMD会特别处理罢了。这时CMD会调用dup2函数,第一个参数为>&(或者<&)后面的数字,第二个参数为>&(或者<&)前面的数字(如果没有则使用操作符默认的句柄,>为1,<为0)。
也就是句柄2现在指向了句柄1的文件,或者说句柄2复制了句柄1,怎么好理解就怎么理解吧,反正句柄2和句柄1现在是同一个东西。
运用重定向与句柄的高级技巧,可以实现一些有用的功能,比如说防止批处理重复运行:
@echo off 2>con 3>&2 4>>%0
echo single instance batch
echo http://demon.tw
pause |
-
1
评分人数
-
|