Out-File
dường như buộc BOM khi sử dụng UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Làm cách nào tôi có thể viết một tệp trong UTF-8 mà không có BOM bằng PowerShell?
Out-File
dường như buộc BOM khi sử dụng UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Làm cách nào tôi có thể viết một tệp trong UTF-8 mà không có BOM bằng PowerShell?
Câu trả lời:
Sử dụng UTF8Encoding
lớp của .NET và chuyển $False
đến hàm tạo dường như hoạt động:
$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)
là đủ. Quá WriteAllLines
tải này ghi chính xác UTF8 mà không có BOM.
WriteAllLines
dường như đòi hỏi $MyPath
phải tuyệt đối.
WriteAllLines
lấy thư mục hiện tại từ [System.Environment]::CurrentDirectory
. Nếu bạn mở PowerShell và sau đó thay đổi thư mục hiện tại của bạn (bằng cách sử dụng cd
hoặc Set-Location
), thì [System.Environment]::CurrentDirectory
sẽ không bị thay đổi và tệp sẽ nằm trong thư mục sai. Bạn có thể làm việc xung quanh điều này bằng cách [System.Environment]::CurrentDirectory = (Get-Location).Path
.
Cách thích hợp như bây giờ là sử dụng giải pháp được đề xuất bởi @Roman Kuzmin trong các nhận xét cho @M. Dudley trả lời :
[IO.File]::WriteAllLines($filename, $content)
(Tôi cũng đã rút ngắn nó một chút bằng cách tước bỏ việc System
làm rõ không gian tên không cần thiết - nó sẽ được thay thế tự động theo mặc định.)
[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Tôi đoán đây không phải là UTF, nhưng tôi chỉ tìm thấy một giải pháp khá đơn giản có vẻ hiệu quả ...
Get-Content path/to/file.ext | out-file -encoding ASCII targetFile.ext
Đối với tôi điều này dẫn đến một utf-8 không có tệp bom bất kể định dạng nguồn.
-encoding utf8
cho yêu cầu của tôi.
-Encoding ASCII
tránh vấn đề BOM, nhưng rõ ràng bạn chỉ nhận được các ký tự ASCII 7 bit . Cho rằng ASCII là tập con của UTF-8, tệp kết quả về mặt kỹ thuật cũng là tệp UTF-8 hợp lệ, nhưng tất cả các ký tự không phải ASCII trong đầu vào của bạn sẽ được chuyển đổi thành ?
ký tự bằng chữ .
-encoding utf8
vẫn đưa ra UTF-8 với BOM. :(
Lưu ý: Câu trả lời này áp dụng cho Windows PowerShell ; ngược lại, trong phiên bản PowerShell Core đa nền tảng (v6 +), UTF-8 không có BOM là mã hóa mặc định , trên tất cả các lệnh ghép ngắn.
Nói cách khác: Nếu bạn đang sử dụng PowerShell [Core] phiên bản 6 trở lên , bạn sẽ nhận được các tệp UTF-8 không BOM theo mặc định (bạn cũng có thể yêu cầu rõ ràng với -Encoding utf8
/ -Encoding utf8NoBOM
, trong khi bạn nhận được bằng mã hóa -BOM với -utf8BOM
).
Để bổ sung cho câu trả lời đơn giản và thực dụng của M. Dudley (và cải cách ngắn gọn hơn của ForNeVeR ):
Để thuận tiện, đây là chức năng nâng cao Out-FileUtf8NoBom
, một giải pháp thay thế dựa trên đường ống bắt chướcOut-File
, có nghĩa là:
Out-File
trong một đường ống dẫn.Out-File
.Thí dụ:
(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath
Lưu ý cách (Get-Content $MyPath)
được bao trong (...)
, đảm bảo rằng toàn bộ tệp được mở, đọc đầy đủ và đóng trước khi gửi kết quả qua đường ống. Điều này là cần thiết để có thể ghi lại vào cùng một tệp (cập nhật nó tại chỗ ).
Tuy nhiên, nói chung, kỹ thuật này không được khuyến khích vì 2 lý do: (a) toàn bộ tệp phải vừa với bộ nhớ và (b) nếu lệnh bị gián đoạn, dữ liệu sẽ bị mất.
Một lưu ý về việc sử dụng bộ nhớ :
Mã nguồn củaOut-FileUtf8NoBom
(cũng có sẵn dưới dạng Gist được MIT cấp phép ):
<#
.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()
}
}
Bắt đầu từ phiên bản 6 hỗ trợ PowerShell sự UTF8NoBOM
mã hóa cho cả set-nội dung và out-tập tin và thậm chí sử dụng điều này như mã hóa mặc định.
Vì vậy, trong ví dụ trên, nó chỉ đơn giản là như thế này:
$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
$PSVersionTable.PSVersion
Khi sử dụng Set-Content
thay vì Out-File
, bạn có thể chỉ định mã hóa Byte
, có thể được sử dụng để ghi một mảng byte vào một tệp. Điều này kết hợp với mã hóa UTF8 tùy chỉnh không phát ra BOM cho kết quả mong muốn:
# 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
Sự khác biệt để sử dụng [IO.File]::WriteAllLines()
hoặc tương tự là nó sẽ hoạt động tốt với bất kỳ loại mục và đường dẫn nào, không chỉ các đường dẫn tệp thực tế.
Tập lệnh này sẽ chuyển đổi, thành UTF-8 mà không có BOM, tất cả các tệp .txt trong DIRECTORY1 và xuất chúng thành 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"
}
Nguồn Cách xóa Dấu hiệu đặt hàng Byte UTF8 (BOM) khỏi tệp bằng PowerShell
Nếu bạn muốn sử dụng [System.IO.File]::WriteAllLines()
, bạn nên truyền tham số thứ hai thành String[]
(nếu loại $MyFile
là Object[]
) và cũng chỉ định đường dẫn tuyệt đối với $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)
, như:
$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)
Nếu bạn muốn sử dụng [System.IO.File]::WriteAllText()
, đôi khi bạn nên đưa tham số thứ hai vào | Out-String |
để thêm CRLF vào cuối mỗi dòng một cách rõ ràng (Đặc biệt là khi bạn sử dụng chúng với 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)
Hoặc bạn có thể sử dụng [Text.Encoding]::UTF8.GetBytes()
với 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"
xem: Cách ghi kết quả của ConvertTo-Csv vào một tệp trong UTF-8 mà không cần BOM
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)
là Convert-Path $MyPath
; nếu bạn muốn đảm bảo một CRLF dấu, chỉ cần sử dụng [System.IO.File]::WriteAllLines()
ngay cả với một đơn chuỗi đầu vào (không có nhu cầu Out-String
).
Một kỹ thuật tôi sử dụng là chuyển hướng đầu ra sang tệp ASCII bằng lệnh ghép ngắn Out-File .
Ví dụ, tôi thường chạy các tập lệnh SQL tạo tập lệnh SQL khác để thực thi trong Oracle. Với chuyển hướng đơn giản (">"), đầu ra sẽ ở dạng UTF-16 không được SQLPlus nhận ra. Để giải quyết vấn đề này:
sqlplus -s / as sysdba "@create_sql_script.sql" |
Out-File -FilePath new_script.sql -Encoding ASCII -Force
Tập lệnh được tạo sau đó có thể được thực thi thông qua một phiên SQLPlus khác mà không phải lo lắng về Unicode:
sqlplus / as sysdba "@new_script.sql" |
tee new_script.log
-Encoding ASCII
tránh sự cố BOM, nhưng rõ ràng bạn chỉ nhận được hỗ trợ cho các ký tự ASCII 7 bit . Cho rằng ASCII là tập con của UTF-8, tệp kết quả về mặt kỹ thuật cũng là tệp UTF-8 hợp lệ, nhưng tất cả các ký tự không phải ASCII trong đầu vào của bạn sẽ được chuyển đổi thành ?
ký tự bằng chữ .
Thay đổi nhiều tệp bằng cách mở rộng thành UTF-8 mà không cần 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)
}
Vì bất kỳ lý do gì, các WriteAllLines
cuộc gọi vẫn tạo ra BOM cho tôi, với UTF8Encoding
đối số BOMless và không có nó. Nhưng những điều sau đây làm việc cho tôi:
$bytes = gc -Encoding byte BOMthetorpedoes.txt
[IO.File]::WriteAllBytes("$(pwd)\BOMthetorpedoes.txt", $bytes[3..($bytes.length-1)])
Tôi đã phải làm cho đường dẫn tập tin tuyệt đối để nó hoạt động. Nếu không, nó đã ghi các tập tin vào máy tính để bàn của tôi. Ngoài ra, tôi cho rằng điều này chỉ hoạt động nếu bạn biết BOM của bạn là 3 byte. Tôi không biết nó đáng tin cậy đến mức nào khi mong đợi một định dạng / độ dài BOM nhất định dựa trên mã hóa.
Ngoài ra, như đã viết, điều này có lẽ chỉ hoạt động nếu tệp của bạn phù hợp với một mảng powershell, dường như có giới hạn độ dài của một số giá trị thấp hơn [int32]::MaxValue
trên máy của tôi.
WriteAllLines
mà không cần mã hóa một cuộc tranh cãi không bao giờ viết một BOM bản thân , nhưng nó có thể tưởng tượng rằng bạn chuỗi xảy ra để bắt đầu với BOM nhân vật ( U+FEFF
), mà trên bằng văn bản có hiệu quả tạo ra một BOM UTF-8; ví dụ: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)
(bỏ qua [char] 0xfeff +
để thấy rằng không có BOM nào được viết).
[Environment]::CurrentDirectory = $PWD.ProviderPath
, bạn có thể đồng bộ hóa chúng với , hoặc, như một cách thay thế chung hơn cho "$(pwd)\..."
phương pháp của bạn (tốt hơn : "$pwd\..."
, thậm chí tốt hơn: "$($pwd.ProviderPath)\..."
hoặc (Join-Path $pwd.ProviderPath ...)
), sử dụng(Convert-Path BOMthetorpedoes.txt)
U+FEFF
trừu tượng .
Có thể sử dụng bên dưới để nhận UTF8 mà không cần BOM
$MyFile | Out-File -Encoding ASCII
ASCII
không phải là UTF-8, nhưng đó không phải là bảng mã ANSI hiện tại - bạn đang nghĩ đến Default
; ASCII
thực sự là mã hóa ASCII 7 bit, với các điểm mã> = 128 được chuyển đổi thành các thể hiện bằng chữ ?
.
-Encoding ASCII
thực sự chỉ là ASCII 7 bit: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)
- ä
đã được phiên âm thành a ?
. Ngược lại, -Encoding Default
("ANSI") sẽ bảo vệ chính xác nó.
Cái này hoạt động với tôi (sử dụng "Mặc định" thay vì "UTF8"):
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath
Kết quả là ASCII không có BOM.
Default
mã hóa sẽ sử dụng trang mã ANSI hiện tại của hệ thống, không phải là UTF-8, như tôi yêu cầu.