ANSI颜色设置图形修饰在批处理过程中中断,继续进行后可以正常工作。

3

我有一个批处理程序,其中包含一个子部分,该部分迭代文件行以尝试运行 EXE,然后批处理程序根据其退出代码对 EXE 进行排序。

由于某种原因,ANSI SGR 似乎会在设置上一个的图形修饰后破坏或回显文字字面意思,而不是重新呈现它。

我回来参考了这个 question 和原始文档,但我不确定为什么我的批处理程序的特定区域在第一行回显后会搞乱控制台内的 ANSI 颜色。

我用 Notepad 替换了我的工具,您可以手动关闭它以获得零退出,或使用控制面板结束进程以获得非零退出。

由于您实际上只是使用记事本并发送一些参数,因此 test_map.log 的内容不太重要。这是我的设置:

C:\temp\qt_selftest.exe
C:\temp\sub_test.exe
C:\temp\cmd_module_test.exe
C:\temp\failing_qt_test.exe
C:\temp\passing_qt_test.exe
C:\temp\random_qt_test.exe
C:\temp\fail_module.exe
C:\temp\pass_module.exe

正如您从屏幕截图中所看到的,这些行被文字逐字处理。在我提取此代码块时,它可以正常运行... 但仅限于该代码块中。

enter image description here

你有任何想法是我可能犯了什么错吗?

由于ESC序列被转换,我无法直接分享代码,所以这里是要点: https://gist.github.com/the-nose-knows/1bebce2719e020188c6307cff736f951

如果您需要在[之前重新添加它们,请使用027的alt-code,如alt 0 2 7


1
当cmd运行外部程序时,它会禁用控制台的虚拟终端模式,并在重新获得前台进程控制权时恢复VT模式 - 如果这有助于您的理解。没有示例代码来重现问题,很难说更多。 - Eryk Sun
你评论中有趣的地方在于,我用记事本替换的检查工具使用了 bool process_created = CreateProcess(obj.process_name.c_str(), parameters, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, p_startup_info, p_proc_info);,而我最近没有对该工具进行过多次更新,但可能会触发批处理文件中更改其行为的某些操作。也许现在总是至少填写要创建的进程的 cmd-params 使其被视为“外部”因素。 - kayleeFrye_onDeck
这是来自要点的批处理输出。一些颜色代码可能不是1:1,但那是因为我重新制作了隔离测试批次。 - kayleeFrye_onDeck
1
这是cmd.exe中的一个bug。在启动时,它保存了原始的控制台输出模式,其中虚拟终端模式被禁用。当它运行外部程序时,它会恢复此模式。程序退出后,它会重置自己的模式,使虚拟终端模式启用。问题在于,在for循环语句中,一旦它恢复了启动控制台模式以执行notepad.exe,它就不会在循环退出之前重置自己的模式设置。显然,它仅依赖于顶层批处理脚本评估循环,在语句之间重置控制台模式。 - Eryk Sun
2
作为一种解决方法,您可以从启用VT模式的简单控制台程序中运行cmd.exe。然后当cmd恢复原始模式以运行外部程序时,VT模式仍将被启用。 - Eryk Sun
显示剩余2条评论
1个回答

3
如erykson所说,这可以通过确保启用虚拟终端模式来解决。如果您只关心PowerShell的颜色,可以在调用CMD.exe时添加/A开关,否则您需要一个小进程来处理此类操作,类似于一个Shim,但也要确保VTM已启用。这并不是完全的坏事,因为这个抽象层可能在未来的使用案例和错误中非常有用。
这段代码的唯一“奇怪”部分可能是我的rel-path使用。这段代码是从一个子目录运行一个批量shim的进程shim。
代码的重点是包括头文件,并在获取控制台句柄后启用StdOut的VTM。
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <Windows.h>

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif

bool uses_whitespace(std::string test_string)
{
    size_t path_white_space_query = test_string.find(' ', 0);

    if (path_white_space_query != std::string::npos)
    {
        return true;
    }

    return false;
}

int main(int argc, char* argv[])
{
    std::string this_app_path = std::string(argv[0]);

    auto it = this_app_path.find_last_of("\\", std::string::npos);

    std::string path(this_app_path, 0, it);

    // Just forwarding the args that were sent to this shim to a batch in a known location,
    // making sure whitespace arguments keep their quotes when forwarded.

    // CMD.exe will need proper quote-handling, or the call will get mangled.
    std::string str = "C:\\Windows\\System32\\cmd.exe /C \"\"" + path + "\\..\\app_shim.bat\"";
    std::vector<std::string> args;
    std::copy(argv + 1, argv + argc, std::back_inserter(args));

    for (auto const& arg : args)
    {
        if (uses_whitespace(arg))
        {
            str += (" \"" + arg + "\"");
        }
        else
        {
            str += (" " + arg);
        }
    }

    // end-of-CMD-call final wrapping quote
    str += "\"";

    HANDLE stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    DWORD mode = 0;
    GetConsoleMode(stdOutHandle, &mode);
    SetConsoleMode(stdOutHandle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);

    int exit_code = system(str.c_str());

    CloseHandle(stdOutHandle);

    return exit_code;
}

2
这应该调用 GetConsoleMode 并将 VT 标志 OR 到当前模式中,以通过 SetConsoleMode 进行设置。此外,您不需要为 StandardOutputStandardError 都执行此操作;这将是一种罕见的情况,其中这些句柄是用于单独的控制台屏幕缓冲区。 - Eryk Sun
嗯,你提到了关于STDERR的很好的观点,谢谢。我的批处理中没有任何回显会进入STDERR,所以对于我的用例来说,这应该是无关紧要的,因为我知道使用STDERR进行ANSI着色的应用程序或脚本都不会直接使用(如果这甚至可能),至少我不知道xD。我学到的越多,就越知道自己还有很多不懂的地方,但学习是有趣的!^_^谢谢,@eryksun。 - kayleeFrye_onDeck
@eryksun,最新的编辑是否反映了您在评论中的意思?我想我理解了您的意思,它似乎运行良好,但只是确保一下。 - kayleeFrye_onDeck
1
是的,那就是我想表达的意思。 - Eryk Sun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接