Verwenden von PowerShell zum Schreiben einer Datei in UTF-8 ohne Stückliste

245

Out-File scheint die Stückliste bei Verwendung von UTF-8 zu erzwingen:

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

Wie kann ich mit PowerShell eine Datei in UTF-8 ohne Stückliste schreiben?

M. Dudley
quelle
23
Stückliste = Byte-Order Mark. Drei Zeichen am Anfang einer Datei (0xEF, 0xBB, 0xBF), die wie "ï» ¿"
aussehen
39
Das ist unglaublich frustrierend. Sogar Module von Drittanbietern werden verschmutzt, beispielsweise wenn Sie versuchen, eine Datei über SSH hochzuladen? Stückliste! "Ja, lasst uns jede einzelne Datei beschädigen. Das klingt nach einer guten Idee." -Microsoft.
MichaelGG
3
Die Standardcodierung ist UTF8NoBOM ab Powershell Version 6.0 docs.microsoft.com/en-us/powershell/module/…
Paul Shiryaev
Sprechen Sie über das Brechen der Abwärtskompatibilität ...
Dragas

Antworten:

220

Die Verwendung der .NET- UTF8EncodingKlasse und die Übergabe $Falsean den Konstruktor scheint zu funktionieren:

$MyRawString = Get-Content -Raw $MyPath
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllLines($MyPath, $MyRawString, $Utf8NoBomEncoding)
M. Dudley
quelle
42
Ugh, ich hoffe das ist nicht der einzige Weg.
Scott Muc
114
Eine Zeile [System.IO.File]::WriteAllLines($MyPath, $MyFile)reicht aus. Diese WriteAllLinesÜberladung schreibt genau UTF8 ohne Stückliste.
Roman Kuzmin
6
Hier wurde eine MSDN-Funktionsanforderung erstellt: connect.microsoft.com/PowerShell/feedbackdetail/view/1137121/…
Groostav
3
Beachten Sie, dass dies absolut zu sein WriteAllLinesscheint $MyPath.
sschuberth
9
@xdhmoore ruft WriteAllLinesdas aktuelle Verzeichnis ab [System.Environment]::CurrentDirectory. Wenn Sie PowerShell öffnen und dann Ihr aktuelles Verzeichnis ändern (mit cdoder Set-Location), [System.Environment]::CurrentDirectorywird dies nicht geändert und die Datei befindet sich im falschen Verzeichnis. Sie können dies umgehen, indem Sie [System.Environment]::CurrentDirectory = (Get-Location).Path.
Shayan Toqraee
79

Der richtige Weg ist ab sofort, eine von @Roman Kuzmin in Kommentaren zu @M empfohlene Lösung zu verwenden. Dudley Antwort :

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

(Ich habe es auch ein wenig verkürzt, indem ich unnötige SystemNamespace-Klarstellungen entfernt habe - es wird standardmäßig automatisch ersetzt.)

