Подтвердить что ты не робот

Powershell: правильная окраска вывода Get-Childitem раз и навсегда

Изменить: Решение в нижней части этой публикации.

Colorizing Get-Childitem (иначе говоря, это не новая идея), но я не смог найти идеальные подходы к раскрашиванию вывода в Powershell. Существует два общих подхода для написания функций color-ls:

  • Перехват вывода Get-Childitem и повторное вывод его в виде текста с помощью Write-Host с параметром -ForegroundColor. Этот подход позволяет как можно больше детализации, но уменьшает вывод Get-Childitem в текст. Как известно большинству пользователей PowerShell, Get-Childitem не выводит текст, а выводит объекты. В частности, список объектов FileInfo и DirectoryInfo. Это позволяет обеспечить большую гибкость при обработке вывода Get-Childitem.

  • Выполните вывод Get-Childitem с помощью Invoke-Expression to Foreach-Object, изменив цвет переднего плана консоли перед выводом каждого объекта. Вид глотки, но лучший вариант, поскольку он сохраняет тип вывода Get-Childitem.

Вот пример последнего подхода, предоставленного Tim Johnson Powershell Blog.

function color-ls
{
    $regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase `
          -bor [System.Text.RegularExpressions.RegexOptions]::Compiled)
    $fore = $Host.UI.RawUI.ForegroundColor
    $compressed = New-Object System.Text.RegularExpressions.Regex(
          '\.(zip|tar|gz|rar|jar|war)$', $regex_opts)
    $executable = New-Object System.Text.RegularExpressions.Regex(
          '\.(exe|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg)$', $regex_opts)
    $text_files = New-Object System.Text.RegularExpressions.Regex(
          '\.(txt|cfg|conf|ini|csv|log|xml|java|c|cpp|cs)$', $regex_opts)

    Invoke-Expression ("Get-ChildItem $args") | ForEach-Object {
        if ($_.GetType().Name -eq 'DirectoryInfo') 
        {
            $Host.UI.RawUI.ForegroundColor = 'Magenta'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        elseif ($compressed.IsMatch($_.Name)) 
        {
            $Host.UI.RawUI.ForegroundColor = 'darkgreen'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        elseif ($executable.IsMatch($_.Name))
        {
            $Host.UI.RawUI.ForegroundColor = 'Red'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        elseif ($text_files.IsMatch($_.Name))
        {
            $Host.UI.RawUI.ForegroundColor = 'Yellow'
            echo $_
            $Host.UI.RawUI.ForegroundColor = $fore
        }
        else
        {
            echo $_
        }
    }
}

Этот код присваивает разные цвета, основанные исключительно на расширении файла, но почти любая метрика может быть заменена для различения типов файлов. Вышеприведенный код выводит следующий результат:

Colored get-childitem example

Это почти идеально, но есть один небольшой недостаток: первые 3 строки вывода (путь к каталогу, заголовки столбцов и горизонтальные разделители) принимают цвет первого элемента в списке. Тим Джонсон прокомментировал в своем блоге:

Я бы предпочел, чтобы заголовок сверху не всегда был того же цвета, что и первый элемент, но я не могу думать об этом.

Я тоже, к сожалению, не могу. Что там, где Qaru и его гуру powershell входят: я ищу способ раскрасить вывод Get-Childitem, сохраняя тип вывода командлета, не испортив цвет заголовка. Я сделал несколько экспериментов и возился с этим подходом, но пока не добился успеха, поскольку первый одиночный эхо-вызов выводит весь заголовок и первый элемент.

Любые вопросы, комментарии или даже лучшие решения приветствуются.

Решение Благодаря jon Z и другим, кто предоставил идеи:

Jon Z обеспечил идеальное решение этой проблемы, которое я немного отполировал, чтобы соответствовать схеме в моем первоначальном вопросе. Вот он, для всех, кто заинтересован. Обратите внимание, что для этого требуется командлет New-CommandWrapper из Поваренной книги Powershell. Весь этот код входит в ваш профиль.

function Write-Color-LS
    {
        param ([string]$color = "white", $file)
        Write-host ("{0,-7} {1,25} {2,10} {3}" -f $file.mode, ([String]::Format("{0,10}  {1,8}", $file.LastWriteTime.ToString("d"), $file.LastWriteTime.ToString("t"))), $file.length, $file.name) -foregroundcolor $color 
    }

New-CommandWrapper Out-Default -Process {
    $regex_opts = ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase)


    $compressed = New-Object System.Text.RegularExpressions.Regex(
        '\.(zip|tar|gz|rar|jar|war)$', $regex_opts)
    $executable = New-Object System.Text.RegularExpressions.Regex(
        '\.(exe|bat|cmd|py|pl|ps1|psm1|vbs|rb|reg)$', $regex_opts)
    $text_files = New-Object System.Text.RegularExpressions.Regex(
        '\.(txt|cfg|conf|ini|csv|log|xml|java|c|cpp|cs)$', $regex_opts)

    if(($_ -is [System.IO.DirectoryInfo]) -or ($_ -is [System.IO.FileInfo]))
    {
        if(-not ($notfirst)) 
        {
           Write-Host
           Write-Host "    Directory: " -noNewLine
           Write-Host " $(pwd)`n" -foregroundcolor "Magenta"           
           Write-Host "Mode                LastWriteTime     Length Name"
           Write-Host "----                -------------     ------ ----"
           $notfirst=$true
        }

        if ($_ -is [System.IO.DirectoryInfo]) 
        {
            Write-Color-LS "Magenta" $_                
        }
        elseif ($compressed.IsMatch($_.Name))
        {
            Write-Color-LS "DarkGreen" $_
        }
        elseif ($executable.IsMatch($_.Name))
        {
            Write-Color-LS "Red" $_
        }
        elseif ($text_files.IsMatch($_.Name))
        {
            Write-Color-LS "Yellow" $_
        }
        else
        {
            Write-Color-LS "White" $_
        }

    $_ = $null
    }
} -end {
    write-host ""
}

