PowerShell标准错误输出重定向到文件会插入换行符。

5

编辑:我为此行为创建了一个PowerShell UserVoice "建议", 请随意投票。

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

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

我在窗口宽度为60的PowerShell控制台中运行了以下脚本:

$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; 它没有插入任何伪造的换行符。

2
可能相关:https://dev59.com/WJHea4cB1Zd3GeqPnVDp - TessellatingHeckler
@TessellatingHeckler:谢谢!这解决了我在问题结尾提到的问题,并需要切换到一种编程模式,消除了我的问题的主要问题。我发布了一个详细的答案。如果想避免外壳出现cmd.exe,这是一个非常丑陋的解决方案,但至少它是一个解释。 - labreuer
1个回答

6
感谢TessellatingHeckler的评论,指引我找到了为什么PowerShell会在stderr上截断消息?这个问题,我已经成功解决了我提出的两个问题。关键在于以下内容:
可执行文件在stderr上的完整输出只是简单地拆分为几个System.Management.Automation.ErrorRecord类型的对象。实际的拆分似乎是不确定性的(*)。此外,部分字符串存储在Exception属性中,而不是TargetObject属性中。只有第一个ErrorRecord具有非空的TargetObject

(*) 它取决于程序的写/刷新调用顺序与Powershell的读取调用的关系。如果在我的下面的测试程序中的每个fprintf()后添加fflush(stderr),将会有更多的ErrorRecord对象。除了第一个看起来是确定性的之外,其中一些包括2个输出行,另一些包括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]) {
                # https://dev59.com/WJHea4cB1Zd3GeqPnVDp#33858097
                if ($_.TargetObject -ne $null) {
                    $err.WriteLine(); 
                }
                $err.Write($_.Exception.Message)
            } else {
                $out.WriteLine($_)
            }
        }
}
finally
{
    $out.Close()
    $err.Close()
}

注释:

  1. 如果没有对TargetObject进行测试,我将排除我在问题中关注的主要问题,但我仍然会遇到我在最后提到的“错误”,这与链接的SO问题有关。
  2. 我本可以使用Out-File(带-NoNewline)代替StreamWriter,但存在两个问题:
    1. Windows Defender使所有文件打开和关闭的速度比我关闭“实时保护”(全局或包含error.txtsuccess.txt的目录上)时慢一个数量级以上。
    2. 即使没有Defender拖慢速度,StreamWriter的性能也比Out-File高一个数量级以上。 供参考,我正在使用 Samsung 960 EVO存储。
  3. StreamWriter(string, bool)构造函数不带字节顺序标记(BOM)写入UTF-8,而PowerShell 5.1的重定向运算符>>>使用带BOM的UTF-16 LE。 供参考,PowerShell 6.0默认为不带BOM的UTF-8
现在,为了获得cmd.exe的以下功能,需要做出绝对荒谬的工作量,所以我包含了标准输出以完整展示实际情况。
$ .\longerr.exe 2000 4 2>error.txt

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