无法在 Bash 中为窗口传输输出
Can't Pipe Output in Bash for Windows
我正在尝试编写一个关于 bash 的包装器,将标准输入/输出/错误重定向到父进程和从父进程重定向。到目前为止,我已经对 Windows 的 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("rn");
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);
}
原来
我是个白痴。 因为我只从 stdout 读取,所以我没有注意到 cmd 以消息的形式向 stderr 发送输出,"'bash' 不被识别为内部或外部命令、可操作程序或批处理文件。 所以我随后显示了命令的结果 dir "%windir%System32" | findstr bash.exe
. 无。 空输出。 这很奇怪,我想。
事实证明,如果您运行的是 Windows 的 64 位副本,如果请求来自 32 位应用程序,它会将对 System32 的任何请求重定向到 SysWOW64。 Bash 已安装到 System32 中。 重新编译我的应用程序以在 64 位环境中运行,瞧,"bash -c ls"输出我的可执行文件运行的文件夹的内容。 整洁。
相关文章:
- 如何在Qt窗口小部件中使用QStringView(或QStringRef)
- 问:如何使用C++中的按钮从窗口打开窗口
- 通过套接字[TCP]传输数据 如何在C / C ++中打包多个整数并使用send() recv()传输数据
- SDL 窗口不会弹出
- 在createdialog创建的窗口中捕获用于编辑控件的OnMouseMove消息
- 如何在cpp文件之间切换窗口?在Qt中
- QuadTree只在窗口的右上角绘制
- Angelscript从C++传输数组
- 如何将图像传输到c++(dll)中的缓冲区,然后在c#的缓冲区中读/写
- VS Code "command":"make"与终端窗口中的命令行"make"不同
- 如何在C++中找到active directory中禁用和锁定的窗口帐户
- 处理闪烁窗口事件
- 从服务器传输到客户端的消息不会出现
- 如何通过按下第三个窗口中的按钮,将QString从一个窗口获取到另一个窗口
- C++win32 API创建多个类似视口的窗口
- SFML RenderWindow打开窗口需要很长时间
- 如何将不同的可执行文件合并到一个窗口框架中进行编码?像浏览器一样
- 获取 SFML 窗口的 HWND 和高可用性?
- 如何获取 GLFW 窗口 ID?
- 无法在 Bash 中为窗口传输输出