PowerShell标准错误输出中的随机换行符

5
我希望您能使用HandBrake将许多.iso文件转换为.mp4,并尝试使用命令行界面。我更喜欢使用PowerShell编写脚本而不是批处理文件。然而,如果我使用PowerShell,标准错误会在随机位置包含换行符。
为了进行故障排除,我创建了一个简化的脚本,分别用PowerShell和批处理编写。
PowerShell:
& "$Env:ProgramFiles\HandBrake\HandBrakeCLI.exe" @(
    '--input', 'V:\',
    '--title', '1', '--chapter', '1',
    '--start-at', 'duration:110', '--stop-at', 'duration:15',
    '--output', 'pmovie.mp4',
    '--format', 'av_mp4'
    ) > ".\pstd.txt" 2> ".\perr.txt"

批处理文件:

"%ProgramFiles%\HandBrake\HandBrakeCLI.exe" --input V:\ --title 1 --chapter 1 --start-at duration:110 --stop-at duration:15 --output ".\cmovie.mp4" --format av_mp4 > ".\cstd.txt" 2> ".\cerr.txt"

这两个脚本都可以创建相同的 .mp4 文件,唯一的区别是它们创建的标准错误输出不同:

Powershell:

HandBrakeCLI.exe : [10:41:44] hb_init: starting libhb thread
At C:\Test\phandbrake.ps1:1 char:2
+ & <<<<  "$Env:ProgramFiles\HandBrake\HandBrakeCLI.exe" @(
    + CategoryInfo          : NotSpecified: ([10:41:44] hb_i...ng libhb thread 
   :String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

[10:41:44] thread 541fc20 started ("libhb")
HandBrake 1.1.2 (2018090500) - MinGW x86_64 - https://handbrake.fr
8 CPUs detected

O
pening V:\...

[10:41:44] CPU: Intel(R) Core(TM) i7-2600K CPU @ 3.40GHz

[10:41:44]  - Intel microarchitecture Sandy Bridge
[10:41:44]  - logical processor count: 8

[10:41:44] Intel Quick Sync Video support: no

[10:41:44] hb_scan: path=V:\, title_index=1

src/libbluray/disc/disc.c:424: error opening file BDMV\index.bdmv

src/libbluray/disc/disc.c:424: error opening file BDMV\BACKUP\index.bdmv

[10:41:44] bd: not a bd - trying as a stream/file instead

libdvdnav: Using dvdnav version 6.0.0

l
ibdvdnav: Unable to open device file V:\.
libdvdnav: vm: dvd_read_name failed
libdvdnav: DVD disk re
ports i
tself wi
th Region mask 0x
0000000
0. Reg
ions:
 1 2 3 4 5 
6 7 8

批处理文件:

[10:41:35] hb_init: starting libhb thread
[10:41:35] thread 5a2cc30 started ("libhb")
HandBrake 1.1.2 (2018090500) - MinGW x86_64 - https://handbrake.fr
8 CPUs detected
Opening V:\...
[10:41:35] CPU: Intel(R) Core(TM) i7-2600K CPU @ 3.40GHz
[10:41:35]  - Intel microarchitecture Sandy Bridge
[10:41:35]  - logical processor count: 8
[10:41:35] Intel Quick Sync Video support: no
[10:41:35] hb_scan: path=V:\, title_index=1
src/libbluray/disc/disc.c:424: error opening file BDMV\index.bdmv
src/libbluray/disc/disc.c:424: error opening file BDMV\BACKUP\index.bdmv
[10:41:35] bd: not a bd - trying as a stream/file instead
libdvdnav: Using dvdnav version 6.0.0
libdvdnav: Unable to open device file V:\.
libdvdnav: vm: dvd_read_name failed
libdvdnav: DVD disk reports itself with Region mask 0x00000000. Regions: 1 2 3 4 5 6 7 8

libdvdread: Attempting to retrieve all CSS keys
libdvdread: This can take a _long_ time, please be patient

libdvdread: Get key for /VIDEO_TS/VIDEO_TS.VOB at 0x00000130
libdvdread: Elapsed time 0

这让我很烦恼,因为我想检查这些文本文件以确保在编码过程中没有错误。

我猜这可能与写入相同流的线程之间缺乏同步有关,但我不确定。

问题:我该怎么做才能从PowerShell获取标准错误输出而不带这些随机换行符?


我已经为你的问题添加了一个v1标签。出于好奇:你为什么要(被迫)使用这样古老的版本工作? - mklement0
我习惯于使用cmd批处理文件。我认为我会转向PowerShell,因为我读到它更好。我仍在学习它,但我还没有看到版本之间的区别。 - z32a7ul
我明白了。虽然新版本保持向后兼容,但也修复了错误,增加了新功能和性能改进,因此选择最高版本绝对是值得的。v1几乎在“野外”中已经灭绝了,而v2(不幸地)仍然存在;当前版本是v5.1。请注意现在还有PowerShell_Core,这是基于.NET Core构建的跨平台版本(当前版本为v6.1.0),这是唯一会推出新功能的版本。 - mklement0
3个回答

1
我认为这里的问题在于控制台有一定的宽度,并且控制台本身基本上被重定向到文件中。
我的解决方案是直接将输出重定向到管道中,使用:
2>&1 #Interpreted by the console
2>&1 | x #Output directly to x

然后使用可用的-Width参数与Out-File一起使用:

$(throw thisisnotsometthingyoucanthrowbutisinfactaverylongmessagethatdemonstratesmypoint) 2>&1 |
 Out-File "test.txt" -Width 10000

在这种情况下,PowerShell会在换行文本之前写入10,000个字符。
但是,您还有一些奇怪的换行符,我现在无法复制。 话虽如此,既然您知道如何通过管道发送输出,就可以使用其他方法来删除换行符。
例如,您可以使用this function,它会打印出导致换行的确切控制字符。
$(throw error) 2>&1 | Out-String | Debug-String

然后,您可以浏览输出并替换问题字符,如下所示:
$(throw error) 2>&1 | Out-String | % {$_ -replace "`r"} | Out-File "test.txt" -Width 10000

1

Burt Harris' helpful answer展示了一种避免问题的方法,通过Start-Process,但是这需要你从根本上不同的方式来构建命令。

如果等效批处理文件产生的输出足够,有一个更简单的方法:只需调用cmd /c并让cmd处理输出重定向,就像在您的批处理文件中一样:

cmd /c "`"`"$Env:ProgramFiles\HandBrake\HandBrakeCLI.exe`"`"" @(
    '--input', 'V:\',
    '--title', '1', '--chapter', '1',
    '--start-at', 'duration:110', '--stop-at', 'duration:15',
    '--output', 'pmovie.mp4',
    '--format', 'av_mp4'
    ) '> .\pstd.txt 2> .\perr.txt'

请注意,这两个输出重定向被传递为一个带引号的字符串,以确保它们被cmd.exe而不是PowerShell解释。
还要注意嵌入的转义双引号(`")围绕可执行路径,以确保cmd.exe将整个路径视为单个双引号字符串。
关于你看到的额外换行符: 我没有具体的解释,但我可以告诉你PowerShell中的>2>是如何不同的——与cmd.exe(批处理文件)和带-RedirectStandard*参数的Start-Process相比:
  • cmd.exe的重定向操作符(>)将原始字节写入指定的目标文件,无论是重定向stdout(只有>或明确指定为1>),还是stderr(2>)。因此,外部程序(如HandBrakeCLI.exe)输出的文本会原样传递。

  • Start-Process在幕后使用.NET API时,在指定-RedirectStandardOutput和/或-RedirectStandardError参数时基本上执行相同的操作。

相比之下,PowerShell自己的 > 运算符的功能不同:

  • PowerShell - 在内部调用本机 PowerShell 命令时,它使用 PowerShell 丰富的输出格式系统将非字符串输入对象转换为字符串,然后将它们发送到输出文件(使用下面详细说明的字符编码)。

  • 从外部程序接收的输出被假定为文本,默认情况下假定其编码为系统的 OEM 字符编码,反映在 [console]::OutputEncodingchcp 中。将解码后的文本逐行加载到 .NET 字符串中(本质上是基于 UTF-16 的)。

    • 对于重定向的 stdout 输出,这些字符串在输出到目标文件时使用以下编码进行重新编码

      • Windows PowerShell:UTF-16LE(“Unicode”)
      • PowerShell Core:UTF-8 without BOM
      • 注意:只有在 Windows PowerShell v5.1 或更高版本和 PowerShell Core 中才能更改这些默认值 - 有关详细信息,请参见this answer
    • 相比之下,通过流2(PowerShell 的错误流)重定向 stderr 输出,这些字符串在输出之前被包装在错误对象中(类型实例[System.Management.Automation.ErrorRecord]),并且根据 PowerShell 的输出格式系统将生成的对象转换为字符串,并使用与上述相同的字符编码在输出到目标文件时应用。

      • 您可以从包含额外信息和行的输出中看到这一点,例如 HandBrakeCLI.exe:[10:41:44] hb_init:starting libhb thread在 C:\Test\phandbrake.ps1 中:1 字符:2...
      • 这也意味着可能会引入额外的换行符,因为由输出格式系统生成的文本假定基于控制台窗口的宽度有一个固定的行宽。
      • 尽管如此,这并不能解释你的情况下放置奇怪的换行符。

1
你可以尝试使用Start-Process命令,带有-RedirectStandardError-RedirectStandardInput-Wait选项。这些-Redirect...选项在Start-Process上执行操作系统级别的I/O重定向,就像大多数shell一样。据我所知,这不是PowerShell角括号重定向的工作方式,而是将输出通过另一个PowerShell管道进行管道传输,使用Write-File(或其他)插入接收到的字符串之间的换行符。我不确定具体细节,但很高兴听到它似乎像对我一样解决了你的问题。

谢谢,使用-RedirectStandardOutput和-RedirectStandardError让它工作了,尽管我不明白这与>和2>有何不同。 - z32a7ul
@z32a7ul,欢迎。我已经编辑了答案并添加了更多细节。 - Burt_Harris

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