Out-File
似乎在使用 UTF-8 时强制使用 BOM:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
如何使用 PowerShell 以 UTF-8 编写没有 BOM 的文件?
2021 年更新
自从我 10 年前写这个问题以来,PowerShell 发生了一些变化。检查下面的多个答案,他们有很多很好的信息!
使用 .NET 的 UTF8Encoding
类并将 $False
传递给构造函数似乎可行:
$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)
目前正确的方法是使用@Roman Kuzmin in comments 向@M 推荐的解决方案。达力answer:
[IO.File]::WriteAllLines($filename, $content)
(我还通过去除不必要的 System
命名空间说明将其缩短了一点 - 默认情况下会自动替换它。)
[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
我认为这不会是 UTF,但我刚刚找到了一个似乎可行的非常简单的解决方案......
Get-Content path/to/file.ext | out-file -encoding ASCII targetFile.ext
对我来说,无论源格式如何,这都会导致没有 bom 文件的 utf-8。
-encoding utf8
来满足我的要求。
-Encoding ASCII
避免了 BOM 问题,但您显然只能得到 7 位 ASCII 字符。鉴于 ASCII 是 UTF-8 的子集,因此生成的文件在技术上也是有效的 UTF-8 文件,但您输入中的所有非 ASCII 字符都将转换为文字 ?
字符。
注意:此答案适用于 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()
}
}
从 版本 6 开始,powershell 支持 set-content 和 out-file 的 UTF8NoBOM
编码,甚至将其用作默认编码。
所以在上面的例子中,它应该是这样的:
$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
$PSVersionTable.PSVersion
检查版本
-Encoding UTF8NoBOM
从来不是必需,因为它是默认 编码。
当使用 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
此脚本会将 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);
}
重要!:这仅适用于文件开头的额外空格或换行符对您的文件用例没有问题(例如,如果它是 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
老问题,新答案:
虽然“旧”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 格式编码。
对于 PowerShell 5.1,启用此设置:
控制面板、区域、管理、更改系统区域设置、使用 Unicode UTF-8 获得全球语言支持
然后在 PowerShell 中输入:
$PSDefaultParameterValues['*:Encoding'] = 'Default'
或者,您可以升级到 PowerShell 6 或更高版本。
https://github.com/PowerShell/PowerShell
将多个文件通过扩展名更改为 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)
}
[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
如果要使用 [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
)。
我在 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 版测试
我想说只使用 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
我使用的一种技术是使用 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 字符都将转换为文字 ?
字符。
可以在下面使用来获得没有 BOM 的 UTF8
$MyFile | Out-File -Encoding ASCII
ASCII
不是 UTF-8 是正确的,但它也不是当前的 ANSI 代码页 - 您正在考虑 Default
; ASCII
确实是 7 位 ASCII 编码,代码点 >= 128 被转换为文字 ?
实例。
-Encoding ASCII
确实只是 7 位 ASCII:'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)
- ä
已被音译为 ?
。相比之下,-Encoding Default
("ANSI") 会正确地保留它。
这个对我有用(使用“默认”而不是“UTF8”):
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath
结果是没有 BOM 的 ASCII。
Default
编码将使用系统当前的 ANSI 代码页,它不是 UTF-8,正如我所要求的那样。
不定期副业成功案例分享
[System.IO.File]::WriteAllLines($MyPath, $MyFile)
就足够了。此WriteAllLines
重载完全写入没有 BOM 的 UTF8。WriteAllLines
似乎要求$MyPath
是绝对的。WriteAllLines
从[System.Environment]::CurrentDirectory
获取当前目录。如果您打开 PowerShell,然后更改当前目录(使用cd
或Set-Location
),那么[System.Environment]::CurrentDirectory
将不会更改,并且文件最终会位于错误的目录中。您可以通过[System.Environment]::CurrentDirectory = (Get-Location).Path
解决此问题。