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

当使用 UTF-8时,Out-File似乎强迫 BOM:

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

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

2021年最新情况

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

366180 次浏览

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

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

这个脚本将把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);
}

这是为我工作(使用“默认”而不是“UTF8”):

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

结果是没有BOM的ASCII。

可以使用下面得到UTF8没有BOM

$MyFile | Out-File -Encoding ASCII

适当的目前的方法是使用@Roman Kuzmin推荐的解决方案在评论到@M。达德利回答:

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

(我还通过剥离不必要的System名称空间澄清来缩短了它-默认情况下它将被自动替换。)

注意:这个答案适用于Windows PowerShell;相比之下,在跨平台的PowerShell 核心版本(v6+)中,UTF-8 没有物料清单是所有cmdlet的默认编码

  • 换句话说:如果您正在使用PowerShell [Core]版本6或更高版本,您将得到无bom的UTF-8文件默认(你也可以用-Encoding utf8 / -Encoding utf8NoBOM显式请求,而你用-utf8BOM得到-BOM编码)。

  • 如果你正在运行Windows 10并且你是愿意切换到BOM-less UTF-8编码系统范围 -这可能有副作用- 甚至Windows PowerShell也可以始终使用无bom的UTF-8 -请参阅这个答案


要补充达德利先生简单而务实的回答(和ForNeVeR的更简洁的重新表述):

  • A 简单的powershell原生替代方案是将Out-StringNew-Item结合起来,这(奇怪的是)即使在Windows PowerShell中默认也会创建BOM-less UTF-8文件:

    $null = New-Item -Force $MyPath -Value ($MyFile | Out-String)
    
  • 为了方便起见,下面是高级函数Out-FileUtf8NoBom一个模仿Out-File的基于管道的替代方法,这意味着:

    • 你可以像管道中的Out-File一样使用它。
    • 不是字符串的输入对象被格式化为如果你将它们发送到控制台,就像Out-File一样。
    • 一个额外的-UseLF开关允许你使用unix格式的仅限lf换行符("`n"),而不是你通常得到的windows格式的CRLF换行符("`r`n")。

例子:

(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath # Add -UseLF for Unix newlines
注意(Get-Content $MyPath)是如何包含在(...)中,这确保了整个文件在通过管道发送结果之前被打开、完整读取和关闭。这是必要的,以便能够写回相同文件(将其更新为在适当的位置)。
但是,一般来说,这种技术并不可取,原因有二:(a)整个文件必须适合内存;(b)如果命令被中断,数据将会丢失

关于内存使用的注意事项:

  • < a href = " https://stackoverflow.com/a/5596984/45375 " > M。达德利自己的回答 和要求首先在内存中构建整个文件内容之上的New-Item选项,这在大输入集时可能会有问题
  • 下面的函数需要,因为它是作为proxy (wrapper)函数实现的(如何定义这样的函数的简明摘要,请参阅这个答案)。

函数Out-FileUtf8NoBom的源代码:

注意:函数是也可用作为麻省理工学院许可的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.


.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, 2022 Michael Klement <mklement0@gmail.com> (http://same2u.net),
released under the [MIT license](https://spdx.org/licenses/MIT#licenseText).


#>


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


begin {


# 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 } }


try {
# Create the script block with the command to use in the steppable pipeline.
# $scriptCmd = {
#   & Microsoft.PowerShell.Utility\Out-String -Stream @htOutStringArgs |
#   ForEach-Object { if ($UseLF) { $sw.Write(($_ + "`n")) } else { $sw.WriteLine($_) } }
# }
$scriptCmd = {
& Microsoft.PowerShell.Utility\Out-String -Stream @htOutStringArgs |
. { process { if ($UseLF) { $sw.Write(($_ + "`n")) } else { $sw.WriteLine($_) } } }
}
      

$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
catch { throw }


}


process
{
$steppablePipeline.Process($_)
}


end {
$steppablePipeline.End()
$sw.Dispose()
}


}

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

例如,我经常运行创建另一个SQL脚本并在Oracle中执行的SQL脚本。通过简单的重定向(">"),输出将是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字符的保存,这不是适合您的答案。

更改多个文件扩展到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)
}

我认为这不会是UTF,但我只是发现了一个相当简单的解决方案,似乎工作…

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

对我来说,这导致了一个没有bom文件的utf-8,不管源格式如何。

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

如何删除UTF8字节顺序标记(BOM)从一个文件使用PowerShell

如果你想使用[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"

看:如何写的结果转换到csv文件在UTF-8没有BOM

当使用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()或类似方法的区别在于,它应该适用于任何类型的项和路径,而不仅仅是实际的文件路径。

版本6开始,powershell支持设置内容out-fileUTF8NoBOM编码,甚至将其用作默认编码。

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

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath

对于PowerShell 5.1,启用此设置:

控制面板,区域,管理,更改系统区域,使用Unicode UTF-8 对于全球语言支持

然后输入PowerShell:

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

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

https://github.com/PowerShell/PowerShell

< p > 重要!:这只适用于当一个额外的空格或换行符在开始是没有问题的文件用例
(例如,如果它是一个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
    

老问题,新答案:

而“old"powershell编写BOM时,新的平台不可知变体的行为有所不同:默认是“no BOM”;可通过开关进行配置:

编码

指定目标文件的编码类型。默认值为utf8NoBOM。

可接受的取值如下:

  • ascii:使用ascii(7位)字符集的编码。
  • bigendianunicode:以UTF-16格式编码,使用大端字节序。
  • oem:使用MS-DOS和控制台程序的默认编码。
  • unicode:使用小端字节顺序以UTF-16格式编码。
  • utf7: UTF-7编码格式。
  • utf8: UTF-8编码格式。
  • utf8BOM:编码为UTF-8格式,带有字节顺序标记(Byte Order Mark, BOM)
  • utf8NoBOM:编码为UTF-8格式,没有字节顺序标记(BOM)
  • utf32: UTF-32编码格式。

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

我在PowerShell中有相同的错误,并使用此隔离并修复了它

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

使用该方法编辑UTF8-NoBOM文件,生成编码正确的文件-

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

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

使用powershell 5.1版进行测试

我会说只使用设置内容命令,不需要其他任何命令。

我系统中的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 < / p >