C++学习:通过管道进行cmd进程输入输出重定向

在日常的工作中,shell使用比较多,尤其在软件测试过程中,但使用手工操作既麻烦,又记不住命令,关键是不能自动化。众所周知,linux或者windows系统的shell都是可以进行输入输出重定向的,利用输入输出重定向技术,把shell的输入输出映射到自己所写的进程里,这样就很方面了。比如要使用ssh2远程一个linux机器,就需要自己实现ssh2客户端的协议,使用重定向,就可以直接使用操作系统自带的ssh(linux系统),或者putty(windows下的telnet、ssh命令)。


本文所要讲述的实现方法就是利用管道,管道的概念,大家网上搜索,这里还是通过直接show代码的方式进行讲述,另外进行了简单的封装和进一步的抽象,大家可以直接使用,扩展起来也比较方便。本文是基于win32,linux实现留给大家自己当着作业吧,于此类似。


首先设计Shell类型的大概的样子如下:

#include "windows.h"

class Shell
{
public:
	Shell(void);
	~Shell(void);

	bool RunProcess(const string &process);
	bool StopProcess(void);
	bool GetOutput(const string &endStr, int timeout, string &outstr );//获取输出字符串
	bool SetInput(const string &cmd);//执行命令
private:
	HANDLE m_hChildInputWrite;	//用于重定向子进程输入的句柄
	HANDLE m_hChildInputRead;
	HANDLE m_hChildOutputWrite;	//用于重定向子进程输出的句柄  
	HANDLE m_hChildOutputRead;
	PROCESS_INFORMATION m_cmdPI;//cmd进程信息
};
上述代码,重定向的句柄保存起来,目的是方便用户自己扩展,同时也保存了cmd进程的信息,可以在程序推出的时候,杀掉cmd进程。接下来看看构造函数和析构函数,资源初始化和释放。

Shell::Shell(void)
{
	m_hChildInputWrite = NULL;
	m_hChildInputRead  = NULL;
	m_hChildOutputWrite= NULL;
	m_hChildOutputRead = NULL;
	ZeroMemory(&m_cmdPI, sizeof(m_cmdPI));   
}

Shell::~Shell(void)
{
	StopProcess(); 
}

接下来就是创建进程(比如cmd.exe)了,便于进行输入输出操作,代码如下:

bool Shell::RunProcess( const string &process )
{
	SECURITY_ATTRIBUTES   sa;   
	sa.bInheritHandle = TRUE;   
	sa.lpSecurityDescriptor = NULL;   
	sa.nLength = sizeof(sa); 

	//创建子进程输出匿名管道 
	if( FALSE == ::CreatePipe(&m_hChildOutputRead, &m_hChildOutputWrite, &sa, 0) )   
	{    
		return false;   
	}  

	//创建子进程输入匿名管道   
	if( FALSE == CreatePipe(&m_hChildInputRead, &m_hChildInputWrite, &sa, 0) )   
	{   
		::CloseHandle(m_hChildOutputWrite);
		::CloseHandle(m_hChildOutputRead);
		::CloseHandle(m_hChildOutputWrite);
		::CloseHandle(m_hChildOutputRead);
		return false;
	}

	ZeroMemory(&m_cmdPI, sizeof(m_cmdPI));   
	STARTUPINFO  si; 
	GetStartupInfo(&si);

	si.cb = sizeof(STARTUPINFO);
	si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_HIDE;
	si.hStdInput   = m_hChildInputRead;     //重定向子进程输入   
	si.hStdOutput  = m_hChildOutputWrite;   //重定向子进程输入    
	si.hStdError   = m_hChildOutputWrite; 

	if( FALSE == ::CreateProcess(NULL, (process.c_str()), NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &m_cmdPI) )   
	{   
		::CloseHandle(m_hChildInputWrite);
		::CloseHandle(m_hChildInputRead);
		::CloseHandle(m_hChildOutputWrite);
		::CloseHandle(m_hChildOutputRead);
		m_hChildInputWrite = NULL;
		m_hChildInputRead  = NULL;
		m_hChildOutputWrite= NULL;
		m_hChildOutputRead = NULL;
		ZeroMemory(&m_cmdPI, sizeof(m_cmdPI));  
		return false;   
	}

	return true;
}

