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

246

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

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

Comment puis-je écrire un fichier en UTF-8 sans nomenclature à l'aide de PowerShell?

M. Dudley
la source
23
BOM = Byte-Order Mark. Trois caractères placés au début d'un fichier (0xEF, 0xBB, 0xBF) qui ressemblent à "ï» ¿"
Signal15
40
C'est incroyablement frustrant. Même les modules tiers sont pollués, comme essayer de télécharger un fichier via SSH? BOM! "Ouais, corrompons chaque fichier; cela semble être une bonne idée." -Microsoft.
MichaelGG
3
L'encodage par défaut est UTF8NoBOM à partir de Powershell version 6.0 docs.microsoft.com/en-us/powershell/module/…
Paul Shiryaev
Parlez de briser la compatibilité descendante ...
Dragas

Réponses:

220

Utiliser la UTF8Encodingclasse de .NET et passer $Falseau constructeur semble fonctionner:

$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)
M. Dudley
la source
42
Ugh, j'espère que ce n'est pas le seul moyen.
Scott Muc
114
Une ligne [System.IO.File]::WriteAllLines($MyPath, $MyFile)suffit. Cette WriteAllLinessurcharge écrit exactement UTF8 sans BOM.
Roman Kuzmin
6
A créé une demande de fonctionnalité MSDN ici: connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/…
Groostav
3
Notez que cela WriteAllLinessemble nécessiter $MyPathd'être absolu.
sschuberth
10
@xdhmoore WriteAllLinesobtient le répertoire courant de [System.Environment]::CurrentDirectory. Si vous ouvrez PowerShell, puis modifiez votre répertoire actuel (à l'aide de cdou Set-Location), il [System.Environment]::CurrentDirectoryne sera pas modifié et le fichier se retrouvera dans le mauvais répertoire. Vous pouvez contourner ce problème en [System.Environment]::CurrentDirectory = (Get-Location).Path.
Shayan Toqraee du
79

La bonne façon pour l'instant est d'utiliser une solution recommandée par @Roman Kuzmin dans les commentaires à @M. Réponse de Dudley :

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

(Je l'ai également raccourci un peu en supprimant la Systemclarification inutile de l' espace de noms - il sera remplacé automatiquement par défaut.)

ForNeVeR
la source
2
Cela (pour une raison quelconque) n'a pas supprimé la nomenclature pour moi, alors que la réponse acceptée l'a fait
Liam
@Liam, probablement une ancienne version de PowerShell ou .NET?
ForNeVeR
1
Je crois que les anciennes versions de la fonction .NET WriteAllLines ont écrit la nomenclature par défaut. Cela pourrait donc être un problème de version.
Bender the Greatest
2
Confirmé par écrit avec une nomenclature dans Powershell 3, mais sans nomenclature dans Powershell 4. J'ai dû utiliser la réponse originale de M. Dudley.
chazbot7
2
Cela fonctionne donc sur Windows 10 où il est installé par défaut. :) Aussi, amélioration suggérée:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Johny Skovdal
50

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 utf-8 sans fichier bom quel que soit le format source.

