web-dev-qa-db-fra.com

Utilisation de PowerShell pour écrire un fichier au format UTF-8 sans la nomenclature

Out-File semble forcer la nomenclature lors de l'utilisation de UTF-8:

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

Comment puis-je écrire un fichier au format UTF-8 sans nomenclature sous PowerShell?

219
M. Dudley

Utiliser la classe UTF8Encoding de .NET et passer _$False_ au constructeur semble fonctionner:

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

La méthode appropriée consiste à utiliser une solution recommandée par @Roman Kuzmin dans les commentaires à @M. Dudley réponse :

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

(je l'ai aussi un peu raccourci en supprimant la clarification inutile de System d'espace de noms - elle sera automatiquement remplacée par défaut.)

69
ForNeVeR

Je pensais que ce ne serait pas UTF, mais je viens de trouver une solution assez simple qui semble fonctionner ...

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

Pour moi, cela se traduit par un fichier utf-8 sans fichier bom, quel que soit le format source.

39
Lenny

Remarque: cette réponse s'applique à Windows PowerShell; en revanche, dans l'édition multiplate-forme Core, UTF-8 sans nomenclature est le codage par défaut.

Pour compléter réponse simple et pragmatique de M. Dudley (et reformulation plus concise de ForNeVeR ):

Pour plus de commodité, voici la fonction avancée Out-FileUtf8NoBom, , une alternative basée sur un pipeline imitant Out-File, ce qui signifie:

  • vous pouvez l'utiliser comme Out-File dans un pipeline.
  • les objets d'entrée qui ne sont pas des chaînes sont formatés comme ils le seraient si vous les envoyiez à la console, comme avec Out-File.

Exemple:

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

Notez comment (Get-Content $MyPath) est placé entre (...), ce qui garantit que le fichier entier est ouvert, lu en entier et fermé avant d’envoyer le résultat via le pipeline. Cela est nécessaire pour pouvoir écrire dans le fichier same (mettez-le à jour in place).
Toutefois, cette technique n’est généralement pas recommandée pour deux raisons: (a) le fichier entier doit tenir dans la mémoire et (b) si la commande est interrompue, les données seront perdues.

Une note sur l'utilisation de la mémoire :

  • La réponse de M. Dudley exige que tout le contenu du fichier soit d'abord créé en mémoire, ce qui peut poser problème avec les gros fichiers.
  • La fonction ci-dessous améliore légèrement ce problème: tous les objets en entrée sont toujours d'abord mis en mémoire tampon, mais leurs représentations sous forme de chaîne sont ensuite générées et écrites un par un dans le fichier de sortie.

Code source de Out-FileUtf8NoBom (également disponible en tant que Gist sous licence GIT ):

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

}
26
mklement0

Lorsque vous utilisez Set-Content au lieu de Out-File, vous pouvez spécifier le codage Byte, qui peut être utilisé pour écrire un tableau d'octets dans un fichier. Ceci en combinaison avec un encodage UTF8 personnalisé qui n'émet pas la nomenclature donne le résultat souhaité:

# 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 différence avec [IO.File]::WriteAllLines() ou similaire est que cela devrait fonctionner avec n'importe quel type d'élément et de chemin, pas seulement les chemins de fichiers réels.

8
Lucero

À partir de version 6 powershell prend en charge le codage UTF8NoBOM à la fois pour set-content et out- fichier et l’utilise même comme encodage par défaut.

Donc, dans l'exemple ci-dessus, cela devrait simplement être comme ceci:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
7
sc911

Ce script convertira, en UTF-8 sans BOM, tous les fichiers .txt dans DIRECTORY1 et les affichera dans DIRECTORY2.

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}
4
jamhan
    [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" 
    }  

Source Comment supprimer la marque de nom d'octet UTF8 d'un fichier à l'aide de PowerShell

1
frank tan

Pour une raison quelconque, les appels WriteAllLines produisaient toujours une nomenclature pour moi, avec l'argument BOMless UTF8Encoding et sans lui. Mais ce qui suit a fonctionné pour moi:

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

Je devais rendre le chemin du fichier absolu pour que cela fonctionne. Sinon, il a écrit le fichier sur mon bureau. De plus, je suppose que cela ne fonctionne que si vous savez que votre nomenclature est de 3 octets. Je ne sais pas à quel point il est fiable de s'attendre à un format/une longueur de nomenclature donné en fonction du codage.

De plus, comme cela est écrit, cela ne fonctionnera probablement que si votre fichier s'inscrit dans un tableau powershell, qui semble avoir une limite de longueur inférieure à [int32]::MaxValue sur ma machine.

0
xdhmoore

Une technique que j'utilise consiste à rediriger la sortie vers un fichier ASCII à l'aide de la cmdlet Out-File.

Par exemple, j'exécute souvent des scripts SQL qui créent un autre script SQL à exécuter dans Oracle. Avec une simple redirection (">"), la sortie sera en UTF-16 qui n’est pas reconnue par SQLPlus. Pour contourner ce problème:

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

Le script généré peut ensuite être exécuté via une autre session SQLPlus sans aucun souci Unicode:

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

Remplacez plusieurs fichiers par extension par UTF-8 sans nomenclature:

$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
Jaume Suñer Mut

Si vous souhaitez utiliser [System.IO.File]::WriteAllLines(), vous devez transtyper le deuxième paramètre en String[] (si le type de $MyFile est Object[]), et spécifier également le chemin absolu avec $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath), comme suit:

$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 vous souhaitez utiliser [System.IO.File]::WriteAllText(), vous devez parfois diriger le second paramètre dans | Out-String | pour ajouter des CRLF à la fin de chaque ligne (de manière explicite) (en particulier lorsque vous les utilisez avec 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)

Ou vous pouvez utiliser [Text.Encoding]::UTF8.GetBytes() avec 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"

voir: Comment écrire le résultat de ConvertTo-Csv dans un fichier en UTF-8 sans nomenclature

0
SATO Yusuke