Utilizzo di PowerShell per scrivere un file in UTF-8 senza la distinta componenti


246

Out-File sembra forzare la distinta quando si utilizza UTF-8:

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

Come posso scrivere un file in UTF-8 senza BOM usando PowerShell?


23
BOM = Contrassegno ordine byte. Tre caratteri inseriti all'inizio di un file (0xEF, 0xBB, 0xBF) che assomigliano a "ï» ¿"
Signal15

40
Questo è incredibilmente frustrante. Anche i moduli di terze parti vengono inquinati, come provare a caricare un file su SSH? BOM! "Sì, corrompiamo ogni singolo file; sembra una buona idea." -Microsoft.
MichaelGG

3
La codifica predefinita è UTF8NoBOM che inizia con Powershell versione 6.0 docs.microsoft.com/en-us/powershell/module/…
Paul Shiryaev

Parla della rottura della compatibilità con le versioni precedenti ...
Dragas,

Risposte:


220

L'uso della UTF8Encodingclasse .NET e il passaggio $Falseal costruttore sembra funzionare:

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

42
Spero non sia l'unico modo.
Scott Muc,

114
È [System.IO.File]::WriteAllLines($MyPath, $MyFile)sufficiente una riga . Questo WriteAllLinessovraccarico scrive esattamente UTF8 senza BOM.
Roman Kuzmin,

6
Creata una richiesta di funzionalità MSDN qui: connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/…
Groostav

3
Nota che WriteAllLinessembra $MyPathessere assoluto.
sschuberth,

10
@xdhmoore WriteAllLinesottiene la directory corrente da [System.Environment]::CurrentDirectory. Se si apre PowerShell e si modifica la directory corrente (utilizzando cdo Set-Location), [System.Environment]::CurrentDirectorynon verrà modificato e il file finirà per essere nella directory errata. Puoi aggirare questo [System.Environment]::CurrentDirectory = (Get-Location).Path.
Shayan Toqraee,

79

Il modo corretto per ora è usare una soluzione raccomandata da @Roman Kuzmin nei commenti a @M. Dudley risponde :

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

(L'ho anche abbreviato un po 'eliminando inutili Systemchiarimenti sullo spazio dei nomi: verrà sostituito automaticamente per impostazione predefinita.)


2
Questo (per qualsiasi motivo) non ha rimosso la DBA per me, come ha fatto la risposta accettata
Liam,

@Liam, probabilmente qualche vecchia versione di PowerShell o .NET?
ForNeVeR,

1
Credo che le versioni precedenti della funzione .NET WriteAllLines abbiano scritto la distinta base per impostazione predefinita. Quindi potrebbe essere un problema di versione.
Bender the Greatest,

2
Confermato con le scritture con una DBA in Powershell 3, ma senza una DBA in Powershell 4. Ho dovuto usare la risposta originale di M. Dudley.
chazbot7,

2
Quindi funziona su Windows 10 dove è installato per impostazione predefinita. :) Inoltre, ha suggerito un miglioramento:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Johny Skovdal,

50

Ho pensato che non sarebbe stato UTF, ma ho appena trovato una soluzione abbastanza semplice che sembra funzionare ...

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

Per me questo si traduce in un utf-8 senza file bom indipendentemente dal formato sorgente.


8
Questo ha funzionato per me, tranne che -encoding utf8per le mie esigenze.
Chim Chimz,

1
Grazie mille. Sto lavorando con i dump di uno strumento - che conteneva delle schede al suo interno. UTF-8 non funzionava. ASCII ha risolto il problema. Grazie.
user1529294

44
Sì, -Encoding ASCIIevita il problema della distinta componenti, ma ovviamente ottieni solo caratteri ASCII a 7 bit . Dato che ASCII è un sottoinsieme di UTF-8, il file risultante è tecnicamente anche un file UTF-8 valido, ma tutti i caratteri non ASCII nell'input verranno convertiti in ?caratteri letterali .
mklement0

