Color words in powershell script format-table output

后端 未结 4 2004
梦如初夏
梦如初夏 2021-01-12 11:03

Is it possible to color only certain words (not complete lines) for a powershell output using format-table. For example, this script scans a folder recursively for a string

相关标签:
4条回答
  • 2021-01-12 11:17

    You could pipe the table into Out-String, then write the string in parts using Write-Host with the -NoNewLine switch.

    Something like this:

    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
    
    0 讨论(0)
  • 2021-01-12 11:24

    I love answer @Ryant gave. I have a modified version here that can be used for colouring multiple words in an output by passing in arrays or words and colours. The trick is that you have to split the input text into lines based on the newline separator.

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

    and would be executed as follows

    Get-Service | Format-Table| Out-String| ColorWord2 -word 'Running','Stopped' -color 'Green','Red'
    
    0 讨论(0)
  • 2021-01-12 11:25
    #$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
    

    Kind of late to the reply, but I've updated this with multiple regex support, as well as simple matching. This was tested under Powershell v4.0.

    0 讨论(0)
  • 2021-01-12 11:37

    I like Rynant's approach. Here is an alternate implementation, using -split instead of 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
    }
    

    Split includes empty strings if the line starts or ends with the given word, hence the extra "if not first" logic.


    Edit: Following Rynant's comment, here's another implementation that supports both simple and regex patterns:

    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
    }
    

    The output from the following examples shows the difference:

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

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

    0 讨论(0)
提交回复
热议问题