Это производит вывод, который выглядит следующим образом: enter image description here

Если вам нужна общая строка размера файла внизу, просто добавьте следующий код:

Remove-Item alias:ls
Set-Alias ls LS-Padded

function LS-Padded
{
    param ($dir)
    Get-Childitem $dir
    Write-Host
    getDirSize $dir
}

function getDirSize
{
    param ($dir)
    $bytes = 0

    Get-Childitem $dir | foreach-object {

        if ($_ -is [System.IO.FileInfo])
        {
            $bytes += $_.Length
        }
    }

    if ($bytes -ge 1KB -and $bytes -lt 1MB)
    {
        Write-Host ("Total Size: " + [Math]::Round(($bytes / 1KB), 2) + " KB")   
    }

    elseif ($bytes -ge 1MB -and $bytes -lt 1GB)
    {
        Write-Host ("Total Size: " + [Math]::Round(($bytes / 1MB), 2) + " MB")
    }

    elseif ($bytes -ge 1GB)
    {
        Write-Host ("Total Size: " + [Math]::Round(($bytes / 1GB), 2) + " GB")
    }    

    else
    {
        Write-Host ("Total Size: " + $bytes + " bytes")
    }
}
4b9b3361

Ответ 1

Модификация Out-Default - это, безусловно, путь. Ниже приведенный, небрежный - пример. Я использую New-CommandWrapper из Поваренной книги PowerShell.

New-CommandWrapper Out-Default `
    -Process {
        if(($_ -is [System.IO.DirectoryInfo]) -or ($_ -is [System.IO.FileInfo]))
        {if(-not ($notfirst)) {
           Write-Host "    Directory: $(pwd)`n"           
           Write-Host "Mode                LastWriteTime     Length Name"
           Write-Host "----                -------------     ------ ----"
           $notfirst=$true
           }
           if ($_ -is [System.IO.DirectoryInfo]) {
           Write-host ("{0,-7} {1,25} {2,10} {3}" -f $_.mode, ([String]::Format("{0,10}  {1,8}", $_.LastWriteTime.ToString("d"), $_.LastWriteTime.ToString("t"))), $_.length, $_.name) -foregroundcolor "yellow" }
           else {
           Write-host ("{0,-7} {1,25} {2,10} {3}" -f $_.mode, ([String]::Format("{0,10}  {1,8}", $_.LastWriteTime.ToString("d"), $_.LastWriteTime.ToString("t"))), $_.length, $_.name) -foregroundcolor "green" }
           $_ = $null
        }
} 

Example Directory Listing

Ответ 2

Я только что установил и использовал https://github.com/Davlind/PSColor, который был безболезненным. Он поддерживает PSGet, поэтому вы можете легко установить его с помощью Install-Module PSColor, чтобы получить его.

Объекты не преобразуются, поэтому они все еще поддерживают конвейер. (Он использует упомянутый выше New-CommandWrapper)

Он также поддерживает другие функции, такие как select-string.

PowerShell Color

Ответ 3

Возможно через функцию прокси-сервера Out-Default.

Ответ 4

У меня есть еще один script, который заботится о случае Format-Wide (ls), а также имеет лучшую производительность, используя словари вместо регулярного выражения: https://github.com/joonro/Get-ChildItem-Color.

Ответ 5

У меня есть другое решение. Вы можете просто создать собственный файл .format.ps1xml и сделать некоторые настройки, чтобы сделать возможной раскраску.

У меня есть файл формы .format.ps1xml на github.com: https://github.com/ecsousa/PSUtils/blob/master/CustomPSUtils.format.ps1xml

Чтобы использовать его, все, что вам нужно сделать, это:

Update-FormatData -Prepend CustomPSUtils.format.ps1xml

Кроме того, чтобы вернуться к исходному цвету консоли после Get-ChildItem, вам необходимо переопределить функцию prompt. Что-то вроде этого:

function prompt {
    if($global:FSFormatDefaultColor) {
        [Console]::ForegroundColor = $global:FSFormatDefaultColor
    }

    "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "
}