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?
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?
Risposte:
L'uso della UTF8Encoding
classe .NET e il passaggio $False
al costruttore sembra funzionare:
$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)
sufficiente una riga . Questo WriteAllLines
sovraccarico scrive esattamente UTF8 senza BOM.
WriteAllLines
sembra $MyPath
essere assoluto.
WriteAllLines
ottiene la directory corrente da [System.Environment]::CurrentDirectory
. Se si apre PowerShell e si modifica la directory corrente (utilizzando cd
o Set-Location
), [System.Environment]::CurrentDirectory
non verrà modificato e il file finirà per essere nella directory errata. Puoi aggirare questo [System.Environment]::CurrentDirectory = (Get-Location).Path
.
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 System
chiarimenti sullo spazio dei nomi: verrà sostituito automaticamente per impostazione predefinita.)
[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
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.
-encoding utf8
per le mie esigenze.
-Encoding ASCII
evita 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 .
-encoding utf8
ancora UTF-8 con una distinta base. :(
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:
Out-File
in una pipeline.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 :
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()
}
}
A partire dalla versione 6, PowerShell supporta la UTF8NoBOM
codifica 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
$PSVersionTable.PSVersion
Quando si utilizza Set-Content
invece 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.
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);
}
[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
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
$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
).
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
-Encoding ASCII
evita 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 .
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)
}
Per qualsiasi motivo, le WriteAllLines
chiamate stavano ancora producendo una distinta base per me, con l' UTF8Encoding
argomento 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]::MaxValue
alla mia macchina.
WriteAllLines
senza 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).
[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)
U+FEFF
astratto .
Potrebbe essere utilizzato di seguito per ottenere UTF8 senza DBA
$MyFile | Out-File -Encoding ASCII
ASCII
dire 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 .
-Encoding ASCII
sia 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.
Questo funziona per me (usa "Default" invece di "UTF8"):
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath
Il risultato è ASCII senza DBA.
Default
codifica verrà utilizzata la codepage ANSI corrente del sistema, che non è UTF-8, come richiesto.