BOM olmadan UTF-8'de dosya yazmak için PowerShell kullanma


246

Out-File UTF-8 kullanırken Malzeme Listesini zorladı gibi görünüyor:

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

PowerShell kullanarak BOM olmadan UTF-8'de nasıl dosya yazabilirim?


23
BOM = Bayt Sırası İşareti. Bir dosyanın başlangıcına "ï» ¿"gibi görünen üç karakter (0xEF, 0xBB, 0xBF) yerleştirildi
Signal15

40
Bu inanılmaz derecede sinir bozucu. Üçüncü taraf modülleri bile SSH üzerinden dosya yüklemeye çalışmak gibi kirlenir mi? BOM! "Evet, hadi her dosyayı bozalım, kulağa iyi bir fikir gibi geliyor." -Microsoft.
MichaelGG

3
Varsayılan kodlama, Powershell sürüm 6.0 ile başlayan UTF8NoBOM'dur docs.microsoft.com/tr-tr/powershell/module/…
Paul Shiryaev

Geriye dönük uyumluluğu bozma hakkında konuşun ...
Dragas

Yanıtlar:


220

.NET UTF8Encodingsınıfını kullanmak ve yapıcıya $Falsegeçmek işe yarıyor gibi görünüyor:

$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)

42
Ugh, umarım tek yol bu değildir.
Scott Muc

114
Bir satır [System.IO.File]::WriteAllLines($MyPath, $MyFile)yeterlidir. Bu WriteAllLinesaşırı yük BOM olmadan tam olarak UTF8 yazar.
Roman Kuzmin

6
Burada bir MSDN özelliği isteği oluşturuldu: connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/…
Groostav

3
Mutlak olması gerektiği WriteAllLinesgörülüyor $MyPath.
17'de sschuberth

10
@xdhmoore WriteAllLinesgeçerli dizini alır [System.Environment]::CurrentDirectory. PowerShell'i açar ve geçerli dizininizi değiştirirseniz ( cdveya kullanarak Set-Location), [System.Environment]::CurrentDirectorydeğiştirilmez ve dosya yanlış dizinde olur. Bu sorunu çözebilirsin [System.Environment]::CurrentDirectory = (Get-Location).Path.
Shayan Toqraee

79

Uygun bugün itibariyle yolu @Roman Kuzmin tarafından önerilen bir çözüm kullanmaktır yorumlarda .m için. Dudley cevabı :

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

(Ayrıca gereksiz Systemad alanı açıklamalarını sıyırıp biraz kısalttım - varsayılan olarak otomatik olarak değiştirilecektir.)


2
Bu (ne sebeple olursa olsun) benim için ürün ağacını kaldırmadı, kabul edilen cevap gibi
Liam

@Liam, muhtemelen bazı eski PowerShell veya .NET sürümü?
ForNeVeR

1
.NET WriteAllLines işlevinin eski sürümlerinin varsayılan olarak Malzeme Listesini yazdığını düşünüyorum. Yani bir sürüm sorunu olabilir.
Greatest Bender

2
Powershell 3'te bir BOM ile yazıyor, ancak Powershell 4'te bir BOM olmadan yazıyor. M. Dudley'nin orijinal cevabını kullanmak zorunda kaldım.
chazbot7

2
Bu nedenle, varsayılan olarak yüklendiği Windows 10'da çalışır. :) Ayrıca, önerilen gelişme:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Johny Skovdal

50

Bunun UTF olmayacağını düşündüm, ama işe yarayan oldukça basit bir çözüm buldum ...

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

Benim için bu, kaynak biçimine bakılmaksızın bom dosyası olmayan bir utf-8 ile sonuçlanır.


8
Bu benim için çalıştı, ancak -encoding utf8ihtiyacım için kullandım .
Chim Chimz

1
Çok teşekkür ederim. İçinde sekmeleri olan bir aracın döküm günlükleriyle çalışıyorum. UTF-8 çalışmıyor. ASCII sorunu çözdü. Teşekkürler.
user1529294

