为什么 Process.Start 产生 ansi 转义码

Why Process.Start produces ansi escape codes

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

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

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

奇怪的是,它只在我启动虚拟主机后出现!

我构建了一个最小的代码示例。

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)[=18 进行了测试=]

我看到了您用 netcoreapp3.1 描述的相同行为。

然而,从 net5.0 开始,似乎 all 调用 Console.WriteLine 写入 DECCKM|DECPAM 序列首先在 linux,不需要先 Host.CreateDefaultBuilder()

我找不到为什么旧的运行时版本仅在 Host.CreateDefaultBuilder() 之后才触发该行为 - 可能与 ILoggerFactory.

的设置有关

这些转义符启用 DEC VT100 Cursor key modeKeypad Application Mode.

我想这样做是为了让没有专用光标的键盘 / 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,但我认为可以在那里遵循类似的过程。