批处理技术内幕:重定向与句柄

标签: , , , , , ,

虽然很多人在批处理中经常用到重定向操作符和句柄,但是能理解重定向与句柄高级用法的寥寥无几,知道重定向与句柄内部实现的更是凤毛麟角。

按照惯例,先从简单的说起:

>blog.txt echo http://demon.tw

重定向输出操作符>的默认句柄为1,即标准输出(STDOUT),该代码将句柄1的输出重定向到blog.txt文件。

严格的说,句柄这个说法并不准确,正确的称呼应该是文件描述符(file descriptor),但是鉴于帮助文档ntcmds.chm里面用的也是句柄(handle)这个概念,并且一个文件描述符对应于一个文件句柄,所以本文沿用句柄这个说法。

在进行重定向时,CMD会先调用_get_osfhandle函数获取文件描述符对应的文件句柄。

_get_osfhandle

如果_get_osfhandle函数的返回值不是-1,即该文件描述符存在对应的文件句柄,则调用_dup函数为该文件描述符所代表的文件创建一个新的文件描述符。

_dup

_dup函数返回的是下一个可以使用的文件描述符,由于标准输入(STDIN)、标准输出(STDOUT)、标准错误(STDERR)已经使用了0、1、2文件描述符,所以不出意外的话,_dup函数应该返回3,这时3和1都代表了标准输出。这就是那些批处理教程中的所谓的“句柄备份”。

“备份”完句柄之后,CMD会调用_close函数将原来的文件句柄(描述符)关闭。

_close

关闭之后调用CreateFile函数打开重定向操作符后的文件,这里是blog.txt,因为是重定向输出,所以以只读模式(GENERIC_WRITE)打开。

CreateFile

成功打开文件之后,又调用_open_osfhandle函数将CreateFile函数返回的文件句柄转成文件描述符。

_open_osfhandle

因为刚才已经把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。这就是批处理教程中所谓的“句柄取回”。

_dup2

从句柄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)。

dup2

也就是句柄2现在指向了句柄1的文件,或者说句柄2复制了句柄1,怎么好理解就怎么理解吧,反正句柄2和句柄1现在是同一个东西。

运用重定向与句柄的高级技巧,可以实现一些有用的功能,比如说防止批处理重复运行:

@echo off 2>con 3>&2 4>>%0
echo single instance batch
echo http://demon.tw
pause

拓展阅读:

随机文章:

  1. 用VBS获取屏幕分辨率
  2. 通过SSH访问iPad
  3. 一个VBS恶作剧程序的解密
  4. /DYNAMICBASE链接选项
  5. 本地机器和Windows 2003远程桌面之间复制粘贴文件

4 条评论 发表在“批处理技术内幕:重定向与句柄”上

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

  2. Simba说道:

    那最后一条,cmd是按照逆序(右至左)还是顺序恢复句柄,虽说逆序不会锁定文件,可是顺序恢复不是2变成了con,3是%0,4是stderr(默认的2)。
    会扰乱后续代码。

  3. amwfjhh说道:

    拜读,根据楼主贴子和第一篇延伸阅读,附在最后的也可以简化成
    @echo off 2>nul 3>>%0
    echo single instance batch
    echo http://demon.tw
    pause

    关键在于前面常用句柄重定向时会触发句柄3的更改,而运行过程中的句柄3被指向一个占用文件而失败。

  4. 噢耶说道:

    找了很多關於標準輸入輸出重定向的帖子,樓主的文章是最有技術含量的,受益匪淺,謝謝博主!博主技術水平真是令我欽佩

留下回复