Niemals
quelle
2
Dies (aus welchem ​​Grund auch immer) hat die Stückliste für mich nicht entfernt, wo wie die akzeptierte Antwort
Liam
@ Liam, wahrscheinlich eine alte Version von PowerShell oder .NET?
ForNeVeR
1
Ich glaube, ältere Versionen der .NET WriteAllLines-Funktion haben die Stückliste standardmäßig geschrieben. Es könnte sich also um ein Versionsproblem handeln.
Bender der Größte
2
Bestätigt mit Schreibvorgängen mit einer Stückliste in Powershell 3, aber ohne Stückliste in Powershell 4. Ich musste die ursprüngliche Antwort von M. Dudley verwenden.
Chazbot7
2
Es funktioniert also unter Windows 10, wo es standardmäßig installiert ist. :) Auch Verbesserungsvorschlag:[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Johny Skovdal
50

Ich dachte, das wäre kein UTF, aber ich habe gerade eine ziemlich einfache Lösung gefunden, die zu funktionieren scheint ...

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

Für mich führt dies zu einem utf-8 ohne bom-Datei, unabhängig vom Quellformat.

Lenny
quelle
8
Dies funktionierte für mich, außer ich habe es -encoding utf8für meine Anforderung verwendet.
Chim Chimz
1
Vielen Dank. Ich arbeite mit Speicherauszugsprotokollen eines Tools, in dem sich Registerkarten befanden. UTF-8 funktionierte nicht. ASCII hat das Problem gelöst. Vielen Dank.
Benutzer1529294
44
Ja, -Encoding ASCIIvermeidet das Stücklistenproblem, aber Sie erhalten offensichtlich nur 7-Bit-ASCII-Zeichen . Da ASCII eine Teilmenge von UTF-8 ist, ist die resultierende Datei technisch auch eine gültige UTF-8 - Datei, aber alle Nicht-ASCII - Zeichen in Ihrer Eingabe werden wörtliche umgewandelt werden ?Zeichen .
mklement0
4
@ChimChimz Ich habe Ihren Kommentar versehentlich hochgestimmt, gebe aber -encoding utf8immer noch UTF-8 mit einer Stückliste aus. :(
TheDudeAbides
33

Hinweis: Diese Antwort gilt für Windows PowerShell . Im Gegensatz dazu ist in der plattformübergreifenden PowerShell Core Edition (Version 6 +) UTF-8 ohne Stückliste die Standardcodierung für alle Cmdlets.
Mit anderen Worten: Wenn Sie PowerShell [Core] Version 6 oder höher verwenden , erhalten Sie standardmäßig Stücklistenlose UTF-8-Dateien (die Sie auch explizit mit -Encoding utf8/ anfordern können -Encoding utf8NoBOM, während Sie mit -BOM-Codierung mit erhalten -utf8BOM).


Zur Ergänzung von M. Dudleys eigener einfacher und pragmatischer Antwort (und der präziseren Neuformulierung von ForNeVeR) ):

Der Einfachheit halber ist hier die erweiterte Funktion Out-FileUtf8NoBom, eine Pipeline-basierte Alternative, die nachahmtOut-File , was bedeutet:

  • Sie können es genauso verwenden Out-File in einer Pipeline verwenden.
  • Eingabeobjekte, die keine Zeichenfolgen sind, werden so formatiert, wie sie wären, wenn Sie sie wie bei an die Konsole senden würden Out-File.

Beispiel:

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

Beachten Sie, wie (Get-Content $MyPath)eingeschlossen ist (...), wodurch sichergestellt wird, dass die gesamte Datei geöffnet, vollständig gelesen und geschlossen wird, bevor das Ergebnis über die Pipeline gesendet wird. Dies ist erforderlich, um in dieselbe Datei zurückschreiben zu können (aktualisieren Sie sie an Ort und Stelle ).
Im Allgemeinen ist diese Technik jedoch aus zwei Gründen nicht ratsam: (a) Die gesamte Datei muss in den Speicher passen, und (b) wenn der Befehl unterbrochen wird, gehen Daten verloren.

Ein Hinweis zur Speichernutzung :

  • Die eigene Antwort von M. Dudley erfordert, dass der gesamte Dateiinhalt zuerst im Speicher aufgebaut wird, was bei großen Dateien problematisch sein kann.
  • Die folgende Funktion verbessert dies nur geringfügig: Alle Eingabeobjekte werden immer noch zuerst gepuffert, aber ihre Zeichenfolgendarstellungen werden dann generiert und einzeln in die Ausgabedatei geschrieben.

Quellcode vonOut-FileUtf8NoBom (auch als MIT-lizenzierte Gist erhältlich ):

<#
.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
quelle
16

Ab Version 6 unterstützt Powershell die UTF8NoBOMCodierung sowohl für Set-Content als auch für Out-File und verwendet diese sogar als Standardcodierung.

Im obigen Beispiel sollte es einfach so sein:

$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
sc911
quelle
@ RaúlSalinas-Monteagudo auf welcher Version bist du?
John Bentley
Nett. FYI überprüfen Version mit$PSVersionTable.PSVersion
KCD
14

Bei Verwendung von Set-Contentanstelle von Out-Filekönnen Sie die Codierung angeben Byte, mit der ein Byte-Array in eine Datei geschrieben werden kann. Dies in Kombination mit einer benutzerdefinierten UTF8-Codierung, die die Stückliste nicht ausgibt, ergibt das gewünschte Ergebnis:

# 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

Der Unterschied zur Verwendung [IO.File]::WriteAllLines()oder ähnlichem besteht darin, dass es mit jeder Art von Element und Pfad gut funktionieren sollte, nicht nur mit tatsächlichen Dateipfaden.

Lucero
quelle
5

Dieses Skript konvertiert alle TXT-Dateien in DIRECTORY1 in UTF-8 ohne Stückliste und gibt sie in DIRECTORY2 aus

foreach ($i in ls -name DIRECTORY1\*.txt)
{
    $file_content = Get-Content "DIRECTORY1\$i";
    [System.IO.File]::WriteAllLines("DIRECTORY2\$i", $file_content);
}
Jamhan
quelle
Dieser schlägt ohne Vorwarnung fehl. Welche Version von Powershell sollte ich verwenden, um es auszuführen?
Darksoulsong
3
Die WriteAllLines-Lösung eignet sich hervorragend für kleine Dateien. Ich benötige jedoch eine Lösung für größere Dateien. Jedes Mal, wenn ich versuche, dies mit einer größeren Datei zu verwenden, wird ein OutOfMemory-Fehler angezeigt.
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" 
    }  

