PowerShell stderr 重定向到文件插入换行符

PowerShell stderr redirect to file inserts newlines

编辑:我为(反对?)此行为创建了一个 PowerShell UserVoice "suggestion";随时投票。

PowerShell(5.1.16299.98,Windows 10 Pro 10.0.16299)在我重定向到文件时将换行符插入到我的标准错误中——好像是为了控制台格式化。让我们生成任意长度的错误消息:

class Program
{
    static void Main(string[] args)
    {
        System.Console.Error.WriteLine(new string('x', int.Parse(args[0])));
    }
}

我把上面的编译成longerr.exe。然后我这样称呼它:

$ .\longerr.exe 60 2>error.txt

我 运行 在 PowerShell 控制台中使用 window 宽度 60 的以下脚本:

$h = '.\longerr.exe : '.Length
$w = 60 - 1
$f = 'error.txt'
Remove-Item $f -ea Ignore
(($w-$h), ($w-$h+1), ($w), ($w+1), ($w*2-$h), ($w*2-$h+1)) |
    % { 
        $_ >> $f
        .\longerr.exe $_ 2>>$f
    }

现在,在更广泛的控制台中,我 运行 以下内容:

$ Get-Content $f | Select-String '^(?![+\t]|At line|  )'

(我本可以在文本编辑器中打开文件并修剪行。)这是输出:

43
.\longerr.exe : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

44
.\longerr.exe : 
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

59
.\longerr.exe : 
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

60
.\longerr.exe : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxx

102
.\longerr.exe : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

103
.\longerr.exe : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
x

PowerShell 为什么要这样做?我能让它停止吗?我宁愿不必做这样的事情:

        .\longerr.exe $_ 2>&1 |
            % {
                if ($_ -is [System.Management.Automation.ErrorRecord]) {
                    $_.Exception.Message | Out-File -FilePath $f -Append
                }
            }

首先,该文件的所有打开和关闭都可能很慢(我知道我可以添加 更多代码 并使用 StreamWriter),其次,还有这种方法仍然是一个问题(错误?),我不会在这个问题中讨论这个问题。

为了完整性检查,我在 cmd.exe 中 运行 longerr.exe 1000 2>test.txt;它没有插入虚假的换行符。

感谢 pointing me to the question ,我已经能够解决问题中的两个问题——主要问题和我最后提到的问题。关键是:

The complete output on stderr of the executable is simply split across several objects of type System.Management.Automation.ErrorRecord. The actual splitting seems to be non deterministic (*). Moreover, the partial strings are stored inside the property Exception instead of TargetObject. Only the first ErrorRecord has a non-null TargetObject.

(*) It depends on the order of write/flush calls of the program in relation to the read calls of the Powershell. If one adds a fflush(stderr) after each fprintf() in my test program below, there will be much more ErrorRecord objects. Except the first one, which seems deterministic, some of them include 2 output lines and some of them 3.

有了这个,我能够修改 longerr.exe 以重现我最后提到的错误:

class Program
{
    static void Main(string[] args)
    {
        if (args.Length == 1)
        {
            System.Console.Error.WriteLine(new string('x', int.Parse(args[0])));
        }
        else
        {
            for (int i = 0; i < int.Parse(args[1]); i++)
            {
                System.Console.Error.WriteLine("\n");
                System.Console.Error.WriteLine(new string('x', int.Parse(args[0])));
            }
        }
    }
}

这是有效(高效)的 PowerShell 脚本:

$p_out = 'success.txt'
$p_err = 'error.txt'

try
{
    [Environment]::CurrentDirectory = $PWD
    $append = $false
    $out = [System.IO.StreamWriter]::new($p_out, $append)
    $err = [System.IO.StreamWriter]::new($p_err, $append)

    .\longerr.exe 2000 4 2>&1 |
        % {
            if ($_ -is [System.Management.Automation.ErrorRecord]) {
                # 
                if ($_.TargetObject -ne $null) {
                    $err.WriteLine(); 
                }
                $err.Write($_.Exception.Message)
            } else {
                $out.WriteLine($_)
            }
        }
}
finally
{
    $out.Close()
    $err.Close()
}

备注:

  1. 如果没有 TargetObject 的测试,我会消除我在问题中关注的主要问题,但我仍然会得到我最后提到的 "bug",链接的 SO问题地址。
  2. 我本可以使用 Out-File(带 -NoNewline)而不是 StreamWriter,但这样做有两个问题:
    1. Windows Defender 使所有文件的打开和关闭速度比我关闭 "Real-time protection" 时慢一个数量级(全局或在包含 error.txtsuccess.txt).
    2. 即使没有 Defender 减慢速度,StreamWriter 的性能也比 Out-File 高出一个数量级以上。作为参考,我使用 Samsung 960 EVO 进行存储。
  3. StreamWriter(string, bool) constructor writes UTF-8 with no Byte-Order Mark (BOM), while PowerShell 5.1's redirection operators > and >> use UTF-16 LE with BOM. For reference, PowerShell 6.0 defaults to UTF-8 with no BOM.

(为了在真实情况下的完整性,我已经包含了标准输出。)现在,绝对荒谬的工作量 来获得 cmd.exe 的以下功能:

$ .\longerr.exe 2000 4 2>error.txt