bool Shell::StopProcess( void )
{
	::CloseHandle(m_hChildInputWrite);
	::CloseHandle(m_hChildInputRead);
	::CloseHandle(m_hChildOutputWrite);
	::CloseHandle(m_hChildOutputRead);
	m_hChildInputWrite = NULL;
	m_hChildInputRead  = NULL;
	m_hChildOutputWrite= NULL;
	m_hChildOutputRead = NULL;
	::TerminateProcess(m_cmdPI.hProcess, -1);
	::CloseHandle(m_cmdPI.hProcess);   
	::CloseHandle(m_cmdPI.hThread); 
	ZeroMemory(&m_cmdPI, sizeof(m_cmdPI));
}

如果你想通过putty来实现telnet、ssh操作进行,一是把cmd.exe替换为putty命令行的形式,另外一种形式,就是把putty当着普通的shell命令执行即可。进程创建好了,下面来看看读写函数的实现:

bool Console::GetOutput( const string &endStr, int timeout, string &outstr )
{
	if( NULL == m_hChildOutputRead )
	{
		return false;
	}

	outstr = "";
	char buffer[4096] = {0};
	DWORD readBytes = 0;
	while( timeout > 0 )
	{
		//对管道数据进行读,但不会删除管道里的数据,如果没有数据,就立即返回
		if( FALSE == PeekNamedPipe( m_hChildOutputRead, buffer, sizeof(buffer) - 1, &readBytes, 0, NULL ) )
		{
			return false;
		}

		//检测是否读到数据,如果没有数据,继续等待
		if( 0 == readBytes )
		{
			::Sleep(200);
			timeout -= 200;
			continue;
		}
		
		readBytes = 0;
		if( ::ReadFile( m_hChildOutputRead, buffer, sizeof(buffer) - 1, &readBytes, NULL) )
		{
			outstr.insert( outstr.end(), buffer, buffer + readBytes );
			size_t pos = outstr.rfind(endStr);
			if( string::npos == pos )
			{
				continue;
			}
			if( pos == outstr.size() - endStr.size() )
			{
				return true;//找到数据
			}
		}
		else
		{
			return false;
		}
	}

	return false;
}

bool Shell::SetInput( const string &cmd )
{
	if( NULL == m_hChildInputWrite )
	{
		return "";
	}

	string tmp = cmd + "\r\n";
	DWORD writeBytes = 0;
	if( FALSE == ::WriteFile( m_hChildInputWrite, tmp.c_str(), tmp.size(), &writeBytes, NULL ) )
	{
		return false;
	}
	return true;
}

写函数实在是平淡无奇,就是调用WriteFile,只要把句柄传递对了就行,这几个句柄是容易混淆,大家仔细阅读代码,仔细理解。在读函数中,使用了PeekNamedPipe函数,对管道进行数据读,这个函数有个作用,即使管道中没有数据,但会立即返回,不会读阻塞。这个时候再调用ReadFile就不会阻塞了。同时,使用Sleep函数就能简单的实现超时读的功能。这个是ReadFile办不到的。


到此为止,代码已经实现完了,很简单吧,曾经为了实现ssh2客户端,费尽心思,找了无数个开源代码研究。用这个方法来实现,真是太简单了。不是吗?下面来看看测试代码:

#include "console.h"
#include <iostream>
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	Console console;
	if( false == console.RunProcess("cmd.exe") )
	{
		cout<<"create cmd.exe process fail"<<endl;
		return -1;
	}
	string outstr;
	console.GetOutput(">", 3000, outstr);
	cout<<outstr<<endl;
	console.SetInput("dir");
	console.GetOutput(">", 3000, outstr);
	cout<<outstr<<endl;

	return 0;
}

上述代码输出:



大功告成,代码实现没有用到什么高级技巧,但简单适用,大家不妨直接借用,也许能帮助大家解决日常的工作问题。


  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
C语言标准库中有一个函数叫做freopen(),它可以用于重定向C程序的输入和输出流。通过调用freopen()函数,我们可以将标准输入流stdin和标准输出流stdout重定向到文件或者管道中。 要将C程序的标准输入流重定向管道,可以使用如下命令: ``` freopen("input.txt", "r", stdin); ``` 这会将程序的标准输入流重定向到名为"input.txt"的文件中。这样,程序执行时会将文件中的内容作为输入。 同样地,要将C程序的标准输出流重定向管道,可以使用如下命令: ``` freopen("output.txt", "w", stdout); ``` 这会将程序的标准输出流重定向到名为"output.txt"的文件中。这样,程序执行后的输出会被写入到文件中。 需要注意的是,freopen()函数也可以对C中的cin和cout进行重定向。使用方法与上述类似,只需要将文件名替换为管道的名称即可。 通过这种方式,我们可以在C程序中实现输入输出重定向,从而实现输入和输出的管道操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++输入输出重定向(3种方法)](https://blog.csdn.net/weixin_39536630/article/details/116964030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值