使用PowerShell在不带BOM的UTF-8格式下编写文件

371

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

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

如何使用PowerShell以UTF-8无BOM格式编写文件?

2021年更新

自我10年前提出此问题以来,PowerShell有了一些变化。请检查下面的多个答案,它们包含很多有用的信息!


35
BOM代表字节顺序标记。它是三个字符(0xEF、0xBB、0xBF)的组合,通常出现在文件开头,看起来像“”。 - Signal15
67
这真是令人沮丧。即使是第三方模块也会被污染,比如试图通过SSH上传文件?突然间就出现了BOM(字节顺序标记)!“没错,让我们破坏每一个文件;这听起来是个好主意。”--微软。 - MichaelGG
10
默认编码为UTF8NoBOM,从PowerShell6.0版本开始。https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-file?view=powershell-6#parameters - Paul Shiryaev
3
谈论破坏向后兼容性的问题... - Dragas
1
我觉得应该注意到,虽然在UTF-8文件中使用BOM会导致许多系统出现问题,但是在Unicode UTF-8规范中明确允许包含BOM。参考链接 - Bacon Bits
谢谢。我都快抓狂了——尝试了两种格式,但都无法满足要求使用UTF-8编码。1)$stdcaltxt | Out-File -encoding utf8 -FilePath $stdCalFileName 和 2)Set-Content -Path $stdCalFileName -Value $stdcaltxt -Encoding utf8 两者都产生了不同的编码 UTF8-BOMUSC2 LE BOM,根据 Notepad++ 编码检查! - JGFMK
20个回答

2

我会建议只使用 Set-Content 命令,不需要其他的。

我的系统中使用的 Power shell 版本是:

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

附言:回答评论中的外部字符问题。使用以下命令将具有外国字符的文件“testfgnchar.txt”的内容复制到“findfnchar2.txt”。

PS C:\Users\XXXXX> Get-Content .\testfgnchar.txt | Set-Content findfnchar2.txt
PS C:\Users\XXXXX>

截图这里

注意:目前已经有比我在回答中使用的版本更新的PowerShell存在。


一开始似乎这样做是有效的,但实际上它使用了用户的 ANSI 代码页,并用最接近的等价物(例如 š → s)或问号替换其他符号。使用 set-content -encoding utf8 可以解决这个问题。 - Chortos-2
1
啊,这是真的;我没有注意到。但这意味着这个命令完全不适合这个任务,因为如果没有“-encoding”,它根本不使用UTF-8,无论是带BOM还是不带。 - Chortos-2
@Chortos-2,每个系统或用户在安装过程中都会有自己的语言设置。我分享的答案是针对我的系统上的英语(美国)语言设置,并且在我的区域语言中也显示相同。我觉得这种语言在安装过程中最常用。因此可能会成为阻碍因素,但无法提供更多帮助。你的系统显示什么语言?按下 Windows 键和 I 键查找相同信息。 - Pravanjan Hota
1
重点是该命令使用ANSI代码页,而不是在问题中明确请求的UTF-8(除非您按照Zombo的答案将ANSI设置为UTF-8)。在您的英语系统上尝试 echo āčķʃλшא⁴ℝ→⅛≈あ子 | set-content file.txt,您会发现没有一个字符被保留。其他答案的评论中也指出了其他PowerShell命令的同样问题。知道 set-content 默认使用单字节编码的拉丁文当然很好,但它与最初请求的UTF-8非常不同。 - Chortos-2
get-content 同样使用 ANSI 编码,就像你在测试中确认看到的那样。如果屏幕截图显示了 get-content a.txt | set-content b.txt 的结果,则它仅将文件读取为 Windows-1252 并将其写回为 Windows-1252,从而产生了逐字节复制。 UTF-8 在整个过程中没有涉及。提出这个问题的根本原因是编码取决于 Windows 设置,因此需要一种可靠的方法来使用 UTF-8。 - Chortos-2
显示剩余11条评论

1

将具有相同扩展名的多个文件转换为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)
}

1

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

例如,我经常运行创建另一个要在Oracle中执行的SQL脚本的SQL脚本。通过简单的重定向(">"),输出将以UTF-16格式显示,而这种格式不被SQLPlus识别。为了解决这个问题:

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字符,那么这不是您的答案。


