Out-File
wydaje się wymuszać BOM podczas korzystania z UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Jak mogę napisać plik w UTF-8 bez BOM za pomocą PowerShell?
Out-File
wydaje się wymuszać BOM podczas korzystania z UTF-8:
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "UTF8" $MyPath
Jak mogę napisać plik w UTF-8 bez BOM za pomocą PowerShell?
Odpowiedzi:
Wydaje się, że używanie UTF8Encoding
klasy .NET i przekazywanie $False
do konstruktora działa:
$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)
wystarczy. To WriteAllLines
przeciążenie zapisuje dokładnie UTF8 bez BOM.
WriteAllLines
że wymaga $MyPath
to absolutności.
WriteAllLines
pobiera bieżący katalog z [System.Environment]::CurrentDirectory
. Jeśli otworzysz PowerShell, a następnie zmienisz swój bieżący katalog (używając cd
lub Set-Location
), [System.Environment]::CurrentDirectory
nie zostanie on zmieniony, a plik znajdzie się w niewłaściwym katalogu. Możesz obejść ten problem przez [System.Environment]::CurrentDirectory = (Get-Location).Path
.
Właściwa droga jak na razie jest zastosowanie rozwiązania zalecanego przez @Roman Kuzmin w komentarzach do @M. Odpowiedź Dudleya :
[IO.File]::WriteAllLines($filename, $content)
(Trochę go też skróciłem, usuwając niepotrzebne System
wyjaśnienie przestrzeni nazw - domyślnie zostanie zastąpione automatycznie).
[IO.File]::WriteAllLines(($filename | Resolve-Path), $content)
Pomyślałem, że to nie będzie UTF, ale właśnie znalazłem dość proste rozwiązanie, które wydaje się działać ...
Get-Content path/to/file.ext | out-file -encoding ASCII targetFile.ext
Dla mnie daje to utf-8 bez pliku BOM niezależnie od formatu źródłowego.
-encoding utf8
moich wymagań.
-Encoding ASCII
unika się problemu BOM, ale oczywiście otrzymujesz tylko 7-bitowe znaki ASCII . Biorąc pod uwagę, że ASCII jest podzbiorem UTF-8, plik wynikowy jest technicznie również poprawnym plikiem UTF-8, ale wszystkie znaki inne niż ASCII w twoim danych wejściowych zostaną przekonwertowane na ?
znaki dosłowne .
-encoding utf8
nadal wysyła UTF-8 z BOM. :(
Uwaga: ta odpowiedź dotyczy programu Windows PowerShell ; dla kontrastu w wieloplatformowej edycji PowerShell Core (v6 +) UTF-8 bez BOM jest domyślnym kodowaniem dla wszystkich poleceń cmdlet.
Innymi słowy: Jeśli używasz programu PowerShell [Core] w wersji 6 lub nowszej , domyślnie otrzymujesz pliki UTF-8 bez BOM (które możesz również jawnie zażądać za pomocą -Encoding utf8
/ -Encoding utf8NoBOM
, podczas gdy za pomocą kodowania -BOM za pomocą -utf8BOM
).
Aby uzupełnić własną prostą i pragmatyczną odpowiedź M. Dudleya (i bardziej zwięzłą przeformułowanie ForNeVeR ):
Dla wygody, oto zaawansowana funkcja Out-FileUtf8NoBom
, oparta na potoku alternatywa, która naśladujeOut-File
, co oznacza:
Out-File
w potoku.Out-File
.Przykład:
(Get-Content $MyPath) | Out-FileUtf8NoBom $MyPath
Zwróć uwagę na sposób, w jaki (Get-Content $MyPath)
jest zamknięty (...)
, co gwarantuje, że cały plik zostanie otwarty, w pełni odczytany i zamknięty przed wysłaniem wyniku przez potok. Jest to konieczne, aby móc ponownie zapisać w tym samym pliku (zaktualizować go w miejscu ).
Ogólnie jednak technika ta nie jest wskazana z dwóch powodów: (a) cały plik musi zmieścić się w pamięci i (b) jeśli polecenie zostanie przerwane, dane zostaną utracone.
Uwaga na temat wykorzystania pamięci :
Kod źródłowyOut-FileUtf8NoBom
(dostępny również jako Gist na licencji 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()
}
}
Począwszy od wersji 6 program PowerShell obsługuje UTF8NoBOM
kodowanie zarówno dla zawartości zestawu, jak i pliku wyjściowego, a nawet używa go jako kodowania domyślnego.
W powyższym przykładzie powinno to wyglądać tak:
$MyFile | Out-File -Encoding UTF8NoBOM $MyPath
$PSVersionTable.PSVersion
Używając Set-Content
zamiast Out-File
, możesz określić kodowanie Byte
, którego można użyć do zapisania tablicy bajtów w pliku. To w połączeniu z niestandardowym kodowaniem UTF8, które nie emituje BOM, daje pożądany rezultat:
# 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
Różnica w stosowaniu [IO.File]::WriteAllLines()
lub podobnym polega na tym, że powinien on działać poprawnie z każdym typem elementu i ścieżki, nie tylko rzeczywistymi ścieżkami plików.
Ten skrypt konwertuje, do UTF-8 bez BOM, wszystkie pliki .txt w DIRECTORY1 i wysyła je do 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"
}
Źródło Jak usunąć UTF8 Byte Order Mark (BOM) z pliku za pomocą PowerShell
Jeśli chcesz użyć [System.IO.File]::WriteAllLines()
, powinieneś rzucić drugi parametr na String[]
(jeśli typ $MyFile
jest Object[]
), a także określić ścieżkę bezwzględną za pomocą $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)
:
$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)
Jeśli chcesz użyć [System.IO.File]::WriteAllText()
, czasami powinieneś | Out-String |
przesłać drugi parametr, aby dodać CRLF na końcu każdej linii w sposób wyraźny (szczególnie, gdy używasz ich z 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)
Lub możesz użyć [Text.Encoding]::UTF8.GetBytes()
z 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"
patrz: Jak zapisać wynik ConvertTo-Csv do pliku w UTF-8 bez BOM
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MyPath)
jest Convert-Path $MyPath
; jeśli chcesz zapewnić końcowe CRLF, po prostu użyj [System.IO.File]::WriteAllLines()
nawet z jednym ciągiem wejściowym (nie ma takiej potrzeby Out-String
).
Jedną z technik, których używam, jest przekierowanie danych wyjściowych do pliku ASCII za pomocą polecenia cmdlet Out-File .
Na przykład często uruchamiam skrypty SQL, które tworzą inny skrypt SQL do wykonania w Oracle. Dzięki prostemu przekierowaniu („>”) dane wyjściowe będą w formacie UTF-16, który nie jest rozpoznawany przez SQLPlus. Aby obejść ten problem:
sqlplus -s / as sysdba "@create_sql_script.sql" |
Out-File -FilePath new_script.sql -Encoding ASCII -Force
Wygenerowany skrypt może być następnie wykonany przez inną sesję SQLPlus bez żadnych obaw związanych z Unicode:
sqlplus / as sysdba "@new_script.sql" |
tee new_script.log
-Encoding ASCII
pozwala uniknąć problemu BOM, ale oczywiście otrzymujesz wsparcie tylko dla 7-bitowych znaków ASCII . Biorąc pod uwagę, że ASCII jest podzbiorem UTF-8, plik wynikowy jest technicznie również poprawnym plikiem UTF-8, ale wszystkie znaki inne niż ASCII w twoim danych wejściowych zostaną przekonwertowane na ?
znaki dosłowne .
Zmień wiele plików przez rozszerzenie na UTF-8 bez 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)
}
Z jakiegokolwiek powodu WriteAllLines
wywołania nadal generowały dla mnie BOM, z UTF8Encoding
argumentem Bez BOM i bez niego. Ale następujące działały dla mnie:
$bytes = gc -Encoding byte BOMthetorpedoes.txt
[IO.File]::WriteAllBytes("$(pwd)\BOMthetorpedoes.txt", $bytes[3..($bytes.length-1)])
Musiałem ustawić bezwzględną ścieżkę pliku, aby działała. W przeciwnym razie plik zostanie zapisany na moim pulpicie. Przypuszczam, że to działa tylko wtedy, gdy wiesz, że twój BOM ma 3 bajty. Nie mam pojęcia, jak wiarygodne jest oczekiwanie określonego formatu / długości BOM na podstawie kodowania.
Ponadto, jak napisano, prawdopodobnie działa to tylko wtedy, gdy plik mieści się w tablicy PowerShell, która wydaje się mieć limit długości o wartości niższej niż [int32]::MaxValue
na moim komputerze.
WriteAllLines
bez argumentu kodującego nigdy nie zapisuje samej BOM , ale możliwe jest, że Twój łańcuch zaczynał się od znaku BOM ( U+FEFF
), który po napisaniu skutecznie stworzył BOM UTF-8; np .: $s = [char] 0xfeff + 'hi'; [io.file]::WriteAllText((Convert-Path t.txt), $s)
(pomiń, [char] 0xfeff +
aby zobaczyć, że BOM nie jest zapisany).
[Environment]::CurrentDirectory = $PWD.ProviderPath
, albo jako bardziej ogólną alternatywę dla swojego "$(pwd)\..."
podejścia (lepiej "$pwd\..."
"$($pwd.ProviderPath)\..."
(Join-Path $pwd.ProviderPath ...)
(Convert-Path BOMthetorpedoes.txt)
U+FEFF
.
Można użyć poniżej, aby uzyskać UTF8 bez BOM
$MyFile | Out-File -Encoding ASCII
ASCII
nie jest UTF-8, ale nie jest to również bieżąca strona kodowa ANSI - myślisz o tym Default
; ASCII
naprawdę jest 7-bitowym kodowaniem ASCII, przy czym punkty kodowe> = 128 są konwertowane na dosłowne ?
instancje.
-Encoding ASCII
rzeczywiście jest to tylko 7-bitowy ASCII: 'äb' | out-file ($f = [IO.Path]::GetTempFilename()) -encoding ASCII; '?b' -eq $(Get-Content $f; Remove-Item $f)
- ä
został transliterowany na a ?
. Natomiast -Encoding Default
(„ANSI”) poprawnie to zachowałby.
Ten działa dla mnie (użyj „Domyślne” zamiast „UTF8”):
$MyFile = Get-Content $MyPath
$MyFile | Out-File -Encoding "Default" $MyPath
Wynikiem jest ASCII bez BOM.
Default
kodowanie będzie używać bieżącej strony kodowej ANSI systemu, która nie jest UTF-8, jak wymagałem.