问题描述
我正在尝试将命令的 stdout 和 stderr 输出存储到两个单独的文件中。我是这样做的:
powershell.exe @_cmd 2>"stderr.txt" >"stdout.txt"
其中 $_cmd
是任意字符串命令。
这有效,但输出文件在输出后附加了换行符。我想修改它以消除换行符。我知道您可以使用 cmd | Out-File ... -NoNewline
或 [System.IO.File]::WriteallText(...,[System.Text.Encoding]::ASCII)
,但我不确定如何使用 stderr 输出完成此操作。
编辑:我意识到问题不在于尾随的新行(尽管我仍然想删除它),而是我需要输出文件为 UTF- 8 编码。尾随的新行显然不是有效的 UTF-8 字符,这让我感到悲伤。也许有一种方法可以捕获stderr 和stdout 来分隔变量,然后使用Out-File -Encoding utf8
?
解决方法
您自己的基于 Start-Process
的 solution 使用 -RedirectStandardOutput
和 -RedirectStandardError
确实创建了 ( BOM-less) UTF-8 编码的输出文件,但请注意,它们也总是有一个尾随换行符。
但是,你不需要需要Start-Process
,因为你可以让PowerShell的redirection operator,>
产生UTF -8 个文件(也带有换行符)。
以下示例使用一个示例 cmd.exe
调用,该调用生成 stdout 和 stderr 输出。
-
在 PowerShell (Core) v6+ 中,不需要额外的努力,因为
>
生成(无 BOM)UTF- 8 个文件 默认(一致使用的默认值;如果您想要带有 BOM 的 UTF-8 ,您可以使用下面针对 Windows PowerShell 详细介绍的技术,但值为'utf8bom'
):cmd /c 'echo hü & dir c:\nosuch' 2>stderr.txt >stdout.txt
-
在 Windows PowerShell 中,
>
默认生成 UTF-16LE(“Unicode”),但 在 5.1 版中,您可以(暂时)使用 UTF-8 重新配置它,尽管总是带有 BOM;详情见this answer;另一个警告是文件中捕获的第一 stderr 行将被“嘈杂地”格式化,就像 PowerShell 错误:# Windows PowerShell v5.1: # Make `>` and its effective alias,Out-File,use UTF-8 with a BOM in the # remainder of the session. # Save and restore any previous value if you want to scope the behavior # to select commands only. $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' cmd /c 'echo hü & dir c:\nosuch' 2>stderr.txt >stdout.txt
警告:
-
每当 PowerShell 处理外部程序的输出时,它总是先解码为 .NET 字符串。假定任何外部程序都根据存储在
[Console]::OutputEncoding
中的字符编码生成输出,默认为系统的活动 OEM 代码页。这与cmd.exe
的预期一致,但还有其他控制台应用程序使用不同的编码 - 特别是node.exe
(Node.js) 和python
,它们使用 UTF-8 和系统的活动 ANSI 代码页,在这种情况下,[Console]::OutputEncoding
必须首先设置为该编码;有关详细信息,请参阅 this answer。
至于您的陈述和问题:
结尾的新行显然不是有效的 UTF-8 字符
PowerShell 的 >
运算符和文件输出 cmdlet 一致应用它们的字符编码,因此尾随换行符的编码始终与文件中其他字符的编码一致。
很可能是 Windows PowerShell 默认使用的 UTF-16LE(“Unicode”)编码才是真正的问题,您可能只注意到了换行符。
也许有一种方法可以捕获 stderr 和 stdout 以分离变量
Stdout 可以通过一个简单的变量赋值来捕获,它将多个输出行捕获为一个数组字符串:
$stdout = cmd /c 'echo hü & dir c:\nosuch'
你不能单独捕获stderr输出,但是你可以将stderr合并到带有2>&1
的stdout中,甚至更晚再次将流各自的输出行分开,基于它们的数据类型:stdout 行总是字符串,而 stderr 行总是 [ErrorRecord]
实例:
# Note the 2>&1 redirection.
$stdoutAndErr = cmd /c 'echo hü & dir c:\nosuch' 2>&1
# If desired,you can split the captured output into stdout and stderr output.
# The [string[]] cast converts the [ErrorRecord] instances to strings too.
$stdout,[string[]] $stderr = $stdoutAndErr.Where({ $_ -is [string] },'Split')
# Now $stdout is the array of stdout lines,and $stderr the array of stderr lines.
# If desired,you could write them to files *without a trailing newline* as follows:
$stdout -join [Environment]::NewLine | Set-Content -NoNewLine -Encoding utf8 stdout.txt
$stderr -join [Environment]::NewLine | Set-Content -NoNewLine -Encoding utf8 stderr.txt
您还可以将这些技术应用于 PowerShell 原生 命令(您甚至可以将 PowerShell 支持的所有其他流合并到成功输出流,PowerShell 类似于标准输出,带有 *>&1
)。
但是,如果给定的 PowerShell 原生命令是 cmdlet / 高级 脚本或函数,更方便的替代方法 是使用 common -OutVariable
parameter(用于成功流输出)和 common -ErrorVariable
parameter(用于错误流输出)。
@TheMadTechnician 的评论给出了有效的答案。
$process = Start-Process powershell.exe -ArgumentList "$_cmd" -Wait -PassThru -NoNewWindow -RedirectStandardError "stderr.txt" -RedirectStandardOutput "stdout.txt"
$exitcode = $process.ExitCode