7
是的,-Encoding ASCII 可以避免 BOM 问题,但显然你只能得到对 7 位 ASCII 字符 的支持。鉴于 ASCII 是 UTF-8 的子集,因此生成的文件在技术上也是有效的 UTF-8 文件,但 _输入中的所有非 ASCII 字符都将被转换为字面值 ? 字符。 - mklement0
1
这个答案需要更多的投票。sqlplus与BOM不兼容是很多头痛问题的原因之一。 - Amit Naidu
2
@AmitNaidu 不,这个答案是错误的,因为如果文本包含任何非ASCII字符:任何重音符号、umlauts、东方/西里尔字母等,它就无法工作。 - Joel Coehoorn
@JoelCoehoorn 这是根据用户提出的问题给出的正确答案。由于用户要求“强制”,因此他们不会遇到任何问题或者可能并不关心,因为源代码没有使用任何非ASCII字符。对于那些关心这些字符保留的人来说,这种方法将行不通。 - Erik Anderson

1
使用这种方法编辑UTF8-NoBOM文件,并生成一个具有正确编码的文件。
$fileD = "file.xml"
(Get-Content $fileD) | ForEach-Object { $_ -replace 'replace text',"new text" } | out-file "file.xml" -encoding ASCII

一开始我对这种方法持怀疑态度,但它让我惊喜并且奏效了!

已在PowerShell版本5.1上进行测试。


1
我在PowerShell中遇到了相同的错误,使用了这个隔离方法并解决了它。
$PSDefaultParameterValues['*:Encoding'] = 'utf8'

仅仅设置utf还不够。您还需要指定-encoding default。例如:$filecontent | Out-File $Filename -Encoding default。请注意,这将重新编码文件为utf8,因此未正确编码的文件将发生变化。 - undefined

1
    [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" 
    }  

源代码 如何使用PowerShell从文件中删除UTF8字节顺序标记(BOM)


1
如果您想使用 [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 | 中,以显式地在每行末尾添加 CRLFs(特别是在与 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"

请参见:如何将 ConvertTo-Csv 的结果写入不带 BOM 的 UTF-8 文件中


好的指针和建议:$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath) 的更简单的替代方法是 Convert-Path $MyPath;如果您想确保有一个结尾的 CRLF,请使用 [System.IO.File]::WriteAllLines() 即使只有一个输入字符串(不需要 Out-String)。 - mklement0

0
如果你的第一行没有包含任何不需要UTF8的花哨内容,以下代码将在Windows 10 Powershell上创建一个没有BOM的UTF8文件。
$file = get-content -path "C:\temp\myfile.txt" -Encoding UTF8

# do some stuff.

$file[0] | out-file "C:\temp\mynewfile.txt" -Encoding ascii
$file | select -skip 1 | out-file "C:\temp\mynewfile.txt" -append utf8

这个使用两行代码来创建新文件。第一行使用-encoding ascii来强制使用UTF8编码,但是它将被限制在7位ASCII码范围内。对于文本文件来说,这通常不是问题,否则你可能会选择字节编码。
第二个命令附加剩余的内容,但是跳过第一行,因为我们已经使用完整的UTF8支持解析了它。

-3

可以使用以下代码获取没有BOM的UTF8

$MyFile | Out-File -Encoding ASCII

4
不,它会将输出转换为当前的ANSI代码页(例如cp1251或cp1252)。 它根本不是UTF-8! - ForNeVeR
1
谢谢Robin。这种方法可能无法用于编写没有BOM的UTF-8文件,但是-Encoding ASCII选项可以去除BOM。这样我就可以为gvim生成一个批处理文件。.bat文件会因为BOM而出现问题。 - Greg
3
您说得对,ASCII编码不是UTF-8,但它也不是当前的ANSI代码页 - 您想到的应该是默认值(Default)ASCII确实是7位ASCII编码,其中代码点>= 128会被转换为文字? - mklement0
据我所知,ASCII在这个API中实际上指的是默认的单字节编码,通常在Windows中也是如此。是的,它与官方的ASCII定义不同步,但只是历史遗留问题。 - ForNeVeR
1
@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”)将正确地保留它。 - mklement0
3
这是一份完美的答案,适用于那些不需要使用UTF-8或其他与ASCII不同的编码,并且不关心编码和Unicode的目的的人。你可以将它作为UTF-8来使用,因为所有ASCII字符的等效UTF-8字符是相同的(这意味着将ASCII文件转换为UTF-8文件将得到一个相同的文件(如果没有BOM))。对于那些文本中有非ASCII字符的人来说,这个答案是错误和误导的。 - TNT

-4
这个对我来说有效(使用“默认”而不是“UTF8”):
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath

结果是没有BOM的ASCII。


2
根据Out-File文档,指定“默认”编码将使用系统当前的ANSI代码页,而不是我所需的UTF-8。 - sourcenouveau
这对我来说似乎有效,至少对于Export-CSV是这样。如果您在适当的编辑器中打开生成的文件,则文件编码为UTF-8无BOM,而不是ASCII所期望的西方拉丁ISO 9。 - eythort
许多编辑器如果无法检测到编码方式,会将文件以UTF-8格式打开。 - emptyother

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