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

Random linebreaks in PowerShell standard error output

我想使用 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 获取标准错误输出?

您可以尝试 Start-Process 命令,使用 -RedirectStandardError
-RedirectStandardInput-Wait 选项。

Start-Process 上的这些 -Redirect... 选项执行 OS 级别 I/O 直接重定向到目标文件,就像大多数 shell 所做的那样。据我了解,这不是 PowerShell 尖括号重定向的工作方式,而是尖括号使用 Write-File (或其他东西)通过另一个 PowerShell 管道传输输出,它在接收到的字符串之间插入换行符。

我不确定这方面的具体细节,但我很高兴听到它似乎解决了你的问题,就像它解决了我的问题一样。

我认为这里的问题是控制台有一定的宽度,控制台本身实际上是被重定向到一个文件。

我的解决方案是将输出直接重定向到管道,使用:

2>&1 #Interpreted by the console
2>&1 | x #Output directly to x

然后使用 Out-File 和可用的 -Width 参数:

$(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

通过 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'

请注意两个 输出重定向 如何作为单个带引号的字符串 传递 ,以确保它们被 [=14] 解释=] 而不是 PowerShell。

还要注意可执行文件路径周围嵌入的转义双引号 (`"),以确保 cmd.exe 将整个路径视为单个双引号字符串。


至于您看到的额外换行符: 我没有具体的解释,但我可以告诉你 >2> 在 PowerShell 中的工作方式不同 - 与 cmd.exe(批处理文件)相比Start-Process-RedirectStandard*:

  • cmd.exe 的重定向运算符 (>) 将原始字节写入指定的目标文件,无论是在重定向 stdout 时(只是 > 或者,明确地, 1>) 和标准错误 (2>);因此,HandBrakeCLI.exe 等外部程序输出的文本按原样传递。

  • Start-Process,它在幕后使用 .NET API,在 -RedirectStandardOutput and/or -RedirectStandardError 时的作用基本相同指定参数。

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

  • PowerShell-内部(当调用本机 PowerShell 命令时)它转换输入 objects(尚未strings) 到 strings 使用 PowerShell 丰富的输出格式化系统,然后使用下面详述的字符编码将它们发送到输出文件。

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

    • 对于重定向的stdout输出,这些字符串是re- encoded 输出到目标文件,默认使用如下编码:

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

      • 您可以在包含额外信息和行的输出中看到证据,例如 HandBrakeCLI.exe : [10:41:44] hb_init: starting libhb threadAt C:\Test\phandbrake.ps1:1 char:2, ...
      • 这也意味着可以引入额外的换行符,因为输出格式系统生成的文本假定基于控制台 window 宽度的固定线宽。
      • 也就是说,这并不能解释您的案例中奇怪的换行符。