【问题标题】:How can I redirect input in PowerShell without a BOM?如何在没有 BOM 的情况下重定向 PowerShell 中的输入?
【发布时间】:2020-05-24 05:46:24
【问题描述】:

我正在尝试通过以下方式重定向 PowerShell 中的输入:

Get-Content input.txt | my-program args

问题是管道 UTF-8 文本前面带有 BOM (0xEFBBBF),我的程序无法正确处理。

一个最小的工作示例:

// File: Hex.java
import java.io.IOException;

public class Hex {
    public static void main(String[] dummy) {
        int ch;
        try {
            while ((ch = System.in.read()) != -1) {
                System.out.print(String.format("%02X ", ch));
            }
        } catch (IOException e) {
        }
    }
}

然后在 PowerShell 中:

javac Hex.java
Set-Content textfile "ABC" -Encoding Ascii
# Now the content of textfile is 0x41 42 43 0D 0A
Get-Content textfile | java Hex

或者干脆

javac Hex.java
Write-Output "ABC" | java Hex

无论哪种情况,输出都是EF BB BF 41 42 43 0D 0A

如何在没有 0xEFBBBF 的情况下将文本通过管道传输到程序中?

【问题讨论】:

  • 如果将其显式转换为字符串会发生什么? [string](Get-Content input.txt) | my-program args
  • @GuentherSchmitz BOM 仍然在这里,但是除了最后一个换行符之外的所有换行符都转换为空格。顺便说一句,虽然我的 input.txt 使用了 LF,但是通过管道传递的内容在这两种情况下都使用了 CR LF。
  • (Get-Content input.txt) | 将单个字符串(带有换行符)传递给管道,Get-Content input.txt | 将多个字符串传递给管道(其中每个字符串代表一行)之间存在很大差异。请注意,如果您将其传递给变量 (String[]),它可能会被空格或换行符分隔,具体取决于您的显示方式。另请注意,对于后面的语法,您的my-program 需要能够process 管道中的每个单独项目。鉴于您问题中的详细信息,我怀疑您的程序是否真的在这样做。
  • 你在 powershell 控制台,而不是 ise 或 vscode 或 windows 终端?
  • @user 当 PowerShell 输出到外部程序(大多数 .exe 文件)时,$OutputEncoding 用于确定编码。您可以在执行命令之前尝试$OutputEncoding = [System.Text.UTF8Encoding]::new($false)

标签: powershell encoding pipe byte-order-mark


【解决方案1】:

您可以尝试在没有 BOM 的情况下将 OutputEncoding 设置为 UTF-8:

# Keep the current output encoding in a variable
$oldEncoding = [console]::OutputEncoding

# Set the output encoding to use UTF8 without BOM
[console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false

Get-Content input.txt | my-program args

# Reset the output encoding to the previous
[console]::OutputEncoding = $oldEncoding

如果以上没有效果,并且您的程序确实理解 UTF-8,但只希望它没有 3 字节 BOM,那么您可以尝试从内容中删除 BOM 并将结果传递给您的程序

(Get-Content 'input.txt' -Raw -Encoding UTF8) -replace '^\xef\xbb\xbf' |  my-program args

如果您曾经使用 chcp 65001“入侵”过代码页,我建议您将其转回 chcp 5129 以获取英语 - 新西兰。见here

【讨论】:

  • @user 请尝试我编辑的替代方案。由于我们不知道您的 my-program 并且不知道参数 args 的作用,因此很难理解那里发生了什么..
  • 也不起作用。我不认为我的程序有问题,因为我写了一个小程序,它只从标准输入打印每个字节的十六进制值,然后发现它首先打印 efbbbf。
  • @user 然后我建议您编辑该程序并让它检测是否存在 BOM,如果存在则跳过这些字节。然后,您还可以包含其他编码字节顺序标记:\x00\x00\xfe\xff UTF-32 big-endian /// \xff\xfe\x00\x00 UTF-32, little-endian /// \xfe\xff UTF-16, big-endian /// \xff\xfe UTF-16, little-endian /// \xef\xbb\xbf UTF-8。我会说这将成为一个更有用的实用程序,因为现在它只能处理没有 BOM 的 UTF8 ..
  • +1 了解有关从 PowerShell 中更改编码的信息。它显然不能解决这个问题,但它可能是这个问题的最佳答案:How to store directional arrow character in a variable
  • @iRon 谢谢。我之前没有看到这个问题,但我相信这个问题的答案是使用输出编码。 (无法测试,因为我没有 tshark.exe)
【解决方案2】:

注意:
以下包含在正常运行的 PowerShell 环境中解释 OP 症状的一般信息。该解决方案在 OP 的情况下不起作用是由于目前未知的特定于机器的原因。
这个答案是关于发送无 BOM 的 UTF-8 外部程序;如果您想让您的 PowerShell 控制台窗口在所有方面使用 UTF-8,请参阅this answer

为确保您的 Java 程序接收其输入的 UTF-8 编码没有 BOM您必须将 $OutputEncoding 设置为 System.Text.UTF8Encoding 实例,该实例不 发出一个 BOM

# Assigns UTF-8 encoding *without a BOM*.
# PowerShell uses this encoding to encode data piped to external programs.
# $OutputEncoding defaults to ASCII(!) in Windows PowerShell, and more sensibly
# to BOM-*less* UTF-8 in PowerShell [Core] v6+
$OutputEncoding = [Text.UTF8Encoding]::new($false)

注意事项

  • 不要使用看似等效的New-Object Text.Utf8Encoding $false,因为由于this GitHub issue 中描述的错误,如果您在非全局范围内分配给$OutpuEncoding,它将不起作用,例如在脚本中。在 PowerShell v4 及更低版本中,使用
    (New-Object Text.Utf8Encoding $false).psobject.BaseObject 作为解决方法。

  • Windows 10 版本 1903 及更高版本允许您将无 BOM 的 UTF-8 设置为系统范围的默认编码(尽管请注意从版本 20H2 开始,该功能仍被归类为 beta) - 请参阅 this answer; [在 PowerShell 7.1 中修复] 在 PowerShell [Core] 到 v7.0 中,启用此功能后,上述技术无效,因为一个假定的 .NET Core 错误会导致 UTF-8 BOM 总是被发射,不管您将$OutputEncoding 设置为什么编码(该错误可能与this GitHub issue 有关); 唯一的解决办法是关闭该功能,如imgx64's answer所示。

相比之下,如果您使用 [Text.Encoding]::Utf8,您将获得一个带有 BOM 的 System.Text.Encoding.UTF8 实例 - 我怀疑这就是您的情况。


请注意,此问题与Get-Content 读取的任何文件 的源编码无关,因为通过 PowerShell 管道发送的内容绝不是原始字节流,但 .NET 对象,在Get-Content 的情况下意味着发送.NET 字符串System.String,内部是UTF-16 代码单元序列)。

