(转)Windows管道(Pipe)重定向stdout,stderr,stdin

时间:2022-09-26 00:04:47

参考:

http://qiusuoge.com/11496.html

http://www.cnblogs.com/BoyXiao/archive/2011/01/01/1923828.html

stdin是标准输入,stdout是标准输出,stderr是标准错误输出。大多数的命令行程序从stdin输入,输出到stdout或 stderr,有时我们需要重定向stdout,stderr,stdin。比如:将输出写入文件,又或者我们要将命令行程序输出结果显示到 Windows对话框中。

相关阅读

----命名管道(Named Pipes)通信学习

在Windows编程中,重定向需要用到管道(Pipe)的概念。管道是一种用于在进程间共享数据的机制。一个管道类似于一个管子的两端,一端是写入的,一端是读出的。由一个进程从写入端写入、另一个进程从读出端读出,从而实现通信,就向一个“管道”一样。

重定向的原理是:

首先声明两个概念:主程序(重定向的操纵者)、子进程(被重定向的子进程)

如果要重定位stdout的话,先生成一个管道, 管道的写入端交给子进程去写,主程序从管道的读出端读数据,然后可以把数据写成文件、显示等等。重定向stderr和stdout是相同的。

同理,要重定向stdin的话,生成一个管道, 管道的写入端由主程序写,子进程从管道的读出端读数据。

其中需要用到几个Windows API :  CreatePipe, DuplicateHandle, CreateProcess, ReadFile, WriteFile 等,函数详解可参见MSDN.

一、编程实现原理 ( C语言)

view plaincopy to clipboard
#include <windows.h> 
 
  //定义句柄: 构成stdin管道的两端句柄 
  HANDLE  hStdInRead;         //子进程用的stdin的读入端 
  HANDLE  hStdInWrite;        //主程序用的stdin的读入端 
 
  //定义句柄: 构成stdout管道的两端句柄 
  HANDLE  hStdOutRead;     ///主程序用的stdout的读入端 
  HANDLE  hStdOutWrite;    ///子进程用的stdout的写入端 
 
  //定义句柄: 构成stderr管道的句柄,由于stderr一般等于stdout,我们没有定义hStdErrRead,直接用hStdOutRead即可 
  HANDLE  hStdErrWrite;       ///子进程用的stderr的写入端 
 
  //定义一个用于产生子进程的STARTUPINFO结构体 (定义见CreateProcess,函数说明) 
  STARTUPINFO siStartInfo; 
  //定义一个用于产生子进程的PROCESS_INFORMATION结构体 (定义见CreateProcess,函数说明) 
  PROCESS_INFORMATION piProcInfo; 
 
 
  //产生一个用于stdin的管道,得到两个HANDLE:  hStdInRead用于子进程读出数据,hStdInWrite用于主程序写入数据 
  //其中saAttr是一个STARTUPINFO结构体,定义见CreatePipe函数说明 
  if   (!CreatePipe(&hStdInRead, &hStdInWrite,&saAttr, 0)) 
          return; 
 
  //产生一个用于stdout的管道,得到两个HANDLE:  hStdInRead用于主程序读出数据,hStdInWrite用于子程序写入数据 
  if  (!CreatePipe(&hStdOutRead, &hStdOutWrite,&saAttr, 0)) 
        return; 
  //由于stderr一般就是stdout, 直接复制句柄hStdOutWrite,得到 hStdErrWrite 
  
