Reputation: 350
This is a question of technique, but as an exercise my intention is to write a PS to accept piped input, with a regex as a parameter, and highlight any text matching the regex.
The part I'm not able to find any info on is that it's easy to match text, capturing to a buffer, or to replace text. But I need to replace matched text with color control, the original text, then resume the previous color. I can't seem to find any way to generate color output other than with write-output, and can't do separate colors in a single write, which would mean:
-matching the regex
-write-host out all text prior to the match in default color, with -NoNewLine
-write-host the match, with -NoNewLine
-write-host the remainder
This seems messy, and gets even more messy if we want to support multiple matches. Is there a more eloquent way to do this?
Upvotes: 7
Views: 2569
Reputation: 169
Alternatively I found using ANSI/VT100 formatting more simple and does exactly what I needed with a much larger range of colors:
$esc=[char]27
$fileContents="abc455315testing123455315abc"
$keywordSearch="testing123"
$fileContents -replace $keywordSearch,"$esc[38;2;0;200;255m$keywordSearch$esc[0m"
Note this only works in a PowerShell console window not in PowerShell ISE. This wikipedia page also was helpful; specifically this line with regards to choosing a color:
ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB foreground color
Upvotes: 1
Reputation: 5748
This is an extension of latkin's answer. Here I'm extending the Match object such that it can be processed for this purpose - and others - more easily.
function Split-Match {
param([Parameter(Mandatory = $true)]
$match
)
$sections = @()
$start = 0
$text = $m.Line
foreach ($m in $match.Matches) {
$i = $m.Index
$l = $m.Length
$sections += $false, $text.Substring($start, $i - $start)
$sections += $true, $text.Substring($i, $l)
$start = $i + $l
}
$sections += $false, $text.Substring($start)
$match | Add-Member -Force Sections $sections
$match
}
function Write-Match {
param([Parameter(Mandatory = $true)]
$match
)
$fg = "White"
$bg = "Black"
foreach($s in $match.Sections) {
if ($s.GetType() -eq [bool]) {
if ($s) {
$fg = "White"
$bg = "Red"
} else {
$fg = "White"
$bg = "Black"
}
} else {
Write-Host -NoNewline -ForegroundColor $fg -BackgroundColor $bg $s
}
}
Write-Host
}
$string = @'
Match this A
Not this B
Not this C
But this A
'@
$m = $string | select-string -CaseSensitive -AllMatches "A"
$m = Split-Match $m
Write-Match $m
Upvotes: 1
Reputation: 16792
Write-Host
is the right way to do this. Use the .Index
and .Length
properties of the resulting Match
object to determine where exactly the matched text is. You just need to be a bit careful keeping track of indices :)
This works for multiple matches, and is not terribly untidy IMO:
function ColorMatch
{
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string] $InputObject,
[Parameter(Mandatory = $true, Position = 0)]
[string] $Pattern
)
begin{ $r = [regex]$Pattern }
process
{
$ms = $r.Matches($inputObject)
$startIndex = 0
foreach($m in $ms)
{
$nonMatchLength = $m.Index - $startIndex
Write-Host $inputObject.Substring($startIndex, $nonMatchLength) -NoNew
Write-Host $m.Value -Back DarkRed -NoNew
$startIndex = $m.Index + $m.Length
}
if($startIndex -lt $inputObject.Length)
{
Write-Host $inputObject.Substring($startIndex) -NoNew
}
Write-Host
}
}
Upvotes: 6