ChatGPT解决这个技术问题 Extra ChatGPT

使用 PowerShell 在没有 BOM 的情况下以 UTF-8 编写文件

Out-File 似乎在使用 UTF-8 时强制使用 BOM:

$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath

如何使用 PowerShell 以 UTF-8 编写没有 BOM 的文件?

2021 年更新

自从我 10 年前写这个问题以来,PowerShell 发生了一些变化。检查下面的多个答案,他们有很多很好的信息!

BOM = 字节顺序标记。位于文件开头的三个字符 (0xEF,0xBB,0xBF),看起来像“”
这令人难以置信的沮丧。甚至第三方模块也会被污染,比如尝试通过 SSH 上传文件?砰! “是的,让我们破坏每一个文件;这听起来是个好主意。” -微软。
从 Powershell 版本 6.0 docs.microsoft.com/en-us/powershell/module/… 开始,默认编码为 UTF8NoBOM
谈论打破向后兼容性......
我觉得应该注意的是,虽然 UTF-8 文件中的 BOM 确实会使很多系统阻塞,但 it is explicitly valid in the Unicode UTF-8 spec to include one

X
XDS

使用 .NET 的 UTF8Encoding 类并将 $False 传递给构造函数似乎可行:

$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)

呃,我希望这不是唯一的方法。
一行 [System.IO.File]::WriteAllLines($MyPath, $MyFile) 就足够了。此 WriteAllLines 重载完全写入没有 BOM 的 UTF8。
请注意,WriteAllLines 似乎要求 $MyPath 是绝对的。
@xdhmoore WriteAllLines[System.Environment]::CurrentDirectory 获取当前目录。如果您打开 PowerShell,然后更改当前目录(使用 cdSet-Location),那么 [System.Environment]::CurrentDirectory 将不会更改,并且文件最终会位于错误的目录中。您可以通过 [System.Environment]::CurrentDirectory = (Get-Location).Path 解决此问题。
C
Community

目前正确的方法是使用@Roman Kuzmin in comments 向@M 推荐的解决方案。达力answer

[IO.File]::WriteAllLines($filename, $content)

(我还通过去除不必要的 System 命名空间说明将其缩短了一点 - 默认情况下会自动替换它。)


这(无论出于何种原因)并没有为我删除 BOM,而接受的答案确实如此
@Liam,可能是一些旧版本的 PowerShell 或 .NET?
我相信旧版本的 .NET WriteAllLines 函数在默认情况下确实编写了 BOM。所以可能是版本问题。
确认在 Powershell 3 中使用 BOM 写入,但在 Powershell 4 中没有 BOM。我不得不使用 M. Dudley 的原始答案。
因此它适用于默认安装的 Windows 10。 :) 另外,建议改进:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
L
Lenny

我认为这不会是 UTF,但我刚刚找到了一个似乎可行的非常简单的解决方案......

Get-Content path/to/file.ext | out-file -encoding ASCII targetFile.ext

对我来说,无论源格式如何,这都会导致没有 bom 文件的 utf-8。


这对我有用,除了我使用 -encoding utf8 来满足我的要求。
非常感谢。我正在使用一个工具的转储日志 - 其中有标签。 UTF-8 不工作。 ASCII 解决了这个问题。谢谢。
是的,-Encoding ASCII 避免了 BOM 问题,但您显然只能得到 7 位 ASCII 字符。鉴于 ASCII 是 UTF-8 的子集,因此生成的文件在技术上也是有效的 UTF-8 文件,但您输入中的所有非 ASCII 字符都将转换为文字 ? 字符
警告:绝对不是。这将删除所有非 ASCII 字符并用问号替换它们。不要这样做,否则您将丢失数据! (在 Windows 10 上使用 PS 5.1 进行了尝试)
m
mklement0

注意:此答案适用于 Windows PowerShell;相比之下,在跨平台 PowerShell Core 版本 (v6+) 中,不带 BOM 的 UTF-8 是所有 cmdlet 的默认编码。

换句话说:如果您使用的是 PowerShell [Core] 版本 6 或更高版本,则默认情况下会获得无 BOM 的 UTF-8 文件(您也可以使用 -Encoding utf8 / -Encoding utf8NoBOM 显式请求,而您可以使用 -使用 -utf8BOM 进行 BOM 编码)。

如果您正在运行 Windows 10 并且您愿意在系统范围内切换到无 BOM 的 UTF-8 编码 - 这可能会产生副作用 - 甚至可以使 Windows PowerShell 始终使用无 BOM 的 UTF-8 - 请参阅此回答。

