Menggunakan PowerShell untuk menulis file dalam UTF-8 tanpa BOM

246

Out-File tampaknya memaksa BOM saat menggunakan UTF-8:

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

Bagaimana saya bisa menulis file di UTF-8 tanpa BOM menggunakan PowerShell?

M. Dudley
sumber
23
BOM = Tanda Byte-Order. Tiga karakter ditempatkan pada awal file (0xEF, 0xBB, 0xBF) yang terlihat seperti "ï» ¿"
Signal15
40
Ini sangat membuat frustrasi. Bahkan modul pihak ketiga tercemar, seperti mencoba mengunggah file melalui SSH? BOM! "Ya, mari kita korup setiap file; itu sepertinya ide yang bagus." -Microsoft.
MichaelGG
3
Pengkodean default adalah UTF8NoBOM dimulai dengan Powershell versi 6.0 docs.microsoft.com/en-us/powershell/module/…
Paul Shiryaev
Bicara tentang melanggar kompatibilitas mundur ...
Dragas

Jawaban:

220

Menggunakan UTF8Encodingkelas .NET dan meneruskan $Falseke konstruktor tampaknya berfungsi:

$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)
M. Dudley
sumber
42
Ugh, kuharap itu bukan satu-satunya jalan.
Scott Muc
114
Satu baris [System.IO.File]::WriteAllLines($MyPath, $MyFile)sudah cukup. WriteAllLinesKelebihan ini menulis persis UTF8 tanpa BOM.
Roman Kuzmin
6
Membuat permintaan fitur MSDN di sini: connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/…
Groostav
3
Catatan yang WriteAllLinestampaknya $MyPathharus mutlak.
sschuberth
9
@xdhmoore WriteAllLinesmendapatkan direktori saat ini dari [System.Environment]::CurrentDirectory. Jika Anda membuka PowerShell dan kemudian mengubah direktori Anda saat ini (menggunakan cdatau Set-Location), maka [System.Environment]::CurrentDirectorytidak akan berubah dan file akan berakhir di direktori yang salah. Anda dapat mengatasi ini dengan [System.Environment]::CurrentDirectory = (Get-Location).Path.
Shayan Toqraee
79

Cara yang tepat seperti sekarang adalah menggunakan solusi yang direkomendasikan oleh @Roman Kuzmin dalam komentar ke @M. Dudley menjawab :

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

(Saya juga telah mempersingkatnya sedikit dengan menghapus Systemklarifikasi namespace yang tidak perlu - itu akan diganti secara otomatis secara default.)

ForNeVeR
sumber
2
Ini (untuk alasan apa pun) tidak menghapus BOM untuk saya, sedangkan jawaban yang diterima
Liam
@Liam, mungkin beberapa versi lama PowerShell atau .NET?
ForNeVeR
1
Saya percaya versi yang lebih lama dari fungsi .NET WriteAllLines memang menulis BOM secara default. Jadi itu bisa menjadi masalah versi.
Bender the Greatest
2
Dikonfirmasi dengan menulis dengan BOM di Powershell 3, tetapi tanpa BOM di Powershell 4. Saya harus menggunakan jawaban asli M. Dudley.
chazbot7
2
Jadi itu berfungsi pada Windows 10 di mana itu diinstal secara default. :) Juga, perbaikan yang disarankan:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Johny Skovdal
50

Saya pikir ini bukan UTF, tapi saya baru saja menemukan solusi yang cukup sederhana yang sepertinya berfungsi ...

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

Bagi saya ini menghasilkan utf-8 tanpa file bom terlepas dari format sumbernya.

