PowerShell-пакетное изменение кодировки файлов в UTF-8

Я пытаюсь сделать мертвую простую вещь: изменить кодировку файлов с чего-либо на UTF-8 без BOM. Я нашел несколько сценариев, которые делают это, и единственный, который действительно работал для меня, это: https://superuser.com/questions/397890/convert-text-files-recursively-to-utf-8-in-powershell#answer-397915.

он работал, как ожидалось, но мне нужно сгенерированные файлы без BOM. Поэтому я попытался немного изменить скрипт, добавив решение, данное этому вопросу: использование PowerShell для записи файла в UTF-8 без спецификации

это мой последний скрипт:

foreach ($i in Get-ChildItem -Recurse) {
    if ($i.PSIsContainer) {
        continue
    }

    $dest = $i.Fullname.Replace($PWD, "some_folder")

    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)

    if (!(Test-Path $(Split-Path $dest -Parent))) {
        New-Item $(Split-Path $dest -Parent) -type Directory
    }

    get-content $i | out-file -encoding $Utf8NoBomEncoding -filepath $dest
}

проблема в том, что powershell возвращает мне ошибку, касающуюся System.Text.UTF8Encoding($False) строка, жалующаяся на неправильный параметр:

невозможно проверить аргумент в параметре' Encoding'. Аргумент " система.Текст.Utf8encoding дополнительно" не принадлежит к группе "Юникод, utf7, utf8 в, кодировках utf32, в ASCII", указанный Атрибут ValidateSet.

интересно, не пропущу ли я что-то, например, версию powershell или что-то в этом роде. Я никогда раньше не кодировал сценарий Powershell, поэтому я полностью потерян с этим. И мне нужно изменить кодировку этих файлов, их сотни, я бы не хотел делать это сам один за другим.

на самом деле я использую версию 2.0, которая поставляется с Windows 7.

спасибо заранее!

изменить 1

я попробовал следующий код, предложенный @LarsTruijens и другими сообщениями:

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach ($i in Get-ChildItem -Recurse) {
    if ($i.PSIsContainer) {
        continue
    }

    $dest = $i.Fullname.Replace($PWD, "some_folder")

    if (!(Test-Path $(Split-Path $dest -Parent))) {
        New-Item $(Split-Path $dest -Parent) -type Directory
    }

    $content = get-content $i
    [System.IO.File]::WriteAllLines($dest, $content, $Utf8NoBomEncoding)
}

это дает мне исключение, жалуясь на один из параметров для WriteAllLines:"Exception on calling 'WriteAllLines' with 3 arguments. The value can't be null". Parameter name: contents. Однако скрипт создает все папки. Но все они пусты.

правка 2

интересная вещь об этой ошибке заключается в том, что параметр" content " не равен null. Если я выведу значение переменной $content (используя Write-host) строки есть. Итак, почему он становится null при передаче метода WriteAllLines?

правка 3

я добавил проверку содержимого в переменную, поэтому скрипт теперь выглядит так:

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach ($i in Get-ChildItem -Recurse) {
    if ($i.PSIsContainer) {
        continue
    }

    $dest = $i.Fullname.Replace($PWD, "some_folder")

    if (!(Test-Path $(Split-Path $dest -Parent))) {
        New-Item $(Split-Path $dest -Parent) -type Directory
    }

    $content = get-content $i

    if ( $content -ne $null ) {

        [System.IO.File]::WriteAllLines($dest, $content, $Utf8NoBomEncoding)
    }
    else {
        Write-Host "No content from: $i"
    }
}

теперь каждая итерация возвращает сообщение" Нет содержимого из: $i", но файл не пуст. Есть еще одна ошибка: Get-content: can't find the path 'C:rootFILENAME.php' because it doesn't exists. кажется, что он пытается найти файлы в корневом каталоге, а не в подпапки. Это, кажется, быть в состоянии чтобы получить имя файла из дочерних папок, но пытается прочитать его из root.

EDIT 4-окончательная рабочая версия

после некоторых усилий и следуя советам, которые я получил здесь, особенно от @LarsTruijens и @AnsgarWiechers, я, наконец, сделал это. Мне пришлось изменить способ получения каталога из $PWD и установить некоторые фиксированные имена для папок. После этого, он работал отлично.

вот он идет, для тех, кто может быть интересует:

    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
    $source = "path"
    $destination = "some_folder"

    foreach ($i in Get-ChildItem -Recurse -Force) {
        if ($i.PSIsContainer) {
            continue
        }

        $path = $i.DirectoryName -replace $source, $destination
        $name = $i.Fullname -replace $source, $destination

        if ( !(Test-Path $path) ) {
            New-Item -Path $path -ItemType directory
        }

        $content = get-content $i.Fullname

        if ( $content -ne $null ) {

            [System.IO.File]::WriteAllLines($name, $content, $Utf8NoBomEncoding)
        } else {
            Write-Host "No content from: $i"   
        }
    }