补充 M. Dudley's own simple and pragmatic answer(和 ForNeVeR's more concise reformulation):

为方便起见,这里是高级函数 Out-FileUtf8NoBom一种模仿 Out-File 的基于管道的替代方法,这意味着:

您可以像管道中的 Out-File 一样使用它。

不是字符串的输入对象的格式与将它们发送到控制台时一样,就像使用 Out-File 一样。

额外的 -UseLF 开关允许您将 Windows 样式的 CRLF 换行符转换为 Unix 样式的 LF-only 换行符。

例子:

(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath # Add -UseLF for Unix newlines

请注意 (Get-Content $MyPath) 是如何包含在 (...) 中的,它确保在通过管道发送结果之前打开、完整读取和关闭整个文件。这是必要的,以便能够写回相同文件(更新它就地)。
但一般来说,这种技术不建议用于 2原因:(a)整个文件必须适合内存,(b)如果命令中断,数据将丢失。

关于内存使用的说明:

M. Dudley 自己的回答要求首先在内存中构建整个文件内容,这对于大文件可能会产生问题。

下面的函数只是稍微改进了一点:所有输入对象仍然首先被缓冲,但是它们的字符串表示然后被生成并一个一个地写入输出文件。

函数Out-FileUtf8NoBom的源代码

注意:该功能也可用as an MIT-licensed Gist,并且只会在以后保持。

您可以使用以下命令直接安装它(虽然我个人可以向您保证这样做是安全的,但在直接以这种方式执行之前,您应该始终检查脚本的内容):

# Download and define the function.
irm https://gist.github.com/mklement0/8689b9b5123a9ba11df7214f82a673be/raw/Out-FileUtf8NoBom.ps1 | iex
function Out-FileUtf8NoBom {
<#
.SYNOPSIS
  Outputs to a UTF-8-encoded file *without a BOM* (byte-order mark).
.DESCRIPTION
  Mimics the most important aspects of Out-File:
    * Input objects are sent to Out-String first.
    * -Append allows you to append to an existing file, -NoClobber prevents
      overwriting of an existing file.
    * -Width allows you to specify the line width for the text representations
       of input objects that aren't strings.
  However, it is not a complete implementation of all Out-File parameters:
    * Only a literal output path is supported, and only as a parameter.
    * -Force is not supported.
    * Conversely, an extra -UseLF switch is supported for using LF-only newlines.
  Caveat: *All* pipeline input is buffered before writing output starts,
          but the string representations are generated and written to the target
          file one by one.
.NOTES
  The raison d'être for this advanced function is that Windows PowerShell
  lacks the ability to write UTF-8 files without a BOM: using -Encoding UTF8 
  invariably prepends a BOM.
  Copyright (c) 2017, 2020 Michael Klement <mklement0@gmail.com> (http://same2u.net), 
  released under the [MIT license](https://spdx.org/licenses/MIT#licenseText).
#>

  [CmdletBinding()]
  param(
    [Parameter(Mandatory, Position=0)] [string] $LiteralPath,
    [switch] $Append,
    [switch] $NoClobber,
    [AllowNull()] [int] $Width,
    [switch] $UseLF,
    [Parameter(ValueFromPipeline)] $InputObject
  )

  #requires -version 3

  # Convert the input path to a full one, since .NET's working dir. usually
  # differs from PowerShell's.
  $dir = Split-Path -LiteralPath $LiteralPath
  if ($dir) { $dir = Convert-Path -ErrorAction Stop -LiteralPath $dir } else { $dir = $pwd.ProviderPath}
  $LiteralPath = [IO.Path]::Combine($dir, [IO.Path]::GetFileName($LiteralPath))

  # If -NoClobber was specified, throw an exception if the target file already
  # exists.
  if ($NoClobber -and (Test-Path $LiteralPath)) {
    Throw [IO.IOException] "The file '$LiteralPath' already exists."
  }

  # Create a StreamWriter object.
  # Note that we take advantage of the fact that the StreamWriter class by default:
  # - uses UTF-8 encoding
  # - without a BOM.
  $sw = New-Object System.IO.StreamWriter $LiteralPath, $Append

  $htOutStringArgs = @{}
  if ($Width) {
    $htOutStringArgs += @{ Width = $Width }
  }

  # Note: By not using begin / process / end blocks, we're effectively running
  #       in the end block, which means that all pipeline input has already
  #       been collected in automatic variable $Input.
  #       We must use this approach, because using | Out-String individually
  #       in each iteration of a process block would format each input object
  #       with an indvidual header.
  try {
    $Input | Out-String -Stream @htOutStringArgs | % { 
      if ($UseLf) {
        $sw.Write($_ + "`n") 
      }
      else {
        $sw.WriteLine($_) 
      }
    }
  } finally {
    $sw.Dispose()
  }

}

u
user2864740

版本 6 开始,powershell 支持 set-contentout-fileUTF8NoBOM 编码,甚至将其用作默认编码。

所以在上面的例子中,它应该是这样的:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath

好的。仅供参考用 $PSVersionTable.PSVersion 检查版本
值得注意的是,在 PowerShell [Core] v6+ 中,-Encoding UTF8NoBOM 从来不是必需,因为它是默认 编码。
L
Lucero

当使用 Set-Content 而不是 Out-File 时,您可以指定编码 Byte,它可用于将字节数组写入文件。这与不发出 BOM 的自定义 UTF8 编码相结合,可提供所需的结果:

# This variable can be reused
$utf8 = New-Object System.Text.UTF8Encoding $false

$MyFile = Get-Content $MyPath -Raw
Set-Content -Value $utf8.GetBytes($MyFile) -Encoding Byte -Path $MyPath

使用 [IO.File]::WriteAllLines() 或类似的区别在于它应该适用于任何类型的项目和路径,而不仅仅是实际的文件路径。


很好 - 与字符串配合使用效果很好(这可能是所需要的,并且肯定符合问题的要求)。如果您需要利用 Out-File(与 Set-Content 不同)提供的格式,请先通过管道传输到 Out-String;例如,$MyFile = Get-ChildItem | Out-String
j
jamhan

此脚本会将 DIRECTORY1 中的所有 .txt 文件转换为没有 BOM 的 UTF-8 并将它们输出到 DIRECTORY2

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}

这个失败了,没有任何警告。我应该使用哪个版本的 powershell 来运行它?
WriteAllLines 解决方案非常适用于小文件。但是,我需要一个更大文件的解决方案。每次我尝试将它与更大的文件一起使用时,我都会收到 OutOfMemory 错误。
A
Andreas Covidiot

重要!:这仅适用于文件开头的额外空格或换行符对您的文件用例没有问题(例如,如果它是 SQL 文件、Java 文件或人类可读的文本文件)

可以使用创建空(非 UTF8 或 ASCII(UTF8 兼容))文件并附加到它的组合(如果源是文件,则将 $str 替换为 gc $src):

" "    |  out-file  -encoding ASCII  -noNewline  $dest
$str  |  out-file  -encoding UTF8   -append     $dest

作为单线

根据您的用例替换 $dest$str

$_ofdst = $dest ; " " | out-file -encoding ASCII -noNewline $_ofdst ; $src | out-file -encoding UTF8 -append $_ofdst

作为简单的功能

function Out-File-UTF8-noBOM { param( $str, $dest )
  " "    |  out-file  -encoding ASCII  -noNewline  $dest
  $str  |  out-file  -encoding UTF8   -append     $dest
}

将它与源文件一起使用:

Out-File-UTF8-noBOM  (gc $src),  $dest

将它与字符串一起使用:

Out-File-UTF8-noBOM  $str,  $dest

可选:继续附加 Out-File: "more foo bar" | Out-File -encoding UTF8 -append $dest


J
JensG

老问题,新答案:

虽然“旧”powershell 编写 BOM,但与平台无关的新变体的行为确实有所不同:默认为“无 BOM”,可以通过开关进行配置:

-Encoding 指定目标文件的编码类型。默认值为 utf8NoBOM。此参数可接受的值如下: ascii:使用 ASCII(7 位)字符集的编码。 bigendianunicode:使用大端字节序以 UTF-16 格式编码。 oem:使用 MS-DOS 和控制台程序的默认编码。 unicode:使用 little-endian 字节顺序以 UTF-16 格式编码。 utf7:以 UTF-7 格式编码。 utf8:以 UTF-8 格式编码。 utf8BOM:以 UTF-8 格式编码,带字节顺序标记 (BOM) utf8NoBOM:以 UTF-8 格式编码,不带字节顺序标记 (BOM) utf32:以 UTF-32 格式编码。

来源:https://docs.microsoft.com/de-de/powershell/module/Microsoft.PowerShell.Utility/Out-File?view=powershell-7 强调我的


Z
Zombo

对于 PowerShell 5.1,启用此设置:

控制面板、区域、管理、更改系统区域设置、使用 Unicode UTF-8 获得全球语言支持

然后在 PowerShell 中输入:

$PSDefaultParameterValues['*:Encoding'] = 'Default'

或者,您可以升级到 PowerShell 6 或更高版本。

https://github.com/PowerShell/PowerShell


详细说明:这是一个系统范围设置,它使 Windows PowerShell默认为无 BOM 的 UTF-8所有 cmdlet,这可能需要也可能不需要,尤其是因为该功能仍处于测试阶段(在撰写本文时)并且可能会破坏旧的控制台应用程序 - 有关背景信息,请参阅 this answer
J
Jaume Suñer Mut

将多个文件通过扩展名更改为 UTF-8 而不使用 BOM:

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach($i in ls -recurse -filter "*.java") {
    $MyFile = Get-Content $i.fullname 
    [System.IO.File]::WriteAllLines($i.fullname, $MyFile, $Utf8NoBomEncoding)
}

f
frank tan
    [System.IO.FileInfo] $file = Get-Item -Path $FilePath 
    $sequenceBOM = New-Object System.Byte[] 3 
    $reader = $file.OpenRead() 
    $bytesRead = $reader.Read($sequenceBOM, 0, 3) 
    $reader.Dispose() 
    #A UTF-8+BOM string will start with the three following bytes. Hex: 0xEF0xBB0xBF, Decimal: 239 187 191 
    if ($bytesRead -eq 3 -and $sequenceBOM[0] -eq 239 -and $sequenceBOM[1] -eq 187 -and $sequenceBOM[2] -eq 191) 
    { 
        $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False) 
        [System.IO.File]::WriteAllLines($FilePath, (Get-Content $FilePath), $utf8NoBomEncoding) 
        Write-Host "Remove UTF-8 BOM successfully" 
    } 
    Else 
    { 
        Write-Warning "Not UTF-8 BOM file" 
    }  

来源How to remove UTF8 Byte Order Mark (BOM) from a file using PowerShell


S
SATO Yusuke

如果要使用 [System.IO.File]::WriteAllLines(),则应将第二个参数强制转换为 String[](如果 $MyFile 的类型为 Object[]),并使用 $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath) 指定绝对路径,例如:

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
Get-ChildItem | ConvertTo-Csv | Set-Variable MyFile
[System.IO.File]::WriteAllLines($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath), [String[]]$MyFile, $Utf8NoBomEncoding)

