注意:此答案适用于Windows PowerShell; 相比之下,在跨平台的PowerShell核心版中,没有BOM的UTF-8是默认编码。
为了补充M. Dudley自己简单实用的答案(以及ForNeVeR更简洁的重新制定):
为方便起见,这里有高级功能Out-FileUtf8NoBom,这是一个基于管道的替代方案,模仿Out-FileUtf8NoBom,这意味着:
您可以像管道中的Out-FileUtf8NoBom一样使用它。
非字符串的输入对象的格式与将它们发送到控制台时的格式相同,就像Out-FileUtf8NoBom一样。
例:
(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath
请注意Out-FileUtf8NoBom如何包含在(...)中,它确保在通过管道发送结果之前打开,读取并关闭整个文件。 这是必要的,以便能够回写到同一个文件(在适当的位置更新)。
但是,一般情况下,这种技术不建议有两个原因:(a)整个文件必须适合内存;(b)如果命令中断,数据将丢失。
关于内存使用的说明:
M. Dudley自己的回答要求首先在内存中构建整个文件内容,这对于大文件来说可能会有问题。
下面的函数仅稍微改进了这一点:所有输入对象仍然首先被缓冲,但是它们的字符串表示然后被生成并逐个写入输出文件。
源代码Out-FileUtf8NoBom(也可作为麻省理工学院授权的Gist提供):
.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-String parameters:
* Only a literal output path is supported, and only as a parameter.
* -Force is not supported.
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, as of PowerShell v5,
Out-File still lacks the ability to write UTF-8 files without a BOM:
using -Encoding UTF8 invariably prepends a BOM.
#>
function Out-FileUtf8NoBom {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position=0)] [string] $LiteralPath,
[switch] $Append,
[switch] $NoClobber,
[AllowNull()] [int] $Width,
[Parameter(ValueFromPipeline)] $InputObject
)
#requires -version 3
# Make sure that the .NET framework sees the same working dir. as PS
# and resolve the input path to a full path.
[System.IO.Directory]::SetCurrentDirectory($PWD) # Caveat: .NET Core doesn't support [Environment]::CurrentDirectory
$LiteralPath = [IO.Path]::GetFullPath($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 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 | % { $sw.WriteLine($_) }
} finally {
$sw.Dispose()
}
}