44
Evet, -Encoding ASCIIürün ağacı sorununu önler, ancak sadece 7 bit ASCII karakterleri alırsınız . ASCII'nin UTF-8'in bir alt kümesi olduğu göz önüne alındığında, sonuçta elde edilen dosya teknik olarak geçerli bir UTF-8 dosyasıdır, ancak girişinizdeki tüm ASCII olmayan karakterler değişmez ?karakterlere dönüştürülecektir .
mklement0

4
@ChimChimz Yanlışlıkla yorumunuzu oyladım, ancak -encoding utf8yine de UTF-8'i bir Malzeme Listesi ile çıktı. :(
TheDudeAbides

33

Not: Bu cevap Windows PowerShell için geçerlidir ; Buna göre, çapraz platform PowerShell içinde Çekirdek baskısında (v6 +), UTF-8 BOM olmadan olduğu varsayılan kodlama tüm cmdlets karşısında.
Başka bir deyişle: Eğer kullanıyorsanız PowerShell [Çekirdek] sürüm 6 veya üstü , sen olsun BOM-az UTF-8 dosyaları varsayılan olarak (siz de açık bir şekilde belirtilen isteyebilir -Encoding utf8/ -Encoding utf8NoBOMelde edersiniz oysa ile birlikte -BOM kodlama -utf8BOM).


Tamamlamak için M. Dudley kendi basit ve pragmatik bir cevap (ve ForNeVeR en fazla özlü yeniden formüle ):

Kolaylık sağlamak için, burada taklit eden boru hattı tabanlı bir alternatif olan gelişmiş işlev Out-FileUtf8NoBom, yani:Out-File

  • tıpkı Out-Filebir boru hattında olduğu gibi kullanabilirsiniz .
  • dize olmayan giriş nesneleri, tıpkı olduğu gibi, konsola gönderirseniz olduğu gibi biçimlendirilir Out-File.

Misal:

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

Nasıl Not (Get-Content $MyPath)içine alınır (...), hangi tüm dosya boru hattıyla sonucu göndermeden önce, açılan tam olarak okunur ve kapalı olmasını sağlar. Bu, aynı dosyaya geri yazabilmek için gereklidir ( yerinde güncelleyin ).
Genel olarak, bu teknik 2 nedenden dolayı tavsiye edilmez: (a) tüm dosya belleğe sığmalıdır ve (b) komut kesilirse veriler kaybolur.

Bellek kullanımına ilişkin bir not :

  • M. Dudley'nin kendi cevabı , önce tüm dosya içeriğinin hafızada oluşturulmasını gerektirir, bu da büyük dosyalarla sorun yaratabilir.
  • Aşağıdaki işlev bu konuda birazcık iyileşir: önce tüm girdi nesneleri arabelleğe alınır, ancak dize gösterimleri daha sonra oluşturulur ve çıktı dosyasına tek tek yazılır.

Kaynak koduOut-FileUtf8NoBom ( MIT lisanslı Gist olarak da mevcuttur ):

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

}

16

Sürüm 6'dan başlayarak powershell, set-content ve out-fileUTF8NoBOM için kodlamayı destekler ve hatta bunu varsayılan kodlama olarak kullanır.

Yani yukarıdaki örnekte bu sadece şöyle olmalıdır:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath

@ RaúlSalinas-Monteagudo Hangi versiyondasın?
John Bentley

Güzel. FYI çek versiyonu ile$PSVersionTable.PSVersion
KCD

14

Set-ContentBunun yerine kullanırken Out-File, Bytebir dosyaya bir bayt dizisi yazmak için kullanılabilecek kodlamayı belirtebilirsiniz . Bu, Malzeme Listesini yaymayan özel bir UTF8 kodlaması ile birlikte istenen sonucu verir:

# 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

Kullanmanın [IO.File]::WriteAllLines()veya benzerinin arasındaki fark , yalnızca gerçek dosya yollarıyla değil, her tür öğe ve yolla iyi çalışması gerektiğidir.


5

Bu komut dosyası, BOM olmadan UTF-8'e, DIRECTORY1 içindeki tüm .txt dosyalarını dönüştürecek ve DIRECTORY2 dizinine çıkaracaktır.

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}