if (!DuplicateHandle(GetCurrentProcess(), hStdOutWrite,
GetCurrentProcess(), &hStdErrWrite, 0, TRUE,
DUPLICATE_SAME_ACCESS)) 
      return; 
 
  //对STARTUPINFO结构体赋值,对stdin,stdout,stderr的Handle设置为刚才得到的管道HANDLE 
  ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); 
  siStartInfo.cb = sizeof(STARTUPINFO); 
  siStartInfo.dwFlags  |= STARTF_USESHOWWINDOW; 
  siStartInfo.dwFlags  |= STARTF_USESTDHANDLES; 
  siStartInfo.hStdOutput = hStdOutWrite;     //意思是:子进程的stdout输出到hStdOutWrite 
  siStartInfo.hStdError  =  hStdErrWrite;        //意思是:子进程的stderr输出到hStdErrWrite 
  siStartInfo.hStdInput  = hStdInRead; 
 
  // 产生子进程,具体参数说明见CreateProcess函数 
  bSuccess = CreateProcess(NULL, 
      CommandLine,    // 子进程的命令行 
      NULL,                   // process security attributes 
      NULL,                   // primary thread security attributes 
      TRUE,                   // handles are inherited 
      0,                          // creation flags 
      NULL,                  // use parent's environment 
      NULL,                  // use parent's current directory 
      &siStartInfo,      // STARTUPINFO pointer 
      &piProcInfo);     // receives PROCESS_INFORMATION 
 
   //如果失败,退出 
   if (!bSuccess ) return; 
 
    //然后,就可以读写管道了 
    //写入stdin,具体代码在一个WriteToPipe函数中 
    WriteToPipe(); 
 
   //不断子检测进程有否结束 
    while (GetExitCodeProcess(piProcInfo.hProcess,&process_exit_code)) 
        { 
            //读stdout,stderr 
           ReadFromPipe(); 
           //如果子进程结束,退出循环 
           if (process_exit_code!=STILL_ACTIVE) break; 
        } 
[html] view plaincopy
#include <windows.h> 
 
  //定义句柄: 构成stdin管道的两端句柄 
  HANDLE  hStdInRead;         //子进程用的stdin的读入端 
  HANDLE  hStdInWrite;        //主程序用的stdin的读入端 
 
  //定义句柄: 构成stdout管道的两端句柄 
  HANDLE  hStdOutRead;     ///主程序用的stdout的读入端 
  HANDLE  hStdOutWrite;    ///子进程用的stdout的写入端 
 
  //定义句柄: 构成stderr管道的句柄,由于stderr一般等于stdout,我们没有定义hStdErrRead,直接用hStdOutRead即可 
  HANDLE  hStdErrWrite;       ///子进程用的stderr的写入端 
 
  //定义一个用于产生子进程的STARTUPINFO结构体 (定义见CreateProcess,函数说明) 
  STARTUPINFO siStartInfo; 
  //定义一个用于产生子进程的PROCESS_INFORMATION结构体 (定义见CreateProcess,函数说明) 
  PROCESS_INFORMATION piProcInfo; 
 
 
  //产生一个用于stdin的管道,得到两个HANDLE:  hStdInRead用于子进程读出数据,hStdInWrite用于主程序写入数据 
  //其中saAttr是一个STARTUPINFO结构体,定义见CreatePipe函数说明 
  if   (!CreatePipe(&hStdInRead, &hStdInWrite,&saAttr, 0)) 
          return; 
 
  //产生一个用于stdout的管道,得到两个HANDLE:  hStdInRead用于主程序读出数据,hStdInWrite用于子程序写入数据 
  if  (!CreatePipe(&hStdOutRead, &hStdOutWrite,&saAttr, 0)) 
        return; 
  //由于stderr一般就是stdout, 直接复制句柄hStdOutWrite,得到 hStdErrWrite 
  