Lenny
sumber
8
Ini bekerja untuk saya, kecuali saya menggunakan -encoding utf8untuk kebutuhan saya.
Chim Chimz
1
Terima kasih banyak. Saya bekerja dengan log dump alat - yang memiliki tab di dalamnya. UTF-8 tidak berfungsi. ASCII memecahkan masalah. Terima kasih.
user1529294
44
Ya, -Encoding ASCIIhindari masalah BOM, tetapi Anda jelas hanya mendapatkan karakter ASCII 7-bit . Karena ASCII adalah bagian dari UTF-8, file yang dihasilkan secara teknis juga merupakan file UTF-8 yang valid, tetapi semua karakter non-ASCII dalam input Anda akan dikonversi ke ?karakter literal .
mklement0
4
@ChimChimz Saya tidak sengaja memilih komentar Anda, tetapi -encoding utf8masih menampilkan UTF-8 dengan BOM. :(
TheDudeAbides
33

Catatan: Jawaban ini berlaku untuk Windows PowerShell ; Sebaliknya, dalam PowerShell Core edisi lintas platform (v6 +), UTF-8 tanpa BOM adalah penyandian default , di semua cmdlet.
Dengan kata lain: Jika Anda menggunakan PowerShell [Core] versi 6 atau lebih tinggi , Anda mendapatkan file UTF-8 BOM-kurang secara default (yang Anda juga dapat secara eksplisit meminta dengan -Encoding utf8/ -Encoding utf8NoBOM, sedangkan Anda mendapatkan dengan -BOM encoding dengan -utf8BOM).


Untuk melengkapi jawaban M. Dudley yang sederhana dan pragmatis (dan reformulasi ForNeVeR yang lebih ringkas ):

Untuk kenyamanan, inilah fungsi lanjutan Out-FileUtf8NoBom, alternatif berbasis pipa yang meniruOut-File , yang berarti:

  • Anda dapat menggunakannya seperti Out-Filedalam pipa.
  • objek input yang bukan string diformat seperti jika Anda mengirimnya ke konsol, sama seperti dengan Out-File.

Contoh:

(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath

Perhatikan bagaimana (Get-Content $MyPath)terlampir (...), yang memastikan bahwa seluruh file dibuka, dibaca secara penuh, dan ditutup sebelum mengirim hasilnya melalui pipa. Ini diperlukan untuk dapat menulis kembali ke file yang sama (perbarui di tempat ).
Namun, secara umum, teknik ini tidak dianjurkan karena 2 alasan: (a) seluruh file harus sesuai dengan memori dan (b) jika perintah terputus, data akan hilang.

Catatan tentang penggunaan memori :

  • Jawaban M. Dudley sendiri mensyaratkan bahwa seluruh isi file dibangun di memori terlebih dahulu, yang dapat bermasalah dengan file besar.
  • Fungsi di bawah ini hanya meningkat sedikit: semua objek input masih buffered pertama, tetapi representasi string mereka kemudian dihasilkan dan ditulis ke file output satu per satu.

Kode sumberOut-FileUtf8NoBom (juga tersedia sebagai Inti berlisensi MIT ):

<#
.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()
  }

}
mklement0
sumber
16

Mulai dari versi 6 PowerShell mendukung UTF8NoBOMencoding baik untuk set-content dan out-file dan bahkan menggunakan ini sebagai encoding default.

Jadi dalam contoh di atas seharusnya menjadi seperti ini:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
sc911
sumber
@ RaúlSalinas-Monteagudo versi apa yang Anda pakai?
John Bentley
Bagus. FYI memeriksa versi dengan$PSVersionTable.PSVersion
KCD
14

Saat menggunakan Set-Contentalih-alih Out-File, Anda dapat menentukan pengkodean Byte, yang dapat digunakan untuk menulis array byte ke file. Ini dikombinasikan dengan pengkodean UTF8 khusus yang tidak memancarkan BOM memberikan hasil yang diinginkan:

# 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

Perbedaan menggunakan [IO.File]::WriteAllLines()atau serupa adalah bahwa itu harus berfungsi dengan baik dengan semua jenis item dan jalur, tidak hanya jalur file yang sebenarnya.

Lucero
sumber
5

Skrip ini akan mengonversi, menjadi UTF-8 tanpa BOM, semua file .txt dalam DIRECTORY1 dan menghasilkannya menjadi DIRECTORY2

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}
jamhan
sumber
Yang ini gagal tanpa peringatan apa pun. Versi PowerShell apa yang harus saya gunakan untuk menjalankannya?
darksoulsong
3
Solusi WriteAllLines bekerja sangat baik untuk file kecil. Namun, saya butuh solusi untuk file yang lebih besar. Setiap kali saya mencoba menggunakan ini dengan file yang lebih besar, saya mendapatkan kesalahan OutOfMemory.
BermudaLamb
2
    [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" 
    }  