Quelle Entfernen von UTF8-Byte-Bestellmarken (BOM) aus einer Datei mit PowerShell

Frank Tan
quelle
2

Wenn Sie verwenden möchten [System.IO.File]::WriteAllLines(), sollten Sie den zweiten Parameter in String[](wenn der Typ von $MyFileist Object[]) umwandeln und auch den absoluten Pfad mit angeben $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath), wie:

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

Wenn Sie verwenden möchten [System.IO.File]::WriteAllText(), sollten Sie manchmal den zweiten Parameter | Out-String |einfügen, um CRLFs explizit am Ende jeder Zeile hinzuzufügen (insbesondere, wenn Sie sie mit verwenden 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)

Oder Sie können verwenden [Text.Encoding]::UTF8.GetBytes()mit 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"

Siehe: So schreiben Sie das Ergebnis von ConvertTo-Csv in eine Datei in UTF-8 ohne Stückliste

SATO Yusuke
quelle
Gute Hinweise; Vorschläge /: die einfachere Alternative zu $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)ist Convert-Path $MyPath; Wenn Sie eine nachfolgende CRLF sicherstellen möchten, verwenden Sie einfach [System.IO.File]::WriteAllLines()auch eine einzelne Eingabezeichenfolge (keine Notwendigkeit Out-String).
mklement0
0

Eine Technik, die ich verwende, besteht darin, die Ausgabe mithilfe des Cmdlets Out-File in eine ASCII -Datei umzuleiten.

Zum Beispiel führe ich häufig SQL-Skripte aus, die ein anderes SQL-Skript erstellen, das in Oracle ausgeführt werden soll. Bei einfacher Umleitung (">") erfolgt die Ausgabe in UTF-16, das von SQLPlus nicht erkannt wird. Um dies zu umgehen:

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

Das generierte Skript kann dann ohne Unicode-Probleme über eine andere SQLPlus-Sitzung ausgeführt werden:

sqlplus / as sysdba "@new_script.sql" |
tee new_script.log
Erik Anderson
quelle
4
Ja, -Encoding ASCIIvermeidet das Stücklistenproblem, aber Sie erhalten offensichtlich nur Unterstützung für 7-Bit-ASCII-Zeichen . Da ASCII eine Teilmenge von UTF-8 ist, ist die resultierende Datei technisch auch eine gültige UTF-8 - Datei, aber alle Nicht-ASCII - Zeichen in Ihrer Eingabe werden wörtliche umgewandelt werden ?Zeichen .
mklement0
Diese Antwort benötigt mehr Stimmen. Die Inkompatibilität von sqlplus mit Stücklisten verursacht viele Kopfschmerzen .
Amit Naidu
0

Ändern Sie mehrere Dateien durch Erweiterung in UTF-8 ohne Stückliste:

$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
quelle
0

Aus irgendeinem Grund produzierten die WriteAllLinesAnrufe immer noch eine Stückliste für mich, mit dem UTF8EncodingArgument Stücklistenlos und ohne. Aber das Folgende hat bei mir funktioniert:

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

Ich musste den Dateipfad absolut machen, damit er funktioniert. Andernfalls wurde die Datei auf meinen Desktop geschrieben. Außerdem funktioniert dies vermutlich nur, wenn Sie wissen, dass Ihre Stückliste 3 Byte umfasst. Ich habe keine Ahnung, wie zuverlässig es ist, ein bestimmtes Stücklistenformat / eine bestimmte Stücklistenlänge basierend auf der Codierung zu erwarten.

Wie geschrieben, funktioniert dies wahrscheinlich nur, wenn Ihre Datei in ein Powershell-Array passt, dessen Längenbegrenzung um einen Wert niedriger zu sein scheint als [int32]::MaxValueauf meinem Computer.