如果您想使用 [System.IO.File]::WriteAllText(),有时您应该将第二个参数通过管道传递到 | Out-String | 以显式地将 CRLF 添加到每行的末尾(特别是当您将它们与 ConvertTo-Csv 一起使用时):

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
Get-ChildItem | ConvertTo-Csv | Out-String | Set-Variable tmp
[System.IO.File]::WriteAllText("/absolute/path/to/foobar.csv", $tmp, $Utf8NoBomEncoding)

或者您可以将 [Text.Encoding]::UTF8.GetBytes()Set-Content -Encoding Byte 一起使用:

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
Get-ChildItem | ConvertTo-Csv | Out-String | % { [Text.Encoding]::UTF8.GetBytes($_) } | Set-Content -Encoding Byte -Path "/absolute/path/to/foobar.csv"

见:How to write result of ConvertTo-Csv to a file in UTF-8 without BOM


好的指点;建议/:$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath) 的更简单替代方案是 Convert-Path $MyPath;如果您想确保尾随 CRLF,只需使用 [System.IO.File]::WriteAllLines(),即使是 single 输入字符串(不需要 Out-String)。
N
Nader Gharibian Fard

我在 PowerShell 中遇到了同样的错误,并使用了这种隔离并修复了它

$PSDefaultParameterValues['*:Encoding'] = 'utf8'

