Out-File
使用UTF-8时似乎强制使用BOM:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
如何使用PowerShell在没有BOM的情况下以UTF-8格式写入文件?
Out-File
使用UTF-8时似乎强制使用BOM:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
如何使用PowerShell在没有BOM的情况下以UTF-8格式写入文件?
Answers:
使用.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
是绝对的。
WriteAllLines
获取当前目录[System.Environment]::CurrentDirectory
。如果打开PowerShell,然后更改当前目录(使用cd
或Set-Location
),则[System.Environment]::CurrentDirectory
不会更改,并且文件最终将位于错误的目录中。您可以通过解决此问题[System.Environment]::CurrentDirectory = (Get-Location).Path
。
到目前为止,正确的方法是使用@Roman Kuzmin 在 @M 注释中推荐的解决方案。达德利回答:
[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字符都将转换为原义?
字符。
-encoding utf8
仍输出带有BOM的UTF-8。:(
注意:此答案适用于Windows PowerShell;相比之下,在跨平台的PowerShell Core版本(v6 +)中,所有cmdlet 的默认编码是不带BOM的 UTF-8 。
换句话说:如果您使用的是PowerShell [Core]版本6或更高版本
,则默认情况下会获得无BOM的UTF-8文件(也可以使用-Encoding utf8
/ 显式请求-Encoding utf8NoBOM
,而使用 -BOM编码获得-utf8BOM
)。
为了补充达德利(M. Dudley)自己简单而务实的答案(以及ForNeVeR更为简洁的表述)):
为了方便起见,这里的先进功能Out-FileUtf8NoBom
,基于管道的替代,模仿Out-File
,这意味着:
Out-File
在管道中。Out-File
。例:
(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath
请注意如何使用(Get-Content $MyPath)
括起来(...)
,以确保在通过管道发送结果之前,打开,完整读取和关闭整个文件。为了能够写回相同的内容,这是必需的文件(在适当位置更新它),。
但是,通常不建议使用此技术,原因有两个:(a)整个文件必须适合内存;(b)如果命令中断,数据将丢失。
关于的注释 内存使用的说明:
的源代码Out-FileUtf8NoBom
(也可作为MIT许可的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.ProviderPath) # Caveat: Older .NET Core versions don'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()
}
}
使用Set-Content
代替时Out-File
,您可以指定encoding 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()
或类似方法的不同之处在于,它应可与任何类型的项目和路径一起正常工作,而不仅仅是实际的文件路径。
此脚本会将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);
}
[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"
}
如果要使用[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"
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)
是Convert-Path $MyPath
; 如果您想确保尾随CRLF,[System.IO.File]::WriteAllLines()
甚至只需使用单个输入字符串即可(无需使用Out-String
)。
我使用的一种技术是使用Out-File cmdlet 将输出重定向到ASCII文件。
例如,我经常运行创建另一个SQL脚本以在Oracle中执行的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
-Encoding ASCII
可以避免BOM表问题,但是显然您仅支持7位ASCII字符。鉴于ASCII是UTF-8的子集,从技术上讲,生成的文件也是有效的UTF-8文件,但是输入中的所有非ASCII字符都将转换为原义?
字符。
通过扩展名将多个文件更改为不带BOM的UTF-8:
$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)
}
无论出于何种原因,WriteAllLines
调用仍然为我生成BOM,带有BOMless UTF8Encoding
参数,并且没有它。但是以下对我有用:
$bytes = gc -Encoding byte BOMthetorpedoes.txt
[IO.File]::WriteAllBytes("$(pwd)\BOMthetorpedoes.txt", $bytes[3..($bytes.length-1)])
我必须使文件路径绝对起作用。否则它将文件写入我的桌面。另外,我想这只有在您知道BOM为3个字节的情况下才有效。我不知道期望基于编码的给定BOM格式/长度有多可靠。
同样,按照书面规定,这可能仅在文件适合Powershell数组的情况下才有效,而该数组的长度限制似乎比[int32]::MaxValue
我的计算机低。
WriteAllLines
没有编码参数的话,BOM 本身不会写,但是可以想到的是,您的字符串恰巧以BOM 字符(U+FEFF
)开头,在编写时有效地创建了UTF-8 BOM;例如:($s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)
忽略则[char] 0xfeff +
看不到任何 BOM)。
[Environment]::CurrentDirectory = $PWD.ProviderPath
,或者作为一种更通用的替代"$(pwd)\..."
方法(更好:"$pwd\..."
,甚至更好:"$($pwd.ProviderPath)\..."
或(Join-Path $pwd.ProviderPath ...)
),使用(Convert-Path BOMthetorpedoes.txt)
U+FEFF
的相应编码的字节表示形式。
可以在下面使用以获得没有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。