使用 ANSI / VT100 代码在 PowerShell 控制台中输出彩色文本

50
我写了一个程序,它打印一个包含 ANSI转义序列的字符串,以使文本变色。但是在默认的Windows 10控制台中,它并没有按预期工作,正如您在截图中所看到的那样。
程序输出出现了转义序列作为打印字符。 如果我通过变量或管道将该字符串提供给PowerShell,则输出将按预期显示(红色文本)。
如何实现程序打印彩色文本而不需要任何解决方法?

enter image description here

这是我的程序源代码(Haskell)- 但语言不重要,只是为了让你看到转义序列的写法。

main = do
    let red = "\ESC[31m"
    let reset = "\ESC[39m"
    putStrLn $ red ++ "RED" ++ reset
4个回答

106

注意:

  • 以下内容适用于 Windows 上的常规控制台窗口(由 conhost.exe 提供),这是默认情况下使用的,包括从 GUI 应用程序启动控制台应用程序时。

  • 相比之下,由Windows Terminal提供的控制台窗口(终端)以及Visual Studio Code 的集成终端默认提供对 VT / ANSI 转义序列的支持,适用于所有控制台应用程序。


虽然在 Windows 10 中,控制台窗口原则上支持 VT(虚拟终端)/ ANSI 转义序列,但默认情况下不支持

您有三个选项:

  • (a) 通过注册表详细说明在this SU answer中,默认情况下全局启用支持并进行持久化。

    • 简而言之:在注册表键[HKEY_CURRENT_USER\Console]中,创建或设置VirtualTerminalLevel DWORD值为1
      • 从PowerShell中,您可以按如下方式以编程方式执行此操作:
        Set-ItemProperty HKCU:\Console VirtualTerminalLevel -Type DWORD 1
      • cmd.exe(也适用于PowerShell):
        reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1
    • 打开一个新的控制台窗口以使更改生效。
    • 请参阅下面的注意事项。
  • (b) 通过调用SetConsoleMode() Windows API函数仅为该程序(进程)启用支持。

    • 请参阅下面的详细信息。
  • (c) 临时解决方案,从PowerShell中

    • PowerShell(Core)7+:将外部程序调用括在(...)中(不可避免地先收集所有输出再打印):

      • (.\test.exe)
    • 流式传输仅适用于Windows PowerShell的替代方法:将外部程序的输出导出到Write-Host

      • .\test.exe | Out-Host
    • 请参阅下面的详细信息。


回答 (a):

基于注册表的方法无论何时都会全局激活 VT 支持,即对所有控制台窗口生效,不管其中运行的是什么 shell / 程序:

  • 单个可执行文件 / shell 仍然可以使用方法 (b) 取消支持。

  • 相反,这意味着任何没有明确控制 VT 支持的程序输出都将受到 VT 序列的解释;虽然这通常是可取的,但假设某些程序意外地生成了带有 VT 类似序列的输出,则可能导致输出被错误解释。

注意:

  • 虽然存在一种机制可以通过启动可执行文件 / 窗口标题的 子键 来定位控制台窗口设置,该机制是通过 [HKEY_CURRENT_USR\Console] 实现的,但似乎不支持 VirtualTerminalLevel 值。

  • 即使有,它也不是一个 强大的 解决方案,因为通过 快捷方式 文件 (*.lnk)(例如从“开始”菜单或任务栏),打开控制台窗口将不会遵守这些设置,因为 *.lnk 文件内置了一些设置;虽然可以通过 属性 GUI 对话框修改这些内置设置,但截至本文撰写时,VirtualTerminalLevel 设置并未在 GUI 中显示。


Re (b):

在程序(进程)内部调用SetConsoleMode() Windows API函数,如此处所示,即使在C#中也很麻烦(因为需要P/Invoke声明),并且可能不是一个选项

  • 对于不支持调用Windows API的语言编写的程序。

  • 如果您有一个无法修改的预先存在的可执行文件。

在这种情况下,接下来讨论的选项(c)(来自PowerShell)可能适合您。


关于(c):

PowerShell在启动时会自动激活VT(虚拟终端)支持本身(在最近的Windows 10版本中,这适用于Windows PowerShell和PowerShell(Core)7+),但是在v7.3.2版中,这并不适用于从PowerShell中调用的外部程序,无论是哪个版本。

  • 另外,在v7.2+中有$PSStyle.OutputRendering 首选项变量,它控制PowerShell命令是否通过格式化系统生成彩色输出,例如Get-ChildItem输出的彩色标题。但是,此设置对于外部程序的(直接)输出没有影响。$PSStyle.OutputRendering默认为Host,这意味着只有打印到终端(控制台)的格式化输出才会被着色。$PSStyle.OutputRendering = 'PlainText'禁用了着色,$PSStyle.OutputRendering = 'Ansi'则使其无条件;请参见this answer获取更多信息。