4
@ChimChimz Ho accidentalmente votato per eccesso il tuo commento, ma emetto -encoding utf8ancora UTF-8 con una distinta base. :(
TheDudeAbides

33

Nota: questa risposta si applica a Windows PowerShell ; al contrario, nell'edizione multipiattaforma PowerShell Core (v6 +), UTF-8 senza distinta base è la codifica predefinita , su tutti i cmdlet.
In altre parole: se si utilizza PowerShell [Core] versione 6 o successiva , si ottengono file UTF-8 senza BOM per impostazione predefinita (che è anche possibile richiedere esplicitamente con -Encoding utf8/ -Encoding utf8NoBOM, mentre si ottiene con la codifica -BOM con -utf8BOM).


Per completare la risposta semplice e pragmatica di M. Dudley (e la riformulazione più concisa di ForNeVeR ):

Per comodità, ecco la funzione avanzata Out-FileUtf8NoBom, un'alternativa basata sulla pipeline che imitaOut-File , il che significa:

  • puoi usarlo proprio come Out-Filein una pipeline.
  • gli oggetti di input che non sono stringhe vengono formattati come se fossero inviati alla console, proprio come con Out-File.

Esempio:

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

Si noti come (Get-Content $MyPath)è racchiuso in (...), il che assicura che l'intero file sia aperto, letto per intero e chiuso prima di inviare il risultato attraverso la pipeline. Ciò è necessario per poter riscrivere nello stesso file (aggiornarlo sul posto ).
In genere, tuttavia, questa tecnica non è consigliabile per 2 motivi: (a) l'intero file deve rientrare nella memoria e (b) se il comando viene interrotto, i dati andranno persi.

Una nota sull'uso della memoria :

  • La risposta di M. Dudley richiede che tutto il contenuto del file sia prima creato in memoria, il che può essere problematico con file di grandi dimensioni.
  • La funzione seguente migliora solo leggermente: tutti gli oggetti di input vengono ancora bufferizzati per primi, ma le loro rappresentazioni di stringa vengono quindi generate e scritte nel file di output una per una.

Codice sorgente diOut-FileUtf8NoBom (disponibile anche come Gist con licenza 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()
  }

}

16

A partire dalla versione 6, PowerShell supporta la UTF8NoBOMcodifica sia per set-content che per out-file e la utilizza anche come codifica predefinita.

Quindi nell'esempio sopra dovrebbe essere semplicemente così:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath

@ RaúlSalinas-Monteagudo su quale versione sei?
John Bentley,

Bello. Versione di controllo FYI con$PSVersionTable.PSVersion
KCD

14

Quando si utilizza Set-Contentinvece di Out-File, è possibile specificare la codifica Byte, che può essere utilizzata per scrivere un array di byte in un file. Questo in combinazione con una codifica UTF8 personalizzata che non emette la distinta base fornisce il risultato desiderato:

# 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

La differenza con l'utilizzo [IO.File]::WriteAllLines()o simile è che dovrebbe funzionare bene con qualsiasi tipo di elemento e percorso, non solo percorsi di file effettivi.


5

Questo script converte, in UTF-8 senza BOM, tutti i file .txt in DIRECTORY1 e li emette in DIRECTORY2

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

Questo fallisce senza alcun preavviso. Quale versione di PowerShell dovrei usare per eseguirlo?
darksoulsong,

3
La soluzione WriteAllLines funziona perfettamente per file di piccole dimensioni. Tuttavia, ho bisogno di una soluzione per file più grandi. Ogni volta che provo a usarlo con un file più grande, visualizzo un errore 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" 
    }  

Origine Come rimuovere il contrassegno di ordine di byte UTF8 (BOM) da un file usando PowerShell


2

Se si desidera utilizzare [System.IO.File]::WriteAllLines(), è necessario eseguire il cast del secondo parametro su String[](se il tipo di $MyFileè Object[]) e specificare anche il percorso assoluto con $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath), ad esempio:

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

Se si desidera utilizzare [System.IO.File]::WriteAllText(), a volte è necessario reindirizzare il secondo parametro | Out-String |per aggiungere esplicitamente CRLF alla fine di ogni riga (soprattutto quando li si utilizza con 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)

Oppure puoi usare [Text.Encoding]::UTF8.GetBytes()con 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"

vedi: Come scrivere il risultato di ConvertTo-Csv in un file in UTF-8 senza BOM


Buoni suggerimenti; suggerimenti /: l'alternativa più semplice a $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)è Convert-Path $MyPath; se si desidera garantire un CRLF finale, utilizzare semplicemente [System.IO.File]::WriteAllLines()anche con una singola stringa di input (non è necessario Out-String).
mklement0,

0

Una tecnica che utilizzo consiste nel reindirizzare l'output in un file ASCII utilizzando il cmdlet Out-File .

Ad esempio, eseguo spesso script SQL che creano un altro script SQL da eseguire in Oracle. Con il reindirizzamento semplice (">"), l'output sarà in UTF-16 che non è riconosciuto da SQLPlus. Per aggirare questo:

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

Lo script generato può quindi essere eseguito tramite un'altra sessione SQLPlus senza preoccupazioni Unicode:

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

4
Sì, -Encoding ASCIIevita il problema della distinta componenti, ma ovviamente ottieni supporto solo per i caratteri ASCII a 7 bit . Dato che ASCII è un sottoinsieme di UTF-8, il file risultante è tecnicamente anche un file UTF-8 valido, ma tutti i caratteri non ASCII nell'input verranno convertiti in ?caratteri letterali .
mklement0,