Lenny
la source
8
Cela a fonctionné pour moi, sauf que j'ai utilisé -encoding utf8pour mon exigence.
Chim Chimz du
1
Merci beaucoup. Je travaille avec les journaux de vidage d'un outil - qui avait des onglets à l'intérieur. UTF-8 ne fonctionnait pas. ASCII a résolu le problème. Merci.
user1529294
44
Oui, -Encoding ASCIIévite le problème de nomenclature, mais vous n'obtenez évidemment que des caractères ASCII 7 bits . Étant donné que l'ASCII est un sous-ensemble de l'UTF-8, le fichier résultant est techniquement également un fichier UTF-8 valide, mais tous les caractères non ASCII de votre entrée seront convertis en ?caractères littéraux .
mklement0
4
@ChimChimz J'ai accidentellement voté pour votre commentaire, mais j'émets -encoding utf8toujours UTF-8 avec une nomenclature. :(
TheDudeAbides
33

Remarque: Cette réponse s'applique à Windows PowerShell ; en revanche, dans l' édition multiplateforme PowerShell Core (v6 +), UTF-8 sans BOM est le codage par défaut , sur toutes les applets de commande.
En d'autres termes: si vous utilisez PowerShell [Core] version 6 ou supérieure , vous obtenez des fichiers UTF-8 sans nomenclature par défaut (que vous pouvez également demander explicitement avec -Encoding utf8/ -Encoding utf8NoBOM, tandis que vous obtenez avec -BOM avec le codage -utf8BOM).


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

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

  • vous pouvez l'utiliser comme Out-Filedans 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, tout comme avec Out-File.

Exemple:

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

Notez comment (Get-Content $MyPath)est inclus (...), ce qui garantit que le fichier entier est ouvert, lu en entier et fermé avant d'envoyer le résultat via le pipeline. Ceci est nécessaire pour pouvoir réécrire dans le même fichier (le mettre à jour sur place ).
En général, cependant, cette technique n'est pas recommandée pour 2 raisons: (a) le fichier entier doit tenir en mémoire et (b) si la commande est interrompue, les données seront perdues.

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

  • La propre réponse de M. Dudley exige que le contenu complet du fichier soit d'abord créé en mémoire, ce qui peut être problématique avec les fichiers volumineux.
  • La fonction ci-dessous ne s'améliore que légèrement: tous les objets d'entrée sont toujours tamponnés en premier, mais leurs représentations de chaînes sont ensuite générées et écrites une par une dans le fichier de sortie.

Code source deOut-FileUtf8NoBom (également disponible sous forme de Gist sous licence 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()
  }

}
mklement0
la source
16

À partir de la version 6, powershell prend en charge l' UTF8NoBOMencodage à la fois pour le contenu défini et le fichier externe 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
sc911
la source
@ RaúlSalinas-Monteagudo sur quelle version êtes-vous?
John Bentley
Agréable. Pour vérifier la version avec$PSVersionTable.PSVersion
KCD
14

Lorsque vous utilisez Set-Contentau lieu de Out-File, vous pouvez spécifier l'encodage 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 l'utilisation [IO.File]::WriteAllLines()ou similaire est qu'elle devrait fonctionner correctement avec n'importe quel type d'élément et de chemin, pas seulement les chemins de fichier réels.

Lucero
la source
5

Ce script convertira, en UTF-8 sans nomenclature, tous les fichiers .txt dans DIRECTORY1 et les exportera vers DIRECTORY2

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}
jamhan
la source
Celui-ci échoue sans aucun avertissement. Quelle version de PowerShell dois-je utiliser pour l'exécuter?
darksoulsong
3
La solution WriteAllLines fonctionne très bien pour les petits fichiers. Cependant, j'ai besoin d'une solution pour les fichiers plus volumineux. Chaque fois que j'essaie de l'utiliser avec un fichier plus volumineux, j'obtiens une erreur 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" 
    }  

Source Comment supprimer UTF8 Byte Order Mark (BOM) d'un fichier à l'aide de PowerShell

bronzage franc
la source
2

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

$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 | Out-String |diriger le deuxième paramètre pour ajouter explicitement des CRLF à la fin de chaque ligne (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 BOM

SATO Yusuke
la source
Bons pointeurs; suggestions /: l'alternative la plus simple à $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)is Convert-Path $MyPath; si vous voulez assurer un CRLF de fin, utilisez simplement [System.IO.File]::WriteAllLines()même avec une seule chaîne d'entrée (pas besoin de Out-String).
mklement0
0

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

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

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
Erik Anderson
la source
4
Oui, -Encoding ASCIIévite le problème de nomenclature, mais vous n'obtenez évidemment que la prise en charge des caractères ASCII 7 bits . Étant donné que l'ASCII est un sous-ensemble de l'UTF-8, le fichier résultant est techniquement également un fichier UTF-8 valide, mais tous les caractères non ASCII de votre entrée seront convertis en ?caractères littéraux .
mklement0
Cette réponse nécessite plus de votes. L'incompatibilité de sqlplus avec la nomenclature est à l'origine de nombreux maux de tête .
Amit Naidu
0

Changez plusieurs fichiers par extension en UTF-8 sans 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)
}
Jaume Suñer Mut
la source
0

Pour une raison quelconque, les WriteAllLinesappels produisaient toujours une nomenclature pour moi, avec l' UTF8Encodingargument BOMless 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)])

J'ai dû rendre le chemin du fichier absolu pour qu'il fonctionne. Sinon, il a écrit le fichier sur mon bureau. En outre, 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 / longueur de nomenclature donné basé sur l'encodage.