6 ответов


вы не следовали всему ответу в здесь. Вы забыли часть WriteAllLines.

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach ($i in Get-ChildItem -Recurse) {
    if ($i.PSIsContainer) {
        continue
    }

    $dest = $i.Fullname.Replace($PWD, "some_folder")

    if (!(Test-Path $(Split-Path $dest -Parent))) {
        New-Item $(Split-Path $dest -Parent) -type Directory
    }

    $content = get-content $i 
    [System.IO.File]::WriteAllLines($dest, $content, $Utf8NoBomEncoding)
}

половина ответа находится в сообщении об ошибке. Он сообщает вам возможные значения, которые принимает параметр кодирования, одним из которых является utf8.

... out-file -encoding utf8

  1. Гото реж вы хотите cd c:\MyDirectoryWithCrazyCharacterEncodingAndUnicode
  2. уволить этот сценарий Прочь!

скопируйте и пройдите скрипт в Windows Powershell

 foreach($FileNameInUnicodeOrWhatever in get-childitem)
 {
    $FileName = $FileNameInUnicodeOrWhatever.Name

    $TempFile = "$($FileNameInUnicodeOrWhatever.Name).ASCII"

    get-content $FileNameInUnicodeOrWhatever | out-file $FileNameInUnicodeOrWhatever -Encoding ASCII 

    remove-item $FileNameInUnicodeOrWhatever

    rename-item $TempFile $FileNameInUnicodeOrWhatever

    write-output $FileNameInUnicodeOrWhatever "converted to ASCII ->" $TempFile
}

Я сделал некоторые исправления

  • Get-Childitem действует на $source
  • replace не пытается интерпретировать $source как regex
  • некоторые решения-путь
  • auto-help

и упаковал все в командлет:

<#
    .SYNOPSIS
        Encode-Utf8

    .DESCRIPTION
        Re-Write all files in a folder in UTF-8

    .PARAMETER Source
        directory path to recursively scan for files

    .PARAMETER Destination
        directory path to write files to 
#>
[CmdletBinding(DefaultParameterSetName="Help")]
Param(
   [Parameter(Mandatory=$true, Position=0, ParameterSetName="Default")]
   [string]
   $Source,

   [Parameter(Mandatory=$true, Position=1, ParameterSetName="Default")]
   [string]
   $Destination,

  [Parameter(Mandatory=$false, Position=0, ParameterSetName="Help")]
   [switch]
   $Help   
)

if($PSCmdlet.ParameterSetName -eq 'Help'){
    Get-Help $MyInvocation.MyCommand.Definition -Detailed
    Exit
}

if($PSBoundParameters['Debug']){
    $DebugPreference = 'Continue'
}

$Source = Resolve-Path $Source

if (-not (Test-Path $Destination)) {
    New-Item -ItemType Directory -Path $Destination -Force | Out-Null
}
$Destination = Resolve-Path $Destination

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)

foreach ($i in Get-ChildItem $Source -Recurse -Force) {
    if ($i.PSIsContainer) {
        continue
    }

    $path = $i.DirectoryName.Replace($Source, $Destination)
    $name = $i.Fullname.Replace($Source, $Destination)

    if ( !(Test-Path $path) ) {
        New-Item -Path $path -ItemType directory
    }

    $content = get-content $i.Fullname

    if ( $content -ne $null ) {
        [System.IO.File]::WriteAllLines($name, $content, $Utf8NoBomEncoding)
    } else {
        Write-Host "No content from: $i"   
    }
}

этот подход создает всю структуру папки перед копированием файлов в UTF-8 из текущего каталога . В конце мы обмениваемся именами родительских каталогов .

$destination = "..\DestinationFolder"
Remove-item $destination -Recurse -Force
robocopy $PWD $destination /e /xf *.*

foreach($i in Get-ChildItem -Recurse) {
    if ($i.PSIsContainer) {
        continue
    }
    $originalContent = $i.Fullname
    $dest = $i.Fullname.Replace($PWD, $destination)
    if (!(Test-Path $(Split-Path $dest -Parent))) {
        New-Item $(Split-Path $dest -Parent) -type Directory
    }
    get-content $originalContent | out-file -encoding utf8 -filepath $dest
}

С:

 foreach ($i in Get-ChildItem -Path $source -Recurse -Force) {

только файлы в папке $source будет использоваться.