change - Powershell: colorare correttamente l'output Get-Childitem una volta per tutte




powershell color output (4)

Forse tramite una funzione proxy Out-Default.

Modifica: soluzione in fondo a questo post.

Colorare Get-Childitem ( dir o ls , in altre parole) non è esattamente una nuova idea, ma non sono stato in grado di individuare alcun approccio ideale per colorare l'output in Powershell. Esistono due approcci generali per la scrittura delle funzioni di colore-ls:

  • Intercettazione dell'output di Get-Childitem e ri-uscita come testo utilizzando Write-Host con il parametro -ForegroundColor. Questo approccio consente la massima granularità possibile, ma riduce l'output di Get-Childitem al testo. Come molti utenti di PowerShell sono a conoscenza, Get-Childitem non emette il testo, ma emette oggetti. In particolare, un elenco di oggetti FileInfo e DirectoryInfo. Ciò consente una grande flessibilità nella gestione dell'output Get-Childitem.

  • Esegui il pipe dell'output di Get-Childitem tramite Invoke-Expression su Foreach-Object, cambiando il colore di primo piano della console prima di emettere ogni oggetto. Una specie di boccone, ma l'opzione migliore perché conserva il tipo di output di Get-Childitem.

Ecco un esempio di quest'ultimo approccio, fornito dal blog Powershell di Tim Johnson .

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 $_
        }
    }
}

Questo codice assegna colori diversi basati esclusivamente sull'estensione del file, ma quasi tutte le metriche potrebbero essere sostituite per differenziare i tipi di file. Il codice sopra riportato produce il seguente output:

Questo è quasi perfetto, ma c'è un piccolo difetto: le prime 3 righe in uscita (Percorso directory, Intestazioni colonne e separatori orizzontali) assumono il colore del primo elemento nell'elenco. Tim Johnson ha commentato nel suo blog:

Preferirei che l'intestazione nella parte superiore non fosse sempre dello stesso colore del primo oggetto, ma non riesco a pensare in alcun modo a ciò.

Nemmeno io, sfortunatamente. Ecco dove arrivano Stack Overflow e i suoi guru PowerShell: sto cercando un modo per colorare l'output di Get-Childitem preservando il tipo di output del cmdlet, senza compromettere il colore dell'intestazione. Ho fatto qualche esperimento e ho giocato con questo approccio, ma non ho ancora avuto alcun successo, dato che la prima singola chiamata echo emette l'intera intestazione e il primo elemento.

Qualsiasi domanda, commento o, ancora meglio, soluzione sono benvenute.

La soluzione Grazie a jon Z e agli altri che hanno fornito idee:

Jon Z ha fornito la soluzione perfetta a questo problema, che ho ripulito un po 'per adattarlo allo schema della mia domanda iniziale. Eccolo, per chiunque sia interessato. Si noti che ciò richiede il cmdlet New- CommandWrapper dal libro di cucina PowerShell. Tutto questo codice va nel tuo profilo.

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 ""
}

Questo produce un output simile allo screenshot seguente:

Se desideri che la riga della dimensione totale del file in basso, aggiungi semplicemente il seguente codice:

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")
    }
}

Ho appena installato e utilizzato https://github.com/Davlind/PSColor che è stato indolore. Supporta PSGet in modo da poter installare facilmente con Install-Module PSColor per ottenerlo.

Gli oggetti non vengono trasformati, quindi continuano a supportare le tubazioni. (Sta usando il New-CommandWrapper menzionato sopra)

Supporta anche altre cose come select-string.


Ho un'altra soluzione. Puoi semplicemente avere un formato .format.ps1xml personalizzato e apportare alcune modifiche per rendere possibile la colorazione.

Ho il mio file formform.ps1xml di personalizzazione su github.com: https://github.com/ecsousa/PSUtils/blob/master/CustomPSUtils.format.ps1xml

Per usarlo, tutto ciò che devi fare è:

Update-FormatData -Prepend CustomPSUtils.format.ps1xml

Inoltre, per assicurarsi di tornare al colore della console originale dopo un oggetto Get-ChildItem, sarà necessario sostituire la funzione di prompt . Qualcosa come questo:

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

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

La modifica di Out-Default è sicuramente la strada da percorrere. Sotto un esempio - scontato, sciatto. Sto usando New-CommandWrapper dal libro di cucina 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
        }
} 





colors