2011-09-09 12 views
7

Czy możliwe jest pokolorowanie tylko niektórych słów (nie pełnych linii) dla wyjścia powłoki programu powershell przy użyciu tabeli formatów. Na przykład ten skrypt skanuje rekursywnie folder dla łańcucha, a następnie wyprowadza wynik za pomocą tabeli formatów.Wyrazy kolorowe w formacie skryptu powłoki powershell

dir -r -i *.* | Select-String $args[0] | 
format-table -Property @{label="Line #"; Expression={$_.LineNumber}; width=6}, 
Path, Line -wrap 

Byłoby miło być w stanie sformatować słowo szukamy z konkretnym kolorze, dzięki czemu można dokładnie zobaczyć, gdzie stwierdzono na linii.

Odpowiedz

14

Można odlutować stół w Out-String, a następnie napisać ciąg w częściach przy użyciu Write-Host z przełącznikiem -NoNewLine.

coś takiego:

filter ColorWord { 
    param(
     [string] $word, 
     [string] $color 
    ) 
    $line = $_ 
    $index = $line.IndexOf($word, [System.StringComparison]::InvariantCultureIgnoreCase) 
    while($index -ge 0){ 
     Write-Host $line.Substring(0,$index) -NoNewline 
     Write-Host $line.Substring($index, $word.Length) -NoNewline -ForegroundColor $color 
     $used = $word.Length + $index 
     $remain = $line.Length - $used 
     $line = $line.Substring($used, $remain) 
     $index = $line.IndexOf($word, [System.StringComparison]::InvariantCultureIgnoreCase) 
    } 
    Write-Host $line 
} 

Get-Process| Format-Table| Out-String| ColorWord -word 1 -color magenta 
+2

Działa świetnie! Zmodyfikował tę linię: $ index = $ line.IndexOf ($ word) do $ index = $ line.IndexOf ($ słowem, [System.StringComparison] :: InvariantCultureIgnoreCase) – EtienneT

+0

dobry punkt na razie zignorować. Zaktualizowałem odpowiedź za pomocą modyfikacji. – Rynant

5

Lubię podejście Rynant „s. Oto alternatywna implementacja przy użyciu -split zamiast IndexOf:

filter ColorWord([string]$word, [ConsoleColor]$color) { 
    $later = $false 
    $_ -split [regex]::Escape($word) | foreach { 
     if($later) { Write-Host "$word" -NoNewline -ForegroundColor $color } 
     else { $later = $true } 
     Write-Host $_ -NoNewline 
    } 
    Write-Host 
} 

Podział obejmuje pustych strun jeśli linia rozpoczyna się lub kończy się z danym słowem, stąd dodatkowy „jeśli nie pierwszy” logiki.


Edit: następujący komentarz Rynant, oto kolejna realizacja, który obsługuje zarówno proste i regex wzorów:

filter ColorPattern([string]$Pattern, [ConsoleColor]$Color, [switch]$SimpleMatch) { 
    if($SimpleMatch) { $Pattern = [regex]::Escape($Pattern) } 

    $split = $_ -split $Pattern 
    $found = [regex]::Matches($_, $Pattern, 'IgnoreCase') 
    for($i = 0; $i -lt $split.Count; ++$i) { 
    Write-Host $split[$i] -NoNewline 
    Write-Host $found[$i] -NoNewline -ForegroundColor $Color 
    } 

    Write-Host 
} 

Wyjście z poniższych przykładów pokazuje różnicę:

PS> '\d00\d!' | ColorPattern '\d' 'Magenta' -Simple
\d00\d!

PS> '\d00\d!' | ColorPattern '\d' 'Magenta'
\d00\d!

+1

Po prostu uważaj, że musisz uciec od znaków regex. Na przykład. '' Ke $ ha - Tik Tok.mp3 '| ColorWords 'ke \ $ ha' red'. Alternatywnie można użyć '$ _ -split [regex] :: Escape ($ word)' w filtrze.Chyba że chcesz dopasować do regex, co może być miłe. – Rynant