Bu herhangi bir uyarı yapmadan başarısız olur. Çalıştırmak için hangi powershell sürümünü kullanmalıyım?
darksoulsong

3
WriteAllLines çözümü küçük dosyalar için harika çalışır. Ancak, daha büyük dosyalar için bir çözüme ihtiyacım var. Bunu daha büyük bir dosyayla kullanmayı denediğimde OutOfMemory hatası alıyorum.
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" 
    }  

Kaynak PowerShell kullanarak bir dosyadan UTF8 Bayt Sipariş İşareti (BOM) nasıl kaldırılır


2

Kullanmak istiyorsanız [System.IO.File]::WriteAllLines(), ikinci parametreyi String[](türü $MyFileise Object[]) olarak kullanmanız ve ayrıca şu şekilde mutlak yol belirtmeniz gerekir $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)

Kullanmak isterseniz [System.IO.File]::WriteAllText(), bazen boru içine ikinci parametre olmalıdır | Out-String |explictly (eğer bunları kullanmak Özellikle her satırın sonuna CRLFs eklemek için 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)

Yoksa kullanabilirsiniz [Text.Encoding]::UTF8.GetBytes()ile 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"

bkz: ConvertTo-Csv'nin sonucunu BOM olmadan UTF-8'deki bir dosyaya yazma


İyi işaretçiler; öneri /: daha basit bir alternatif $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)olduğu Convert-Path $MyPath; bir izleyen CRLF sağlamak istiyorsanız, tek bir giriş dizesi [System.IO.File]::WriteAllLines()ile bile kullanın (gerek yok ). Out-String
mklement0

0

Kullandığım tekniklerden biri çıktıyı Out-File cmdlet'ini kullanarak bir ASCII dosyasına yeniden yönlendirmektir .

Örneğin, genellikle Oracle'da yürütmek için başka bir SQL komut dosyası oluşturan SQL komut dosyaları çalıştırıyorum. Basit yeniden yönlendirme (">") ile çıkış, SQLPlus tarafından tanınmayan UTF-16'da olacaktır. Bu sorunu çözmek için:

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

Oluşturulan komut dosyası daha sonra herhangi bir Unicode endişesi olmadan başka bir SQLPlus oturumu aracılığıyla yürütülebilir:

sqlplus / as sysdba "@new_script.sql" |
tee new_script.log

4
Evet, -Encoding ASCIIürün ağacı sorununu önler, ancak açıkçası yalnızca 7 bit ASCII karakterleri için destek alırsınız . ASCII'nin UTF-8'in bir alt kümesi olduğu göz önüne alındığında, sonuçta elde edilen dosya teknik olarak geçerli bir UTF-8 dosyasıdır, ancak girişinizdeki tüm ASCII olmayan karakterler değişmez ?karakterlere dönüştürülecektir .
mklement0

Bu cevabın daha fazla oy alması gerekiyor. BOM ile sqlplus uyumsuzluğu birçok baş ağrısının bir nedenidir .
Amit Naidu

0

Birden çok dosyayı BOM olmadan UTF-8 olarak değiştirin:

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

0

Her ne sebeple olursa olsun, WriteAllLinesçağrılar hala BOMless UTF8Encodingargümanı ve onsuz bir BOM üretiyordu . Ama aşağıdakiler benim için çalıştı:

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

Dosya yolunu çalışması için mutlak hale getirmek zorunda kaldım. Aksi takdirde dosyayı Masaüstüme yazdı. Ayrıca, bunun yalnızca ürün ağacınızın 3 bayt olduğunu biliyorsanız çalışır. Ben kodlama dayalı verilen bir ürün ağacı biçimi / uzunluğu beklemek ne kadar güvenilir bir fikrim yok.

Ayrıca, yazıldığı gibi, bu muhtemelen sadece dosyanız bir powershell dizisine sığarsa çalışır, bu da [int32]::MaxValuebenim makinemden daha düşük bir değer uzunluğu sınırı var gibi görünüyor .