因为您要传送到 外部程序(在您的情况下是 Java 应用程序),PowerShell 字符编码发送给它的(按需字符串化)对象基于preference variable $OutputEncoding,生成的编码就是外部程序接收的内容。

也许令人惊讶的是,即使 BOM 通常只在文件中使用,PowerShell 也尊重分配给$OutputEncoding 的编码的 BOM 设置,也在管道中 , 将其添加到发送的第一行(仅)。

请参阅this answer 的底部部分,了解有关 PowerShell 如何处理外部程序的管道输入和输出的更多信息,包括 当 PowerShell 解释接收到的数据时,[Console]::OutputEncoding 的重要性 em> 外部程序


使用您的示例程序来说明差异(注意如何使用 PowerShell 字符串文字作为输入就足够了;无需从文件中读取):

# Note the EF BB BF sequence representing the UTF-8 BOM.
# Enclosure in & { ... } ensures that a local, temporary copy of $OutputEncoding
# is used.
PS> & { $OutputEncoding = [Text.Encoding]::Utf8; 'hö' | java Hex }
EF BB BF 68 C3 B6 0D 0A

# Note the absence of EF BB BF, due to using a BOM-less
# UTF-8 encoding.
PS> & { $OutputEncoding = [Text.Utf8Encoding]::new($false); 'hö' | java Hex }
68 C3 B6 0D 0A

Windows PowerShell 中,$OutputEncoding 默认为 ASCII(!),您会看到以下默认设置:

# The default of ASCII(!) results in *lossy* encoding in Windows PowerShell.
PS> 'hö' | java Hex 
68 3F 0D 0A

注意3F 代表文字? 字符,这也是非ASCII 字符ö 的音译,因为它没有ASCII 表示;换句话说:信息丢失了

PowerShell [Core] v6+ 现在明智地默认为无 BOM 的 UTF-8,因此默认行为符合预期。
虽然无 BOM 的 UTF-8 是 PowerShell [Core] 的 一致 默认值,但对于读取和写入文件的 cmdlet,在 Windows[Console]::OutputEncoding 仍然反映从 v7.0 开始,默认情况下活动的 OEM 代码页,因此要正确捕获来自 UTF-8 发射外部程序的输出,它也必须设置为 [Text.UTF8Encoding]::new($false) - 请参阅 this GitHub issue

【讨论】:

  • 我也无法在虚拟机(Windows 10 build 1909)中重现。可能我这边出了点问题。不管怎样,谢谢你。仅供参考,它是 Windows 10 build 19559,Powershell 5.1.19559.1000。该问题发生在 PS 控制台、vscode 控制台和 ISE 中,但不适用于 Powershell v6+。
  • 谢谢,@user。有趣的症状;如果您找到原因,请告诉我们。
【解决方案3】:

虽然mklement0's answer 在一台电脑上为我工作,但在另一台电脑上却不工作。

原因是我在LanguageAdministrative language settingsChange system locale 中选中了Beta: Use Unicode UTF-8 for worldwide language support 复选框。

我取消选中它,现在 $OutputEncoding = [Text.UTF8Encoding]::new($false) 可以正常工作。

启用它会强制 BOM 很奇怪,但我想它是 beta 版是有原因的。

【讨论】:

    猜你喜欢
    • 2011-09-28
    • 2013-11-15
    • 2020-08-23
    • 1970-01-01
    • 2017-05-25
    • 1970-01-01
    • 2015-10-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多