Questa risposta richiede più voti. L'incompatibilità di sqlplus con la distinta componenti è una causa di molti mal di testa .
Amit Naidu,

0

Modifica più file per estensione in UTF-8 senza 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)
}

0

Per qualsiasi motivo, le WriteAllLineschiamate stavano ancora producendo una distinta base per me, con l' UTF8Encodingargomento BOMless e senza di essa. Ma per me ha funzionato:

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

Ho dovuto rendere assoluto il percorso del file affinché funzioni. Altrimenti ha scritto il file sul mio desktop. Inoltre, suppongo che questo funzioni solo se sai che la tua DBA è di 3 byte. Non ho idea di quanto sia affidabile aspettarsi un determinato formato / lunghezza della distinta base basato sulla codifica.

Inoltre, come scritto, questo probabilmente funziona solo se il tuo file si inserisce in un array PowerShell, che sembra avere un limite di lunghezza di un valore inferiore rispetto [int32]::MaxValuealla mia macchina.


1
WriteAllLinessenza un argomento di codifica non scrive mai una DBA stessa , ma è concepibile che la tua stringa abbia avuto inizio con il carattere DBA ( U+FEFF), che sulla scrittura ha effettivamente creato una DBA UTF-8; ad es .: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)(omettere il [char] 0xfeff + per vedere che nessuna DBA è scritta).
mklement0

1
Per quanto riguarda la scrittura inaspettata in una posizione diversa: il problema è che il framework .NET in genere ha una directory corrente diversa da PowerShell; puoi prima sincronizzarli con [Environment]::CurrentDirectory = $PWD.ProviderPath, o, come alternativa più generica al tuo "$(pwd)\..."approccio (meglio "$pwd\...""$($pwd.ProviderPath)\..."(Join-Path $pwd.ProviderPath ...)(Convert-Path BOMthetorpedoes.txt)
:,

Grazie, non mi ero reso conto che ci potesse essere un singolo personaggio della BOM in una conversione della BOM UTF-8 del genere.
xdhmoore,

1
Tutte le sequenze di byte DBA (firme Unicode) sono in realtà la rappresentazione di byte della codifica corrispondente del singolo carattere UnicodeU+FEFF astratto .
mklement0,

Ah ok. Questo sembra semplificare le cose.
xdhmoore

-2

Potrebbe essere utilizzato di seguito per ottenere UTF8 senza DBA

$MyFile | Out-File -Encoding ASCII

4
No, convertirà l'output nella tabella codici ANSI corrente (ad esempio cp1251 o cp1252). Non è affatto UTF-8!
ForNeVeR,

1
Grazie Robin. Questo potrebbe non aver funzionato per la scrittura di un file UTF-8 senza la distinta componenti, ma l'opzione -Encoding ASCII ha rimosso la distinta componenti. In questo modo ho potuto generare un file bat per gvim. Il file .bat è scattato nella distinta componenti.
Greg,

3
@ForNeVeR: Hai ragione nel ASCIIdire che la codifica non è UTF-8, ma non è anche l'attuale tabella codici ANSI - stai pensando Default; ASCIIè veramente una codifica ASCII a 7 bit, con punti di codice> = 128 che vengono convertiti in ?istanze letterali .
mklement0

1
@ForNeVeR: Probabilmente stai pensando a "ANSI" o " ASCII esteso ". Prova questo per verificare che -Encoding ASCIIsia effettivamente solo ASCII a 7 bit: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)- äè stato traslitterato in a ?. Al contrario, -Encoding Default("ANSI") lo preserverebbe correttamente.
mklement0,

3
@rob Questa è la risposta perfetta per tutti coloro che non hanno bisogno di utf-8 o di qualsiasi altra cosa diversa da ASCII e non sono interessati a comprendere le codifiche e lo scopo dell'unicode. Puoi usarlo come utf-8 perché gli equivalenti caratteri utf-8 in tutti i caratteri ASCII sono identici (significa che convertire un file ASCII in un file utf-8 risulta in un file identico (se non ottiene alcuna DBA)). Per tutti coloro che hanno caratteri non ASCII nel loro testo questa risposta è semplicemente falsa e fuorviante.
TNT,

-3

Questo funziona per me (usa "Default" invece di "UTF8"):

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

Il risultato è ASCII senza DBA.


1
Secondo la documentazione Out-File che specifica la Defaultcodifica verrà utilizzata la codepage ANSI corrente del sistema, che non è UTF-8, come richiesto.
M. Dudley,

Questo sembra funzionare per me, almeno per Export-CSV. Se si apre il file risultante in un editor appropriato, la codifica del file è UTF-8 senza distinta base e non ISO 9 in latino occidentale come mi sarei aspettato con ASCII
eythort

Molti editor aprono il file come UTF-8 se non riescono a rilevare la codifica.
Vuoto altro il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.