En outre, comme écrit, cela ne fonctionne probablement que si votre fichier s'inscrit dans un tableau PowerShell, qui semble avoir une limite de longueur d'une valeur inférieure à celle [int32]::MaxValuede ma machine.

xdhmoore
la source
1
WriteAllLinessans argument de codage n'écrit jamais une nomenclature elle - même , mais il est concevable que votre chaîne commence par le caractère BOM ( U+FEFF), qui lors de l'écriture a effectivement créé une nomenclature UTF-8; par exemple: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)(omettez le [char] 0xfeff + pour voir qu'aucune nomenclature n'est écrite).
mklement0
1
Quant à l'écriture inattendue vers un emplacement différent: le problème est que le framework .NET a généralement un répertoire actuel différent de PowerShell; vous pouvez soit les synchroniser d'abord avec [Environment]::CurrentDirectory = $PWD.ProviderPath, soit, comme alternative plus générique à votre "$(pwd)\..."approche (mieux "$pwd\...""$($pwd.ProviderPath)\..."(Join-Path $pwd.ProviderPath ...)(Convert-Path BOMthetorpedoes.txt)
:,
Merci, je ne savais pas qu'il pouvait y avoir un seul caractère BOM en conversion BOM UTF-8 comme ça.
xdhmoore
1
Toutes les séquences d'octets de nomenclature (signatures Unicode) sont en fait la représentation d'octets du codage respectif du caractère Unicode uniqueU+FEFF abstrait .
mklement0
Ah ok. Cela semble simplifier les choses.
xdhmoore
-2

Pourrait utiliser ci-dessous pour obtenir UTF8 sans BOM

$MyFile | Out-File -Encoding ASCII
Robin Wang
la source
4
Non, il convertira la sortie en page de code ANSI actuelle (cp1251 ou cp1252, par exemple). Ce n'est pas du tout UTF-8!
ForNeVeR
1
Merci Robin. Cela n'a peut-être pas fonctionné pour l'écriture d'un fichier UTF-8 sans la nomenclature, mais l'option -Encoding ASCII a supprimé la nomenclature. De cette façon, je pouvais générer un fichier bat pour gvim. Le fichier .bat trébuchait sur la nomenclature.
Greg
3
@ForNeVeR: Vous avez raison de dire que l'encodage ASCIIn'est pas UTF-8, mais ce n'est pas non plus la page de code ANSI actuelle - vous pensez Default; ASCIIest vraiment un codage ASCII 7 bits, avec des points de code> = 128 convertis en ?instances littérales .
mklement0
1
@ForNeVeR: Vous pensez probablement à "ANSI" ou " ASCII étendu ". Essayez ceci pour vérifier qu'il ne -Encoding ASCIIs'agit bien que d'ASCII 7 bits: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)- le äa été translittéré en a ?. En revanche, -Encoding Default("ANSI") le conserverait correctement.
mklement0
3
@rob C'est la réponse parfaite pour tous ceux qui n'ont tout simplement pas besoin d'utf-8 ou de tout autre élément différent de l'ASCII et qui ne sont pas intéressés à comprendre les encodages et le but de l'unicode. Vous pouvez l' utiliser comme utf-8 car les caractères utf-8 équivalents à tous les caractères ASCII sont identiques (signifie que la conversion d'un fichier ASCII en fichier utf-8 donne un fichier identique (s'il n'obtient pas de nomenclature)). Pour tous ceux qui ont des caractères non ASCII dans leur texte, cette réponse est juste fausse et trompeuse.
TNT
-3

Celui-ci fonctionne pour moi (utilisez "Default" au lieu de "UTF8"):

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

Le résultat est ASCII sans nomenclature.

Krzysztof
la source
1
Selon la documentation Out-File spécifiant l' Defaultencodage, il utilisera la page de codes ANSI actuelle du système, qui n'est pas UTF-8, comme je le demandais.
M. Dudley
Cela semble fonctionner pour moi, au moins pour Export-CSV. Si vous ouvrez le fichier résultant dans un éditeur approprié, l'encodage du fichier est UTF-8 sans BOM, et non Western Latin ISO 9 comme je m'y attendais avec ASCII
eythort
De nombreux éditeurs ouvrent le fichier au format UTF-8 s'ils ne peuvent pas détecter l'encodage.
videautre