我试图围绕bash编写一个包装器,将标准输入/输出/错误重定向到父进程和从父进程重定向。到目前为止,我已经有了一个围绕Window的cmd.exe的包装器。我可以键入命令并让它在控制台中运行,然后读取该命令的输出并将其显示给用户。 所以我认为用同样的方式将它包装在bash中是一件容易的事。但是......如果我将进程设置为打开bash,则无法获得任何输出。如果我打开一个cmd进程并运行" bash"甚至,如下面的演示,用" -c"运行一个命令。选项。没有输出。 我编写了最小的测试用例,如下所示:
#include <algorithm>
#include <cassert>
#include <functional>
#include <string>
#include <tchar.h>
#include <Windows.h>
using wstring = std::wstring;
using astring = std::string;
#ifdef UNICODE
using tstring = wstring;
using tchar = wchar_t;
#else
using tstring = astring;
using tchar = char;
#endif
const tstring PROMPT = L"ATOTALLYRANDOMSTRING";
/**
* Represents an instance of a terminal process with piped in, out, and err
* handles.
*/
class Terminal
{
public:
using OutputCallback = std::function<void(tstring)>;
/**
* Terminal constructor.
*/
Terminal();
/**
* Terminal destructor.
*/
~Terminal();
/**
* Executes the specified command. If a callback is specified, the output
* be passed as the first argument.
*
* @param command
* @param callback
* @param buffer If specified, the callback parameter will be called as
* output is available until the command is complete.
*/
void exec(astring command, OutputCallback callback = nullptr, bool buffer = true);
/**
* Reads from the terminal, calling the specified callback as soon as any
* output is available.
*
* @param callback
*/
void read(OutputCallback callback);
/**
* Reads from the terminal, calling the specified callback upon reaching a
* newline.
*
* @param callback
* @param buffer If specified, causes the callback to be called as output
* is available until a newline is reached.
*/
void readLine(OutputCallback callback, bool buffer = true);
/**
* Reads from the terminal, calling the specified callback upon reaching
* the specified terminator.
*
* @param terminator
* @param callback
* @param buffer If specified, causes the callback to be called as
* output is available until the specified terminator is reached.
*/
void readUntil(const tstring& terminator, OutputCallback callback, bool buffer = true);
/**
* Read from the terminal, calling the specified callback upon reaching the
* prompt.
*
* @param callback
* @param buffer If specified, causes the callback to be called as output
* is available until the specified prompt is reached.
*/
void readUntilPrompt(OutputCallback callback, bool buffer = true);
/**
* Writes the specified text to the terminal.
*
* @param data
*/
void write(const astring& data);
private:
struct
{
struct
{
HANDLE in;
HANDLE out;
HANDLE err;
} read, write;
} pipes;
tstring bufferedData;
PROCESS_INFORMATION process;
void initPipes();
void initProcess();
void terminatePipes();
void terminateProcess();
};
int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
Terminal terminal;
terminal.readUntilPrompt([] (tstring startupInfo) {
MessageBox(nullptr, startupInfo.c_str(), L"Startup Information", MB_OK);
});
// This works
terminal.exec("dir", [] (tstring result) {
MessageBox(nullptr, result.c_str(), L"dir", MB_OK);
});
// This doesn't (no output)
terminal.exec("bash -c \"ls\"", [] (tstring result) {
MessageBox(nullptr, result.c_str(), L"bash -c \"ls\"", MB_OK);
});
return 0;
}
Terminal::Terminal()
{
this->initPipes();
this->initProcess();
}
Terminal::~Terminal()
{
this->terminateProcess();
this->terminatePipes();
}
void Terminal::exec(astring command, OutputCallback callback, bool buffer)
{
command.append("\r\n");
this->write(command);
this->readUntilPrompt([&callback, &command] (tstring data) {
if (!callback) {
return;
}
// Remove the prompt from the string
data.erase(data.begin(), data.begin() + command.length());
callback(data);
}, buffer);
}
void Terminal::initPipes()
{
SECURITY_ATTRIBUTES attr;
attr.nLength = sizeof(attr);
attr.bInheritHandle = TRUE;
attr.lpSecurityDescriptor = nullptr;
if(!CreatePipe(&this->pipes.read.in, &this->pipes.write.in, &attr, 0))
{
throw std::exception("Failed to create stdin pipe.");
}
if(!SetHandleInformation(this->pipes.write.in, HANDLE_FLAG_INHERIT, 0))
{
throw std::exception("Failed to unset stdin pipe inheritance flag.");
}
if(!CreatePipe(&this->pipes.read.out, &this->pipes.write.out, &attr, 0))
{
throw std::exception("Failed to create stdout pipe.");
}
if(!SetHandleInformation(this->pipes.read.out, HANDLE_FLAG_INHERIT, 0))
{
throw std::exception("Failed to unset stdout pipe inheritance flag.");
}
if(!CreatePipe(&this->pipes.read.err, &this->pipes.write.err, &attr, 0))
{
throw std::exception("Failed to create stderr pipe.");
}
if(!SetHandleInformation(this->pipes.read.err, HANDLE_FLAG_INHERIT, 0))
{
throw std::exception("Failed to unset stderr pipe inheritance flag.");
}
}
void Terminal::initProcess()
{
tstring command;
STARTUPINFO startup;
#ifdef UNICODE
command.append(L"cmd /U /K \"prompt ");
command.append(PROMPT);
command.append(L"\"");
#else
command.append("cmd /A /K \"prompt ");
command.append(PROMPT);
command.append("\"");
#endif
ZeroMemory(&this->process, sizeof(this->process));
ZeroMemory(&startup, sizeof(startup));
startup.cb = sizeof(startup);
startup.dwFlags |= STARTF_USESTDHANDLES;
startup.hStdInput = this->pipes.read.in;
startup.hStdOutput = this->pipes.write.out;
startup.hStdError = this->pipes.write.err;
startup.dwFlags |= STARTF_USESHOWWINDOW;
startup.wShowWindow = SW_HIDE;
auto created = CreateProcess(
nullptr,
_tcsdup(command.c_str()),
nullptr,
nullptr,
TRUE,
0,
nullptr,
nullptr,
&startup,
&this->process
);
if (!created) {
throw std::exception("Failed to create process.");
}
}
void Terminal::read(OutputCallback callback)
{
this->readUntil(L"", callback);
}
void Terminal::readLine(OutputCallback callback, bool buffer)
{
this->readUntil(L"\n", callback, buffer);
}
void Terminal::readUntil(const tstring& terminator, OutputCallback callback, bool buffer)
{
auto terminatorIter = terminator.cbegin();
auto terminatorEnd = terminator.cend();
auto bufferIter = this->bufferedData.begin();
auto bufferEnd = this->bufferedData.end();
do {
DWORD bytesAvailable = 0;
if (!PeekNamedPipe(this->pipes.read.out, nullptr, 0, nullptr, &bytesAvailable, nullptr)) {
throw std::exception("Failed to peek command input pipe.");
}
if (bytesAvailable)
{
DWORD bytesRead;
tchar* data;
data = new tchar[bytesAvailable / sizeof(tchar)];
if (!ReadFile(this->pipes.read.out, data, bytesAvailable, &bytesRead, nullptr)) {
throw std::exception("ReadFile failed.");
}
assert(bytesRead == bytesAvailable);
auto iterDistance = bufferIter - this->bufferedData.begin();
this->bufferedData.append(data, bytesRead / sizeof(tchar));
bufferIter = this->bufferedData.begin() + iterDistance;
bufferEnd = this->bufferedData.end();
}
if (terminator.empty()) {
if (!this->bufferedData.empty())
{
bufferIter = bufferEnd;
terminatorIter = terminatorEnd;
}
} else {
while(bufferIter != bufferEnd && terminatorIter != terminatorEnd) {
if (*bufferIter == *terminatorIter) {
++terminatorIter;
} else {
terminatorIter = terminator.begin();
}
++bufferIter;
}
}
if (!buffer || terminatorIter == terminatorEnd) {
callback(tstring(this->bufferedData.begin(), bufferIter - terminator.length()));
this->bufferedData.erase(this->bufferedData.begin(), bufferIter);
}
} while (terminatorIter != terminatorEnd);
}
void Terminal::readUntilPrompt(OutputCallback callback, bool buffer)
{
this->readUntil(PROMPT, callback, buffer);
}
void Terminal::terminatePipes()
{
if (this->pipes.read.err) {
CloseHandle(this->pipes.read.err);
}
if (this->pipes.write.err) {
CloseHandle(this->pipes.write.err);
}
if (this->pipes.read.out) {
CloseHandle(this->pipes.read.out);
}
if (this->pipes.write.out) {
CloseHandle(this->pipes.write.out);
}
if (this->pipes.read.in) {
CloseHandle(this->pipes.read.in);
}
if (this->pipes.write.in) {
CloseHandle(this->pipes.write.in);
}
}
void Terminal::terminateProcess()
{
if (this->process.hProcess) {
CloseHandle(this->process.hProcess);
}
}
void Terminal::write(const astring& data)
{
DWORD byteCount;
DWORD bytesWritten;
byteCount = data.length();
if (!WriteFile(this->pipes.write.in, data.c_str(), byteCount, &bytesWritten, nullptr)) {
throw std::exception("WriteFile failed.");
}
assert(bytesWritten == byteCount);
}
答案 0 :(得分:1)
事实证明我是个白痴。因为我只是从stdout读取,所以我没有注意到cmd以消息读取的形式向stderr发送输出,“'bash'不被识别为内部或外部命令,可操作程序或批处理文件。”然后我显示了命令dir "%windir%\System32" | findstr bash.exe
的结果。没有。空输出。我想,这很奇怪。
如果您运行的是64位Windows副本,如果请求来自32位应用程序,它会将对System32的任何请求重定向到SysWOW64。 Bash已安装到System32。重新编译我的应用程序以在64位环境中运行etvoilà,“bash -c ls”输出我的可执行文件运行的文件夹的内容。整洁。