Sumber Cara menghapus UTF8 Byte Order Mark (BOM) dari file menggunakan PowerShell

frank tan
sumber
2

Jika Anda ingin menggunakan [System.IO.File]::WriteAllLines(), Anda harus memberikan parameter kedua ke String[](jika tipe $MyFileis Object[]), dan juga menentukan path absolut dengan $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath), seperti:

$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)

Jika Anda ingin menggunakan [System.IO.File]::WriteAllText(), kadang-kadang Anda harus memasukkan parameter kedua ke dalam | Out-String |untuk menambahkan CRLF ke akhir setiap baris secara eksplisit (Terutama ketika Anda menggunakannya 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)

Atau Anda dapat menggunakan [Text.Encoding]::UTF8.GetBytes()dengan 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"

lihat: Cara menulis hasil ConvertTo-Csv ke file di UTF-8 tanpa BOM

SATO Yusuke
sumber
Petunjuk bagus; saran /: alternatif yang lebih sederhana $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)adalah Convert-Path $MyPath; jika Anda ingin memastikan CRLF tertinggal, cukup gunakan [System.IO.File]::WriteAllLines()bahkan dengan string input tunggal (tidak perlu untuk Out-String).
mklement0
0

Salah satu teknik yang saya gunakan adalah untuk mengarahkan output ke file ASCII menggunakan cmdlet Out-File .

Sebagai contoh, saya sering menjalankan skrip SQL yang membuat skrip SQL lain untuk dieksekusi di Oracle. Dengan pengalihan sederhana (">"), output akan berada di UTF-16 yang tidak dikenali oleh SQLPlus. Untuk mengatasi ini:

sqlplus -s / as sysdba "@create_sql_script.sql" |
Out-File -FilePath new_script.sql -Encoding ASCII -Force

Script yang dihasilkan kemudian dapat dieksekusi melalui sesi SQLPlus lain tanpa kekhawatiran Unicode:

sqlplus / as sysdba "@new_script.sql" |
tee new_script.log
Erik Anderson
sumber
4
Ya, -Encoding ASCIIhindari masalah BOM, tetapi Anda jelas hanya mendapatkan dukungan untuk karakter ASCII 7-bit . Karena ASCII adalah bagian dari UTF-8, file yang dihasilkan secara teknis juga merupakan file UTF-8 yang valid, tetapi semua karakter non-ASCII dalam input Anda akan dikonversi ke ?karakter literal .
mklement0
Jawaban ini membutuhkan lebih banyak suara. Ketidakcocokan sqlplus dengan BOM adalah penyebab banyak sakit kepala .
Amit Naidu
0

Ubah beberapa file dengan ekstensi menjadi UTF-8 tanpa 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)
}
Jaume Suñer Mut
sumber
0

Untuk alasan apa pun, WriteAllLinestelepon masih menghasilkan BOM untuk saya, dengan UTF8Encodingargumen BOMless dan tanpa itu. Tetapi yang berikut ini berhasil untuk saya:

$bytes = gc -Encoding byte BOMthetorpedoes.txt
[IO.File]::WriteAllBytes("$(pwd)\BOMthetorpedoes.txt", $bytes[3..($bytes.length-1)])

Saya harus membuat path file mutlak untuk berfungsi. Kalau tidak, ia menulis file ke Desktop saya. Juga, saya kira ini hanya berfungsi jika Anda tahu BOM Anda adalah 3 byte. Saya tidak tahu seberapa dapat diandalkan untuk mengharapkan format BOM yang diberikan / panjang berdasarkan pengkodean.

Juga, seperti yang ditulis, ini mungkin hanya berfungsi jika file Anda cocok dengan array powershell, yang tampaknya memiliki batas panjang beberapa nilai lebih rendah daripada [int32]::MaxValuedi komputer saya.