T
Tanmay Sarin

使用此方法编辑 UTF8-NoBOM 文件并生成具有正确编码的文件-

$fileD = "file.xml"
(Get-Content $fileD) | ForEach-Object { $_ -replace 'replace text',"new text" } | out-file "file.xml" -encoding ASCII

起初我对这种方法持怀疑态度,但它让我感到惊讶并且奏效了!

使用 powershell 5.1 版测试


P
Pravanjan Hota

我想说只使用 Set-Content 命令,不需要其他任何东西。

我系统中的 powershell 版本是:-

PS C:\Users\XXXXX> $PSVersionTable.PSVersion | fl


Major         : 5
Minor         : 1
Build         : 19041
Revision      : 1682
MajorRevision : 0
MinorRevision : 1682

PS C:\Users\XXXXX>

所以你需要像下面这样的东西。

PS C:\Users\XXXXX> Get-Content .\Downloads\finddate.txt
Thursday, June 23, 2022 5:57:59 PM
PS C:\Users\XXXXX> Get-Content .\Downloads\finddate.txt | Set-Content .\Downloads\anotherfile.txt
PS C:\Users\XXXXX> Get-Content .\Downloads\anotherfile.txt
Thursday, June 23, 2022 5:57:59 PM
PS C:\Users\XXXXX>