+0

@Rynant Dzięki za wskazanie tego! Zaktualizowano nową wersję. –

1
#$VerbosePreference = 'continue' 
$VerbosePreference = 'silent' 

filter ColorPattern { 
    param ([object]$colors, [switch]$SimpleMatch) 
    [string]$line = $_ 

    $collection = New-Object 'System.Collections.Generic.SortedDictionary[int, pscustomobject]' 
    $RegexOptions = [Text.RegularExpressions.RegexOptions]::IgnoreCase.value__ + [Text.RegularExpressions.RegexOptions]::Singleline.value__ 

    if ($SimpleMatch){ 
     $patternMatches = $colors.keys | % {[regex]::Escape($_)} 
     $reference = 'Value' 
    } else { 
     $patternMatches = $colors.keys 
     $reference = 'Pattern' 
    } 

    # detect RegEx matches and add to collection object 
    Write-Verbose "'$line'" 

    $measureparsing_match = (Measure-Command { 
     foreach ($pattern in $patternMatches){ 
      Write-Verbose "regex pattern: $pattern" 
      foreach ($match in ([regex]::Matches($line, $pattern, $RegexOptions))){ # lazy matching 
       Write-Verbose "`tmatch index: $($match.Index) length: $($match.length)" 

       $currentset = ($match.Index)..($match.Index + $match.length - 1) 
       Write-Verbose "`tcurrent set: $currentset" 

       if (-not [bool]$collection.Count){ 
        Write-Verbose "`t`tindex: $($match.Index) value: $($match.value) (inital add)" 
        [void]$collection.Add($match.Index, [PSCustomObject]@{Length = $match.Length; Value = $match.Value; Pattern = $pattern; Range = $currentset}) 
       } else { 
        (,$collection.Values) | % { 
         $currentRange = $_.range 
         $intersect = Compare-Object -PassThru $currentset $currentRange -IncludeEqual -ExcludeDifferent 
         if ($intersect){ 
          Write-Verbose "`t`tintersect: $([string]($intersect | % {[string]::Concat($_)})) (skipped)" 

          $nonintersect = Compare-Object -PassThru $currentset $intersect 
          Write-Verbose "`t`tnonintersect: $([string]($nonintersect | % {[string]::Concat($_)}))" 

          $nonintersect | % { 
           if ($currentRange -notcontains $_){ 
            Write-Verbose "`t`tindex: $_ value: $($line[$_]) (adding intersect-fallout)" 
            [void]$collection.Add($_, [PSCustomObject]@{Length = $_.Length; Value = $line[$_]; Pattern = $pattern; Range = $currentset}) 
           } else { 
            Write-Verbose "`t`t`tindex: $_ value: $($line[$_]) (skipped intersect-fallout)" 
           } 
          } 
         } else { 
          Write-Verbose "`t`tindex: $($match.index) value: $($match.value) (adding nonintersect)" 
          [void]$collection.Add($match.Index, [PSCustomObject]@{Length = $match.Length; Value = $match.Value; Pattern = $pattern; Range = $currentset}) 
         } 
        } # end values 
       } #end if 
      } # end matching 
     } # end pattern 
    }).TotalMilliseconds 

    $measureparsing_nonmatch = (Measure-Command { 
     if ([bool]$collection.count){ # if there are no matches, skip! 
      Compare-Object -PassThru ` 
      -ReferenceObject (
       $collection.Keys | % { # all matched keys and their lengths 
        $word = $collection.item($_) 
        $currentlength = ($word.value).length 
        ($_..($_ + ($currentlength - 1))) 
       }) ` 
      -DifferenceObject (0..($line.Length - 1)) | # entire line 
       % {[void]$collection.Add($_, [PSCustomObject]@{Length = $_.length; Value = $line[$_]})} # add non matches to collection 
     } 
    }).TotalMilliseconds 

    Write-Verbose "match: $measureparsing_match ms. VS nonmatch: $measureparsing_nonmatch ms." 

    $collection.keys | % { 
     $word = $collection.item($_) 
     if ($word.pattern){ 
      if ($colors.ContainsKey($word.$reference)){ 
       $color = @{ 
        ForegroundColor = $colors[$word.$reference].ForegroundColor; 
        BackgroundColor = $colors[$word.$reference].BackgroundColor 
       } 
       if ($word.value){ 
        Write-Host -NoNewline $([string]::Concat($word.value)) @color 
       } 
      } 
     } else { 
      Write-Host -NoNewline $([string]::Concat($word.value)) 
     } 
    } 
    Write-Host # needed for line feed 
} 