if (!DuplicateHandle(GetCurrentProcess(), hStdOutWrite,
GetCurrentProcess(), &hStdErrWrite, 0, TRUE,
DUPLICATE_SAME_ACCESS)) 
      return; 
 
  //对STARTUPINFO结构体赋值,对stdin,stdout,stderr的Handle设置为刚才得到的管道HANDLE 
  ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); 
  siStartInfo.cb = sizeof(STARTUPINFO); 
  siStartInfo.dwFlags  |= STARTF_USESHOWWINDOW; 
  siStartInfo.dwFlags  |= STARTF_USESTDHANDLES; 
  siStartInfo.hStdOutput = hStdOutWrite;     //意思是:子进程的stdout输出到hStdOutWrite 
  siStartInfo.hStdError  =  hStdErrWrite;        //意思是:子进程的stderr输出到hStdErrWrite 
  siStartInfo.hStdInput  = hStdInRead; 
 
  // 产生子进程,具体参数说明见CreateProcess函数 
  bSuccess = CreateProcess(NULL, 
      CommandLine,    // 子进程的命令行 
      NULL,                   // process security attributes 
      NULL,                   // primary thread security attributes 
      TRUE,                   // handles are inherited 
      0,                          // creation flags 
      NULL,                  // use parent's environment 
      NULL,                  // use parent's current directory 
      &siStartInfo,      // STARTUPINFO pointer 
      &piProcInfo);     // receives PROCESS_INFORMATION 
 
   //如果失败,退出 
   if (!bSuccess ) return; 
 
    //然后,就可以读写管道了 
    //写入stdin,具体代码在一个WriteToPipe函数中 
    WriteToPipe(); 
 
   //不断子检测进程有否结束 
    while (GetExitCodeProcess(piProcInfo.hProcess,&process_exit_code)) 
        { 
            //读stdout,stderr 
           ReadFromPipe(); 
           //如果子进程结束,退出循环 
           if (process_exit_code!=STILL_ACTIVE) break; 
        }

具体看一下WriteToPipe(), ReadFromPipe函数:

view plaincopy to clipboard
//写入stdin 
BOOL WriteToPipe() 

  DWORD dwWritten; 
  BOOL bSuccess = FALSE; 
   
//用WriteFile,从hStdInWrite写入数据,数据在in_buffer中,长度为dwSize 
  bSuccess = WriteFile( hStdInWrite, in_buffer, dwSize, &dwWritten, NULL); 
  return bSuccess; 

[html] view plaincopy
//写入stdin 
BOOL WriteToPipe() 

  DWORD dwWritten; 
  BOOL bSuccess = FALSE; 
   
//用WriteFile,从hStdInWrite写入数据,数据在in_buffer中,长度为dwSize 
  bSuccess = WriteFile( hStdInWrite, in_buffer, dwSize, &dwWritten, NULL); 
  return bSuccess; 
}

view plaincopy to clipboard
// 读出stdout 
BOOL ReadFromPipe() 

  char out_buffer[4096]; 
  DWORD dwRead;   
  BOOL bSuccess = FALSE; 
 
   //用WriteFile,从hStdOutRead读出子进程stdout输出的数据,数据结果在out_buffer中,长度为dwRead 
   bSuccess = ReadFile( hStdOutRead, out_buffer, BUFSIZE, &dwRead, NULL); 
   if ((bSuccess) && (dwRead!=0))  //如果成功了,且长度>0 
             { 
                   // 此处加入你自己的代码 
                   // 比如:将数据写入文件或显示到窗口中 
             } 
  return bSuccess; 

[html] view plaincopy
// 读出stdout 
BOOL ReadFromPipe() 

  char out_buffer[4096]; 
  DWORD dwRead;   
  BOOL bSuccess = FALSE; 
 
   //用WriteFile,从hStdOutRead读出子进程stdout输出的数据,数据结果在out_buffer中,长度为dwRead 
   bSuccess = ReadFile( hStdOutRead, out_buffer, BUFSIZE, &dwRead, NULL); 
   if ((bSuccess) && (dwRead!=0))  //如果成功了,且长度>0 
             { 
                   // 此处加入你自己的代码 
                   // 比如:将数据写入文件或显示到窗口中 
             } 
  return bSuccess; 
}

OK,到此原理写完了。为简化说明原理,上述代码省略了出错处理、结束处理(如:CloseHandle等),具体可以参见我的源码。

二、封装、实用的代码

上述过程有些麻烦,实际使用中,我封装成几个函数:

首先定义三个回调函数 (就是函数指针类型)

//当stdin输入时,调用此函数。将要写的数据写入buf中,*p_size写为数据长度即可。
typedef void FuncIn(char *buf,int *p_size);

//当stdout,stderr输出时,调用此函数。可读取的数据在buf中,数据长度为size。
typedef void FuncOut(char *buf,int size);

//当子进程持续过程中,周期性调用此函数,设置p_abort可中断子进程。
typedef void FuncProcessing(int *p_abort);

然后定义了四个函数