现在,当我们根据屏幕截图检查文件时,它是 utf8。 anotherfile.txt


E
Erik Anderson

我使用的一种技术是使用 Out-File cmdlet 将输出重定向到 ASCII 文件。

例如,我经常运行 SQL 脚本来创建另一个 SQL 脚本以在 Oracle 中执行。使用简单重定向 (">"),输出将采用 SQLPlus 无法识别的 UTF-16。要解决此问题:

sqlplus -s / as sysdba "@create_sql_script.sql" |
Out-File -FilePath new_script.sql -Encoding ASCII -Force

然后可以通过另一个 SQLPlus 会话执行生成的脚本,而无需担心 Unicode:

sqlplus / as sysdba "@new_script.sql" |
tee new_script.log

更新:正如其他人所指出的,这将丢弃非 ASCII 字符。由于用户要求一种“强制”转换的方法,我认为他们并不关心这一点,因为他们的数据可能不包含此类数据。

如果您关心非 ASCII 字符的保存,那么这不是您的答案。


是的,-Encoding ASCII 避免了 BOM 问题,但您显然只能获得对 7 位 ASCII 字符的支持。鉴于 ASCII 是 UTF-8 的子集,因此生成的文件在技术上也是有效的 UTF-8 文件,但您输入中的所有非 ASCII 字符都将转换为文字 ? 字符
这个答案需要更多的选票。 sqlplus 与 BOM 不兼容是导致 many headaches 的原因。
@AmitNaidu 不,这是错误的答案,因为如果文本有任何非 ascii 字符,它将不起作用:任何重音、变音符号、东方/克里尔等。
@JoelCoehoorn 根据用户的要求,这是一个正确的答案。由于用户要求一种“强制”的方法,因此他们预计不会出现任何问题或不在乎,可能是因为源不使用任何非 ASCII 字符。对于那些关心保存这些字符的人来说,这是行不通的。
R
Robin Wang

可以在下面使用来获得没有 BOM 的 UTF8

$MyFile | Out-File -Encoding ASCII

不,它会将输出转换为当前的 ANSI 代码页(例如 cp1251 或 cp1252)。它根本不是 UTF-8!
谢谢罗宾。这可能不适用于在没有 BOM 的情况下编写 UTF-8 文件,但 -Encoding ASCII 选项删除了 BOM。这样我就可以为 gvim 生成一个 bat 文件。 .bat 文件在 BOM 上出错了。
@ForNeVeR:编码 ASCII 不是 UTF-8 是正确的,但它也不是当前的 ANSI 代码页 - 您正在考虑 DefaultASCII 确实是 7 位 ASCII 编码,代码点 >= 128 被转换为文字 ? 实例。
@ForNeVeR:您可能正在考虑“ANSI”或“extended ASCII”。试试这个来验证 -Encoding ASCII 确实只是 7 位 ASCII:'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f) - ä 已被音译为 ?。相比之下,-Encoding Default ("ANSI") 会正确地保留它。
@rob 对于不需要 utf-8 或其他任何与 ASCII 不同并且对理解编码和 unicode 目的不感兴趣的人来说,这是一个完美的答案。您可以将其用作 utf-8,因为与所有 ASCII 字符等效的 utf-8 字符是相同的(意味着将 ASCII 文件转换为 utf-8 文件会产生相同的文件(如果它没有 BOM))。对于所有文本中包含非 ASCII 字符的人来说,这个答案只是错误的和误导性的。
K
Krzysztof

这个对我有用(使用“默认”而不是“UTF8”):

$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath

结果是没有 BOM 的 ASCII。


根据 Out-File documentation,指定 Default 编码将使用系统当前的 ANSI 代码页,它不是 UTF-8,正如我所要求的那样。
这似乎对我有用,至少对于 Export-CSV。如果您在适当的编辑器中打开生成的文件,文件编码是没有 BOM 的 UTF-8,而不是我对 ASCII 所期望的 Western Latin ISO 9
如果无法检测到编码,许多编辑器会以 UTF-8 格式打开文件。