xdhmoore
quelle
1
WriteAllLinesohne eine Codierung Argument schreibt nie eine Stückliste selbst , aber es ist denkbar , dass Ihre Zeichenfolge mit der BOM starten passierten Zeichen ( U+FEFF), die effektiv erstellt einen UTF-8 BOM auf dem Schreiben; zB: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)(lassen Sie das weg, um [char] 0xfeff + zu sehen, dass keine Stückliste geschrieben ist).
mklement0
1
Was das unerwartete Schreiben an einen anderen Speicherort betrifft: Das Problem besteht darin, dass das .NET-Framework normalerweise ein anderes aktuelles Verzeichnis als PowerShell hat. Sie können sie entweder zuerst mit synchronisieren [Environment]::CurrentDirectory = $PWD.ProviderPathoder als allgemeinere Alternative zu Ihrem "$(pwd)\..."Ansatz (besser : "$pwd\...", noch besser: "$($pwd.ProviderPath)\..."oder (Join-Path $pwd.ProviderPath ...))(Convert-Path BOMthetorpedoes.txt)
mklement0
Vielen Dank, ich wusste nicht, dass es eine solche Konvertierung von Stücklistenzeichen zu UTF-8-Stücklisten geben kann.
xdhmoore
1
Alle Stücklistenbyte- Sequenzen (Unicode-Signaturen) sind tatsächlich die Byte-Darstellung der jeweiligen Codierung des abstrakten einzelnen Unicode-ZeichensU+FEFF .
mklement0
Ah ok. Das scheint die Dinge einfacher zu machen.
xdhmoore
-2

Könnte unten verwenden, um UTF8 ohne Stückliste zu erhalten

$MyFile | Out-File -Encoding ASCII
Robin Wang
quelle
4
Nein, die Ausgabe wird in die aktuelle ANSI-Codepage konvertiert (z. B. cp1251 oder cp1252). Es ist überhaupt nicht UTF-8!
ForNeVeR
1
Danke Robin. Dies hat möglicherweise nicht zum Schreiben einer UTF-8-Datei ohne Stückliste funktioniert, aber die Option -Encoding ASCII hat die Stückliste entfernt. Auf diese Weise konnte ich eine Bat-Datei für gvim generieren. Die .bat-Datei wurde in der Stückliste ausgelöst.
Greg
3
@ForNeVeR: Sie haben Recht, dass die Codierung ASCIInicht UTF-8 ist, aber es ist auch nicht die aktuelle ANSI-Codepage - Sie denken daran Default; ASCIIEs handelt sich tatsächlich um eine 7-Bit-ASCII-Codierung, bei der Codepunkte> = 128 in Literalinstanzen konvertiert ?werden.
mklement0
1
@ForNeVeR: Sie denken wahrscheinlich an "ANSI" oder " Extended ASCII". Versuchen Sie dies, um sicherzustellen, dass -Encoding ASCIIes sich tatsächlich nur um 7-Bit-ASCII handelt: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)- Das äwurde in a transkribiert ?. Im Gegensatz dazu würde -Encoding Default("ANSI") es korrekt beibehalten.
mklement0
3
@rob Dies ist die perfekte Antwort für alle, die utf-8 oder etwas anderes, das sich von ASCII unterscheidet, nicht benötigen und nicht daran interessiert sind, Codierungen und den Zweck von Unicode zu verstehen. Sie können verwenden es als utf-8 , da das entsprechende utf-8 - Zeichen für alle ASCII - Zeichen identisch sind (Mittel eine ASCII-Datei an ein utf-8-Datei Ergebnisse in einer identischen Datei konvertieren (falls es wird keine BOM)). Für alle, deren Text Nicht-ASCII-Zeichen enthält, ist diese Antwort nur falsch und irreführend.
TNT
-3

Dieser funktioniert für mich (verwenden Sie "Standard" anstelle von "UTF8"):

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

Das Ergebnis ist ASCII ohne Stückliste.

Krzysztof
quelle
1
Gemäß der Out-File-Dokumentation , in der die DefaultCodierung angegeben ist, wird die aktuelle ANSI-Codepage des Systems verwendet, die nicht wie erforderlich UTF-8 ist.
M. Dudley
Dies scheint für mich zumindest für Export-CSV zu funktionieren. Wenn Sie die resultierende Datei in einem geeigneten Editor öffnen, lautet die
Dateicodierung
Viele Editoren öffnen die Datei als UTF-8, wenn sie die Codierung nicht erkennen können.
leer