然而,作为一种解决方法,您可以通过PowerShell的(成功)输出流中继外部程序的(stdout)输出,这样VT序列将会被识别

  • 截至PowerShell(Core) 7.3.2,这只能通过将调用括在(...)或使用Out-String来实现,但请注意,在打印之前,所有输出都会被收集[1]

    (.\test.exe)
    
  • Windows PowerShell中,除了上述方法外,还可以通过管道传输到Write-HostOut-HostWrite-OutputOut-String -Stream也可以)来进行流式传输

    .\test.exe | Write-Host
    
  • 注意:只有当您想要打印到控制台时才需要使用这些技术。相反,如果您想要捕获外部程序的输出(包括转义序列),请使用$capturedOutput = .\test.exe

字符编码注意事项:Windows PowerShell默认期望外部程序的输出使用OEM代码页,由遗留系统区域设置(例如,在美国英语系统上为437)定义,并反映在[console]::OutputEncoding中。 .NET控制台程序自动遵守该设置,但对于使用不同编码(并且产生不仅仅是7位ASCII输出的非.NET程序(例如Python脚本)),您必须(至少暂时)通过分配给[console]::OutputEncoding来指定该编码;例如,对于UTF-8:
[console]::OutputEncoding = [Text.Encoding]::Utf8.
请注意,这不仅对于VT序列解决方法是必要的,而且通常对于PowerShell正确解释非ASCII字符是必要的

PowerShell Core(v6+)很不幸,截至v7.3.2,仍然默认使用OEM代码页,但这应该被视为一个错误(请参见GitHub问题#7233),因为它否则默认为UTF-8 without BOM


[1] 使用Out-String -Stream或其内置的包装函数oss来实现流式输出是很诱人的,但自PowerShell 7.3.2起,这不再起作用,可能是由于在GitHub PR #16612中实施的优化所致。


1
谢谢,这对我帮助很大。 @Szabolcs指出可以使用os.system('color') 参考:https://dev59.com/U3VC5IYBdhLWcg3wcw0m - Yehla
从v7.3开始,它现在默认为ANSI - Robert Love
@RobertLove,默认使用ANSI吗?此答案讨论了对ANSI / VT转义序列的支持,这些序列也在Windows PowerShell和早期PowerShell(Core)版本中得到支持。但是,只有最近的PowerShell(Core)版本才将该支持扩展到_外部程序_。请注意,在v7.2+中,有$PSStyle.OutputRendering来控制PowerShell自己命令的着色行为;可以将此变量设置为Ansi,但其默认值为Host。我已更新答案以澄清$PSStyle.OutputRendering的作用。 - mklement0
@mklement0 - 根据发布说明 https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-73?view=powershell-7.3,您最后的评论现在已经不正确了。他们更改了$PSStyle.OutputRendering的默认值为“Ansi”。 - Robert Love
@RobertLove,是的,这是他们(截至本文撰写时)所声明的,但事实并非如此:只需运行$PSStyle.OutputRendering(在启动了-NoProfile的会话中,以确保安全)。我已经提出了一个问题来纠正这个问题:https://github.com/MicrosoftDocs/PowerShell-Docs/issues/9497 - mklement0

17
感谢用户 mklement0 让我意识到 VT 支持不会自动启用。这使我朝着正确的方向寻找,我找到了这篇有帮助的文章
所以回答我的问题:添加或设置注册表键。
HKCU:\Console  -  [DWORD] VirtualTerminalLevel = 1

重新启动控制台,它就能正常工作了。


2
感谢您深入挖掘 - 我甚至没有想到可能会有全局开关,但这是完全有道理的。我借此机会将您的发现并入我的更新答案中 - 包括翻转开关的编程方式和相关注意事项 - 以提供全面的答案。 - mklement0

1

1
如果有人遇到这个问题,而且无法使其正常工作,希望这能有所帮助。
我一直在使用基本的SetConsoleMode方法来将控制台模式更改为ENABLE_VIRTUAL_TERMINAL_PROCESSING0x0004)。但对我来说没有起作用。
我找到了与Python和这行代码相关的答案
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)

引起了我的注意。显然,如果您输入7而不是4(即来自MSDN的ENABLE_VIRTUAL_TERMINAL_PROCESSING),您就可以使用ANSI序列。
我不确定这适用于哪些终端,哪些不适用。到目前为止,Windows PowerShell、命令提示符和ConHost都接受它。我不知道如何在Linux上给文本上色,也不知道这种方法是否适用于旧版本的Windows。

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