Usar PowerShell para escribir un archivo en UTF-8 sin la lista de materiales

Out-File parece forzar la lista de materiales cuando se usa UTF-8:

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

¿Cómo puedo escribir un archivo en UTF-8 sin BOM usando PowerShell?

Usar la clase UTF8Encoding de .NET y pasar $False al constructor parece funcionar:

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

La forma correcta a partir de ahora es usar una solución recomendada por @Roman Kuzmin en comentarios a @M. Dudley responde :

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

(También lo he acortado un poco eliminando la aclaración innecesaria System espacio de nombres del System ; se sustituirá automáticamente de manera predeterminada).

Pensé que esto no sería UTF, pero acabo de encontrar una solución bastante simple que parece funcionar …

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

Para mí, esto da como resultado un archivo utf-8 sin bom sin importar el formato fuente.

Para complementar la respuesta simple y pragmática de M. Dudley (y la reformulación más concisa de ForNeveR ):

Para su comodidad, aquí está la función avanzada Out-FileUtf8NoBom , una alternativa basada en canalizaciones que imita a Out-File , lo que significa:

  • puede usarlo como Out-File en una tubería.
  • los objetos de entrada que no son cadenas tienen el formato que tendrían si los enviara a la consola, al igual que con Out-File .

Ejemplo:

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

Observe cómo (Get-Content $MyPath) está encerrado en (...) , lo que garantiza que todo el archivo se abra, se lea por completo y se cierre antes de enviar el resultado a través de la canalización. Esto es necesario para poder volver a escribir en el mismo archivo (actualizarlo en su lugar ).
En general, sin embargo, esta técnica no es aconsejable por dos razones: (a) todo el archivo debe caber en la memoria y (b) si se interrumpe el comando, se perderán los datos.

Una nota sobre el uso de la memoria :

  • La propia respuesta de M. Dudley requiere que todo el contenido del archivo se construya primero en la memoria, lo que puede ser problemático con archivos grandes.
  • La función siguiente mejora ligeramente esto: todos los objetos de entrada todavía se almacenan en el búfer primero, pero sus representaciones de cadena se generan y escriben en el archivo de salida una por una.

Código fuente de Out-FileUtf8NoBom (también disponible como Gist licenciado por 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) # Caveat: .NET Core doesn'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() } } 

Este script convertirá, a UTF-8 sin BOM, todos los archivos .txt en DIRECTORY1 y los enviará a DIRECTORY2

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

Al usar Set-Content lugar de Out-File , puede especificar el Byte encoding, que se puede usar para escribir una matriz de bytes en un archivo. Esto en combinación con una encoding UTF8 personalizada que no emite la BOM da el resultado deseado:

 # 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 diferencia con el uso de [IO.File]::WriteAllLines() o similar es que debería funcionar bien con cualquier tipo de elemento y ruta, no solo rutas de archivos reales.

Tenía el mismo problema. Eso hizo el truco para mí:

 $MyFile | Out-File -Encoding Oem $MyPath 

Al abrir el archivo con Visual Studio Code o Notepad ++ se muestra como UTF-8

Una técnica que utilizo es redirigir el resultado a un archivo ASCII utilizando el cmdlet Out-File .

Por ejemplo, a menudo ejecuto scripts SQL que crean otro script SQL para ejecutar en Oracle. Con una redirección simple (“>”), la salida estará en UTF-16 que SQLPlus no reconoce. Para solucionar esto:

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

La secuencia de comandos generada se puede ejecutar a través de otra sesión SQLPlus sin preocupaciones Unicode:

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

Cambie varios archivos por extensión a UTF-8 sin 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) } 

Por alguna razón, las llamadas a WriteAllLines aún producían una lista de materiales para mí, con el argumento BOMless UTF8Encoding y sin él. Pero lo siguiente funcionó para mí:

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

Tenía que hacer que la ruta del archivo sea absoluta para que funcione. De lo contrario, escribió el archivo en mi escritorio. Además, supongo que esto solo funciona si sabes que tu lista de materiales es de 3 bytes. No tengo idea de qué tan confiable es esperar un formato / longitud de lista de materiales dada en función de la encoding.

Además, tal como está escrito, esto probablemente solo funcione si su archivo se ajusta a una matriz powershell, que parece tener un límite de longitud de algún valor inferior a [int32]::MaxValue en mi máquina.

  [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" } 

Fuente Cómo quitar UTF8 Byte Order Mark (BOM) de un archivo usando PowerShell

Si desea utilizar [System.IO.File]::WriteAllLines() , debe convertir el segundo parámetro a String[] (si el tipo de $MyFile es Object[] ), y también especificar la ruta absoluta con $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath) , como:

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

Si desea utilizar [System.IO.File]::WriteAllText() , a veces debe canalizar el segundo parámetro en | Out-String | | Out-String | para agregar CRLF al final de cada línea de manera explícita (especialmente cuando los usa 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) 

O puede usar [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" 

ver: Cómo escribir el resultado de ConvertTo-Csv en un archivo en UTF-8 sin BOM

Podría usar debajo para obtener UTF8 sin BOM

 $MyFile | Out-File -Encoding ASCII 

Este funciona para mí (use “Predeterminado” en lugar de “UTF8”):

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

El resultado es ASCII sin BOM.