//执行一个命令行,重定向stdin, stdout,stderr。
//OnStdOut是回调函数指针,当有输出时,OnStdOut被调用。
//OnStdIn是回调函数指针,当输入时,OnStdIn被调用。
int ExecCommandEx(char *szCommandLine,char *CurrentDirectory,char *Environment,unsigned short ShowWindow,
              FuncOut *OnStdOut,FuncProcessing *OnProcessing,FuncIn *OnStdIn);

//执行一个命令行,重定向stdout,stderr。
//OnLineOut是回调函数指针,当有一行输出时,OnLineOut被调用。
int ExecCommandOutput(char *szCommandLine,char *Environment,unsigned short ShowWindow,
                       FuncOut *OnLineOut,FuncProcessing *OnProcessing);

//执行一个命令行,等待子进程结束,返回子进程的程序退出代码。不处理重定向。
int ExecCommandWait(char *szCommandLine,unsigned short ShowWindow,FuncProcessing *OnProcessing);

//执行一个命令行,不等待子进程结束,即返回。不处理重定向。功能相当于 Windows API  WinExec.
int ExecCommandNoWait(char *szCommandLine,unsigned short ShowWindow);

还定义了一个存储数据的EXEC_INFO结构体及操作它的函数。

全部代码为C语言,在JExecution.c, JExecution.h两个文件中。只用到了Windows API,没有用MFC及其他库。

三、使用方法

有了JExecution.c,使用就很方便了。比如:

view plaincopy to clipboard
#include <stdio.h> 
#include <windows.h> 
#include "JExecution.h" 
 
//定义一个处理输出的回调函数 
void OnLineOut(char *buf,int size) 

   printf("%s\n", buf); 

 
 
int main(int argc, char* argv[]) 

  int n; 
  char *command; 
 
  command = "cmd.exe  /r dir/w ";   //这个命令的意思就是调用DOS,执行dir命令,显示当前目前下的文件 
  n=ExecCommandOutput(command,NULL,SW_HIDE,OnLineOut,NULL); 
  printf("<Return:>%d",n); 

[html] view plaincopy
#include <stdio.h> 
#include <windows.h> 
#include "JExecution.h" 
 
//定义一个处理输出的回调函数 
void OnLineOut(char *buf,int size) 

   printf("%s\n", buf); 

 
 
int main(int argc, char* argv[]) 

  int n; 
  char *command; 
 
  command = "cmd.exe  /r dir/w ";   //这个命令的意思就是调用DOS,执行dir命令,显示当前目前下的文件 
  n=ExecCommandOutput(command,NULL,SW_HIDE,OnLineOut,NULL); 
  printf("<Return:>%d",n); 
}

我用C++ Builder 6写了一个示范程序。示范将stdout重定向,输出到窗口中。

(转)Windows管道(Pipe)重定向stdout,stderr,stdin

四、小结

Windows管道虽然有些麻烦,却可以产生好的效果。

比如:我们常用的IDE就是把调用命令行的编译程序,将其stdout重定向,将结果信息显示在窗口中。
又比如,我们把上述的程序改一下,把dir命令的结果通过网络发送出去,嗯,Hacker.