1
WriteAllLinesbir kodlama argümanı bir BOM yazar asla olmadan kendisi ama bu senin tabi mümkünse dize BOM ile başlamak oldu karakteri ( U+FEFFüzerinde etkili UTF-8 BOM oluşturulan yazılı); örneğin: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)( hiçbir Malzeme Listesinin yazılmadığını [char] 0xfeff + görmek için) ' i atlayın ).
mklement0

1
Beklenmedik bir şekilde farklı bir konuma yazma gelince: sorun, .NET çerçevesinin genellikle PowerShell'den farklı bir geçerli dizine sahip olmasıdır; bunları önce senkronize edebilir [Environment]::CurrentDirectory = $PWD.ProviderPathveya "$(pwd)\..."yaklaşımınıza daha genel bir alternatif olarak (daha iyi:, "$pwd\..."daha da iyi: "$($pwd.ProviderPath)\..."veya (Join-Path $pwd.ProviderPath ...)), kullanın(Convert-Path BOMthetorpedoes.txt)
mklement0

Teşekkürler, UTF-8 BOM dönüşümüne tek bir BOM karakteri olabileceğini fark etmedim.
xdhmoore

1
Tüm BOM bayt dizileri (Unicode imzaları) aslında ilgili kodlamanın soyut tek Unicode karakterininU+FEFF bayt temsilidir .
mklement0

Ah tamam. Bu işleri kolaylaştırıyor gibi görünüyor.
xdhmoore

-2

BOM olmadan UTF8 almak için aşağıda kullanılabilir

$MyFile | Out-File -Encoding ASCII

4
Hayır, çıkışı mevcut ANSI kod sayfasına dönüştürür (örneğin, cp1251 veya cp1252). Hiç UTF-8 değil!
ForNeVeR

1
Teşekkürler Robin. Bu, Malzeme Listesi olmadan bir UTF-8 dosyası yazmak için işe yaramamış olabilir, ancak -Encoding ASCII seçeneği Malzeme Listesini kaldırmıştır. Bu şekilde gvim için bir yarasa dosyası oluşturabilirim. .Bat dosyası ürün ağacında açılır.
Greg

3
@ForNeVeR: Kodlamanın ASCIIUTF-8 olmadığı doğru , ancak şu anki ANSI kod sayfası değil - siz düşünüyorsunuz Default; ASCIIgerçekte 7 bitlik ASCII kodlamasıdır ve kod noktaları> = 128 değişmez ?örneklere dönüştürülür .
mklement0

1
@ForNeVeR: Muhtemelen "ANSI" veya " genişletilmiş ASCII" yi düşünüyorsunuz. -Encoding ASCIIGerçekten 7-bit ASCII olduğunu doğrulamak için bunu deneyin : 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)- äa'ya dönüştürülmüştür ?. Aksine, -Encoding Default("ANSI") doğru şekilde koruyacaktır.
mklement0

3
@rob Bu, utf-8'e veya ASCII'den farklı olan ve kodlamaları ve unicode'un amacını anlamakla ilgilenmeyen başka bir şeye ihtiyaç duymayan herkes için mükemmel bir cevaptır. Bunu utf-8 olarak kullanabilirsiniz , çünkü tüm ASCII karakterlerine eşdeğer utf-8 karakterleri aynıdır (ASCII dosyasını utf-8 dosyasına dönüştürmek aynı bir dosyaya neden olur (eğer BOM yoksa). Metinlerinde ASCII olmayan karakterler olan herkes için bu cevap sadece yanlış ve yanıltıcıdır.
TNT

-3

Bu benim için çalışıyor ("UTF8" yerine "Varsayılan" kullanın):

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

Sonuç BOM'suz ASCII olur.


1
Başına dışı Dosya belgelerinde belirtilmesi DefaultBen gerektiği gibi, UTF-8 değil sistemin geçerli ANSI kod sayfası, kullanacağı kodlamayı.
M. Dudley

Bu benim için, en azından Export-CSV için işe yarıyor gibi görünüyor. Ortaya çıkan dosyayı uygun bir düzenleyicide açarsanız, dosya kodlaması
BOM'siz

Birçok düzenleyici, kodlamayı algılayamazlarsa dosyayı UTF-8 olarak açar.
emptyother
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.