$Patterns = [ordered]@{ 
    # higher in list takes precendence 
    'stopped' = @{ForegroundColor = 'Red'; BackgroundColor='DarkRed'} 
    'running' = @{ForegroundColor = 'Green'; BackgroundColor='DarkGreen'} 
    'paused' = @{ForegroundColor = 'Yellow'; BackgroundColor='DarkYellow'} 
    0 = @{ForegroundColor = 'White'; BackgroundColor='Gray'} 
    '\d+' = @{ForegroundColor = 'Gray'; BackgroundColor='Black'} 
    '\.' = @{ForegroundColor = 'Magenta'; BackgroundColor='DarkMagenta'} 
    '(a|e|i|o|u)' = @{ForegroundColor = 'Yellow'; BackgroundColor='DarkYellow'} 
    '\w+' = @{ForegroundColor = 'Cyan'; BackgroundColor='DarkCyan'} 

} 

# strongly typed collection.. we could probably do this better.. 
$colorCollection = New-Object 'system.collections.generic.dictionary[string, hashtable]'([StringComparer]::OrdinalIgnoreCase) # Ordinal 
$Patterns.GetEnumerator() | % {[void]$colorCollection.Add($_.Name, $_.Value)} 

Get-Service | Out-String -Stream | ColorPattern -colors $colorCollection 
#Get-Service | Out-String -Stream | ColorPattern -colors $colorCollection -SimpleMatch 

Trochę późno na odpowiedź, ale już aktualizowany to ze stwardnieniem wsparcia regex, a także prostego dopasowania. Zostało to przetestowane w Powershell v4.0.

3

Uwielbiam odpowiedź @Ryant dał. Mam tutaj zmodyfikowaną wersję, która może być używana do kolorowania wielu słów w wyniku poprzez przekazywanie tablic lub słów i kolorów. Sztuką jest to, że musisz podzielić tekst wejściowy na linie na podstawie separatora nowej linii.

filter ColorWord2 { 
param(
    [string[]] $word, 
    [string[]] $color 
) 
$all = $_ 
$lines = ($_ -split '\r\n') 

$lines | % { 
    $line = $_  
    $x = -1 

    $word | % { 
     $x++ 
     $item = $_  

     $index = $line.IndexOf($item, [System.StringComparison]::InvariantCultureIgnoreCase)        
      while($index -ge 0){ 
       Write-Host $line.Substring(0,$index) -NoNewline     
       Write-Host $line.Substring($index, $item.Length) -NoNewline -ForegroundColor $color[$x] 
       $used =$item.Length + $index 
       $remain = $line.Length - $used 
       $line =$line.Substring($used, $remain) 
       $index = $line.IndexOf($item, [System.StringComparison]::InvariantCultureIgnoreCase) 
      } 
     } 

    Write-Host $line 
} } 

i będzie realizowane w następujący sposób

Get-Service | Format-Table| Out-String| ColorWord2 -word 'Running','Stopped' -color 'Green','Red' 
+0

Uwielbiam tę odpowiedź, dokładnie tego szukałem. – sch4v4

+0

Pozdrawiam. Obecnie używam odmiany. Zamiast przechodzić przez dwie tablice, po prostu przekazuję tablicę hashtable, której kolor jest kluczem, a wartość jest tablicą słów, do których przypisany jest kolor. – stevethethread

+0

@stevethethread \t Czy możesz udostępnić swoją najnowszą wersję? –

Powiązane problemy