(转)Windows管道(Pipe)重定向stdout,stderr,stdin的更多相关文章

  1. file descriptor 0 1 2 一切皆文件 stdout stderr stdin &sol;dev&sol;null 沉默是金 pipes 禁止输出 屏蔽 stdout 和 stderr 输入输出重定向 重定向文件描述符

    movie.mpeg.001 movie.mpeg.002 movie.mpeg.003 ... movie.mpeg.099   $cat movie.mpeg.0*>movie.mpeg   ...

  2. 【python】使用unix管道pipe处理stdout实时数据

    现在有个实时抓包处理的程序,大概的流程是 使用tshark抓包->实时上传,如果写log的话是可以的,但是log文件切割需要定时执行. 由于log中有些内容需要实时处理,延迟时间会导致数据误差, ...

  3. file descriptor 0 1 2 一切皆文件 stdout stderr stdin &sol;dev&sol;null 沉默是金 pipes

    $>emtry_or_create_a_file.f $ll>>append_a_file.f standard output input error $ls -l /usr/bin ...

  4. 【原创】Linux基础之重定向stdout&sol;stderr

    启动进程后查看日志(stdout和stderr) 1 nohup+tail # nohup $cmd > /path/to/file 2>&1 & # tail -f /p ...

  5. shell基础知识之 stdin&comma;stdout&comma;stderr和文件描述符

    stdin,stdout,stderr stdin=0 stdout=1 stderr=2 使用tee来传递内容,把stdout 作为stdin 传到下个命令 root@172-18-21-195:/ ...

  6. Linux Shell 文件描述符 及 stdin stdout stderr 重定向

    Abstract: 1) Linux Shell 命令的标准输入.标准输出.标准错误,及其重定位: 2)Linux Shell 操作自定义文件描述符: 文件描述符是与文件相关联的一些整数,他们保持与已 ...

  7. WorkerMan源码分析&lpar;resetStd方法,PHP中STDIN&comma; STDOUT&comma; STDERR的重定向&rpar;

    WorkerMan中work.php中 resetStd 方法中代码如下 public static function resetStd() { if (!static::$daemonize || ...

  8. STDOUT&sol;STDERR重定向到ALOG中

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/kangear/article/details/24534707      说下背景:如今众多Andr ...

  9. stdin stdout stderr 标准I&sol;O流

    Unix中一切皆文件,磁盘等设备在操作系统来看都是文件. 对文件进行操作时,需要打开这个文件,并获得文件描述符(file descriptor, fd) 而每个进程生来就有三个文件描述符绑定在它身上, ...

随机推荐

  1. stringbuffer 和 stringbuilder的区别

    1. stringbuffer 和 stringbuilder的区别 StringBuffer是线程安全的, 这个类里的所有方法是同步的.这个反过来就会对程序的性能有一定的影响.StringBuild ...

  2. github 更新fork分支

    在github上开发代码的时候我们习惯的是fork一个分支,然后修改再往主分支push request,这样就可以保证多人开发, 但是随着时间的推移,自己fork的版本和主分支的版本差异越来越大; 这 ...

  3. 开始学习机器学习,从Ng的视频开始

    时隔开5个月,忙完了考研和毕设后终于有时间搞自己想搞得,研究生导师方向是图像处理与机器学习结合,重新开工 何为机器学习? 对于机器学习(Machine Learning)的定义大体上有两种,第一种是美 ...

  4. Mesos和Docker的集成

    摘要: 众所周知,Mesos全面支持Docker.但是这意味着什么呢?在命令行里运行docker run...就可以使用Docker了.还需要做什么?让我们一起研究下Mesos的高级特性——和Dock ...

  5. SwitchHosts—hosts管理工具

    SwitchHosts是一个管理.快速切换Hosts小工具,开源软件,一键切换Hosts配置,非常实用,高效.开发Web过程成,部署有多套环境,网址域名都相同,部署在不同的服务器上,有开发环境.测试环 ...

  6. spark streaming 概述

    批处理 & 流处理 像这个是批处理 像这样就是流处理 为什么需要流处理--更多场景需要 Spark Core & RDD 本质上是离线运算 Spark Streaming是什么(分布式 ...

  7. C&sol;C&plus;&plus;内存泄露及检测工具

    内存泄漏的定义   一般我们常说的内存泄漏是指堆内存的泄漏.堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内 存.应用程序一般使用malloc,re ...

  8. Android studio 导入ApiDemo

    1.import 项目,sdk目录:sdk\samples\android-21\legacy\ApiDemos,import时一直下一步就ok了. 2.Error:Error: The file n ...

  9. DFS leetcode

    把字符串转换成整数 class Solution { public: int StrToInt(string str) { int n = str.size(), s = 1; long long r ...

  10. 微信支付接入的总结 —— NATIVE &amp&semi; MWEB &amp&semi; JSAPI

    这段时间工作中需要对接微信支付,而且要多个端同时进行接入,web端,手机浏览器,微信浏览器,所以研究了下.不同场景选择合适的接入方式是必须的.https://pay.weixin.qq.com/wik ...