xdhmoore
sumber
1
WriteAllLinestanpa argumen penyandian tidak pernah menulis BOM itu sendiri , tetapi mungkin string Anda dimulai dengan BOM karakter ( U+FEFF), yang pada penulisan secara efektif membuat BOM UTF-8; misalnya: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)(hilangkan [char] 0xfeff + untuk melihat bahwa tidak ada BOM yang ditulis).
mklement0
1
Adapun penulisan yang tidak terduga ke lokasi yang berbeda: masalahnya adalah .NET framework biasanya memiliki direktori saat ini berbeda dari PowerShell; Anda dapat menyinkronkannya terlebih dahulu dengan [Environment]::CurrentDirectory = $PWD.ProviderPath, atau, sebagai alternatif yang lebih umum untuk "$(pwd)\..."pendekatan Anda (lebih baik:, "$pwd\..."bahkan lebih baik: "$($pwd.ProviderPath)\..."atau (Join-Path $pwd.ProviderPath ...)), gunakan(Convert-Path BOMthetorpedoes.txt)
:,
Terima kasih, saya tidak menyadari bahwa mungkin ada satu karakter BOM untuk konversi BOM UTF-8 seperti itu.
xdhmoore
1
Semua urutan byte BOM (tanda tangan Unicode) sebenarnya representasi byte encoding masing-masing karakter UnicodeU+FEFF abstrak tunggal .
mklement0
Ah baiklah. Tampaknya hal itu membuat semuanya lebih sederhana.
xdhmoore
-2

Dapat menggunakan di bawah ini untuk mendapatkan UTF8 tanpa BOM

$MyFile | Out-File -Encoding ASCII
Robin Wang
sumber
4
Tidak, itu akan mengubah output ke codepage ANSI saat ini (cp1251 atau cp1252, misalnya). Itu sama sekali bukan UTF-8!
ForNeVeR
1
Terima kasih Robin. Ini mungkin tidak berfungsi untuk menulis file UTF-8 tanpa BOM tetapi opsi -Encoding ASCII menghapus BOM. Dengan begitu saya bisa menghasilkan file kelelawar untuk gvim. File .bat tersandung di BOM.
Greg
3
@ForNeVeR: Anda benar bahwa pengkodean ASCIIbukan UTF-8, tetapi itu bukan codepage ANSI saat ini - Anda sedang memikirkan Default; ASCIIbenar-benar adalah pengkodean ASCII 7-bit, dengan codepoints = = 128 yang dikonversi ke ?instance literal .
mklement0
1
@ForNeVeR: Anda mungkin berpikir tentang "ANSI" atau " extended ASCII". Coba ini untuk memverifikasi bahwa -Encoding ASCIImemang hanya ASCII 7-bit: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)- ätelah ditransliterasikan ke a ?. Sebaliknya, -Encoding Default("ANSI") akan melestarikannya dengan benar.
mklement0
3
@rob Ini adalah jawaban sempurna untuk semua orang yang tidak membutuhkan utf-8 atau hal lain yang berbeda dari ASCII dan tidak tertarik untuk memahami penyandian dan tujuan unicode. Anda dapat menggunakannya sebagai utf-8 karena karakter utf-8 yang setara dengan semua karakter ASCII adalah identik (berarti mengonversi file ASCII ke file utf-8 menghasilkan file yang identik (jika tidak mendapat BOM)). Untuk semua yang memiliki karakter non-ASCII dalam teks mereka, jawaban ini hanya salah dan menyesatkan.
TNT
-3

Ini berfungsi untuk saya (gunakan "Default" dan bukan "UTF8"):

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

Hasilnya adalah ASCII tanpa BOM.

Krzysztof
sumber
1
Per dokumentasi Out-File yang menetapkan Defaultpengkodean akan menggunakan halaman kode ANSI sistem saat ini, yang bukan UTF-8, seperti yang saya perlukan.
M. Dudley
Ini sepertinya bekerja untuk saya, setidaknya untuk Ekspor-CSV. Jika Anda membuka file yang dihasilkan dalam editor yang tepat, pengkodean file adalah UTF-8 tanpa BOM, dan bukan Latin Latin ISO 9 seperti yang saya harapkan dengan ASCII
eythort
Banyak editor membuka file sebagai UTF-8 jika mereka tidak dapat mendeteksi pengkodean.
emptyother