为什么 Process.Start 会生成 ANSI 转义码

3

在使用 System.Diagnostics.Process(在 Linux 系统上)时,我遇到了一个奇怪的问题。

每次启动一个进程时,会输出 ANSI 转义序列。
这个序列是 <ESC>[?1h<ESC>= (DECCKM, DECKPAM), 但它并不是被调用的程序产生的,我在这里使用了 pwd,但是这个序列也可以由其他任何程序创建。

这个序列似乎是在进程外部生成的,因为标准和错误输出流无法捕获它。

更奇怪的是,只有在启动了 Web 主机之后才会出现这个问题!

我构建了一个简化的代码示例。

using System;
using System.Diagnostics;
using System.Threading;

using Microsoft.Extensions.Hosting;

class Program
{
    static int count;
    static void Main(string[] args)
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        execTest();

        Host.CreateDefaultBuilder().Build().RunAsync(cancellationTokenSource.Token);
        Thread.Sleep(2000);

        execTest();
        cancellationTokenSource.Cancel();
        Thread.Sleep(2000);

        execTest();
    }

    private static void execTest()
    {
        for (var i = 0; i < 3; i++)
        {
            executeCommand("pwd");
            Console.WriteLine($"Exectest {++count}");
        }
    }

    static void executeCommand(string cmd)
    {
        var process = new Process();
        process.StartInfo.FileName = cmd;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.Start();
        process.WaitForExit();
    }
}

要查看效果,您需要特殊的日志记录、管道或重定向来禁用该效果。
而当xterm解释它们时,转义序列是不可见的。

因此,我使用 script -c myTestProg output.log; cat -A output.log

在输出中,您可以看到前三个ExecuteTests按预期工作,但Exectest 4到9产生了这种意外的输出。

Exectest 1
Exectest 2
Exectest 3
^[[?1h^[=^[[40m^[[32minfo^[[39m^[[22m^[[49m: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
^[[40m^[[32minfo^[[39m^[[22m^[[49m: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
^[[40m^[[32minfo^[[39m^[[22m^[[49m: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/jeb/xx/csharp-test
^[[?1h^[=Exectest 4
^[[?1h^[=Exectest 5
^[[?1h^[=Exectest 6
^[[40m^[[32minfo^[[39m^[[22m^[[49m: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
^[[?1h^[=Exectest 7
^[[?1h^[=Exectest 8
^[[?1h^[=Exectest 9

我的主要问题是我的程序使用Microsoft.Hosting并每秒调用一个进程,导致我的日志文件被淹没。 即使我禁用了主机的输出日志记录,问题仍然存在。
.ConfigureLogging(loggingBuilder =>
{
  loggingBuilder.ClearProviders();
});

问题仍然存在。
什么导致了输出,如何避免或抑制它? System.Diagnostics.Process和Microsoft.Extensions.Hosting之间有什么关系?
PS:这个序列是在Process.Start()之后和调用程序结束之前产生的,在调用WaitForExit之前进行了一些Thread.Sleep(100)的测试。

看起来像是将彩色日志输出到控制台。 - Klaus Gütter
@KlausGütter 是的,Microsoft.Hosting.Lifetimeinfo 是绿色的,但是 ^[[?1h 似乎是 DECCKM - 启用光标键应用模式,而 ^[=DECKPAM - 启用键盘应用模式,但为什么它会被 WaitForExit() 产生呢? - jeb
你尝试过在 appsettings.json 中将 Logging.Console.DisableColors 设置为 true 来禁用颜色吗?请参考这个问题 Logs include control characters (in place of coloring) in Visual Studio output window when using Docker 和这个 PR Add DisableColors option (#764)。或者,使用环境变量 ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS 可以禁用 ANSI 转义字符。 - Christos Lytras
@ChristosLytras 谢谢你的提示,但禁用颜色只会从“info: Microsoft.Hosting.Lifetime [0]”中删除转义序列,但令人困惑的“^[[?1h ^ [=”仍然存在。 - jeb
1个回答

4

我看到你描述的相同情况在netcoreapp3.1中也存在。

然而,在从net5.0开始的版本中,似乎对于Linux上的所有对Console.WriteLine的调用都会先写入DECCKM|DECPAM序列,无需先执行Host.CreateDefaultBuilder()

我找不到为什么旧的运行时版本只有在Host.CreateDefaultBuilder()之后才触发该行为的原因——可能与ILoggerFactory的设置有关。

这些转义字符启用了DEC VT100光标键模式键盘应用程序模式

我猜这样做是为了让没有专门的光标/PgUp/PgDn按键的键盘也能使用小键盘。

在ubuntu 1804上,您可以使用infocmp -i查看当前终端配置的转义序列:

smkx: {DEC+CKM}{DECPAM}

降低终端的功能级别是防止这种情况发生的简单方法,可以使用以下命令:
export TERM=ansi

一个NetCore应用程序将读取终端能力并跳过不受支持的发射序列。

一种更有针对性的方法是从当前终端定义中删除该有问题的序列:

infocmp | sed 's/smkx=\\E\[?1h\\E=,//g' | sed 's/$TERM/nosmkx/g' > ~/infocmp.mod
TERM=nosmkx; export TERM
tic -o ~ ~/infocmp.mod
TERMINFO=~/n/nosmkx; export TERMINFO

一些系统使用TERMCAP而不是TERMINFO,但我认为在那里可以遵循类似的过程。

谢谢,两种解决方案都有效。但我仍然不明白为什么微软一开始认为这是个好主意。 - jeb
1
据我所知,这是为了VT-100兼容性。我相信有些人很高兴这已经默认启用在.net5.0中了!! - Peter Wishart

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