Skip to content

Instantly share code, notes, and snippets.

@earthdiver
Created April 29, 2024 05:33
Show Gist options
  • Select an option

  • Save earthdiver/0dbb016790ba531fb9e3e8002d7b671a to your computer and use it in GitHub Desktop.

Select an option

Save earthdiver/0dbb016790ba531fb9e3e8002d7b671a to your computer and use it in GitHub Desktop.
<#
.SYNOPSIS
A grep-like tool with a color highlighting feature.
.DESCRIPTION
This function searches for text patterns in input strings.
If input is an object, it is converted to a string prior to processing.
Matched characters are highlighted.
'(some command) | Select-MatchedString -Pattern <regex>'
is similar to
'(some command) | Output-String -Stream | Select-String -Pattern <regex> -AllMatch -CaseSensitive'
with PowerShell 7.X, in which Select-String cmdlet has a highlighting feature.
.PARAMETER Pattern
Specifies the text patterns to find. Type a string or regular expression.
If you type a string, use the SimpleMatch parameter.
.PARAMETER BackgroundColor
Specifies the background color for matches. (Alias: -bc)
The default value is "Blue".
.PARAMETER CapturegroupColor
Specifies the foreground color for capture-group matches. (Alias: -cc)
The default value is "Red".
.PARAMETER ForegroundColor
Specifies the foreground color for matches. (Alias: -fc)
The default value is "White".
.PARAMETER Group
Specifies the name or number of capture group. (Alias: -g)
The default value is "0".
.PARAMETER ECMAScript
Enables ECMAScript-compliant behavior. (Alias: -e)
.PARAMETER IgnoreCase
Makes matches case-insensitive. By default, matches are case-sensitive. (Alias: -i)
.PARAMETER InputObject
Specifies the text to be searched. (Alias: -io)
.PARAMETER PassThru
Outputs all lines, including ones that do not match. (Alias: -p)
.PARAMETER Narrow
Converts wide characters into narrow ones internally. (Alias: -n)
Useful when you don't want to distinguish between narrow and wide characters.
.PARAMETER SimpleMatch
Uses a simple match rather than a regular expression match. (Alias: -s)
.NOTES
Author: earthdiver1
Version: V1.03
Licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
#>
Function Select-MatchedString {
[Alias('hGrep')]
Param(
[Parameter(Mandatory=$True)][String]$Pattern,
[Alias("g")][String]$Group = "0",
[Alias("e")][Switch]$ECMAScript,
[Alias("i")][Switch]$IgnoreCase,
[Alias("w")][Switch]$Narrow,
[Alias("n")][Switch]$Number,
[Alias("p")][Switch]$PassThru,
[Alias("s")][Switch]$SimpleMatch,
[Alias("bc")][ConsoleColor]$BackgroundColor = "Blue",
[Alias("cc")][ConsoleColor]$CapturegroupColor = "Red",
[Alias("fc")][ConsoleColor]$ForegroundColor = "White",
[Parameter(ValueFromPipeline=$True)][Alias("io")][PSObject]$InputObject
)
Begin {
try {
if ( -not $Pattern ) { break }
if ( $Narrow ) {
Add-Type -AssemblyName "Microsoft.VisualBasic"
$Pattern = [Microsoft.VisualBasic.Strings]::StrConv($Pattern,[Microsoft.VisualBasic.VbStrConv]::Narrow)
}
if ( $SimpleMatch ) { $Pattern = [regex]::Escape( $Pattern ) }
if ( $Number ) {
$line = 0
$width = $host.UI.RawUI.BufferSize.Width - 7
} else {
$width = $host.UI.RawUI.BufferSize.Width - 1
}
$regexOptions = "Compiled"
if ( $ECMAScript ) { $regexOptions += ", ECMAScript" }
if ( $IgnoreCase ) { $regexOptions += ", IgnoreCase" }
$Regex = New-Object Text.RegularExpressions.Regex $Pattern, $regexOptions
$process_block = {
Process {
$line++
$i = 0
if ( $Narrow ) { $_ = [Microsoft.VisualBasic.Strings]::StrConv($_,[Microsoft.VisualBasic.VbStrConv]::Narrow) }
$match = $Regex.Match($_,$i)
$m = $match.Groups[$Group]
if (-not $PassThru -and -not $m.Success) { return }
if ( $Number ) { Write-Host $("{0,5}:" -F $line) -NoNewline }
if ( $Group -eq "0" ) {
while ($m.Success) {
if ( $m.Index -ge $_.Length ) { break }
if ( $m.Length -gt 0 ) {
Write-Host $_.SubString($i, $m.Index - $i) -NoNewline
Write-Host $m.Value -BackgroundColor $BackgroundColor -ForegroundColor $ForegroundColor -NoNewline
$i = $m.Index + $m.Length
} else {
Write-Host $_.SubString($i, $m.Index - $i + 1) -NoNewline
$i = $m.Index + 1
}
$m = $Regex.Match($_,$i).Groups[0]
}
} else {
while ( $m.Success ) {
if ( $m.Index -ge $_.Length ) { break }
$m0 = $match.Groups[0]
if ( $m0.Length -gt 0 ) {
Write-Host $_.SubString($i, $m0.Index - $i) -NoNewline
Write-Host $_.SubString($m0.Index, $m.Index - $m0.Index) `
-BackgroundColor $BackgroundColor -ForegroundColor $ForegroundColor -NoNewline
Write-Host $m.Value -BackgroundColor $BackgroundColor -ForegroundColor $CapturegroupColor -NoNewline
$i = $m0.Index + $m0.Length
$ii = $m.Index + $m.Length
Write-Host $_.SubString($ii, $i - $ii) `
-BackgroundColor $BackgroundColor -ForegroundColor $ForegroundColor -NoNewline
} else {
Write-Host $_.SubString($i, $m0.Index - $i + 1) -NoNewline
$i = $m0.Index + 1
}
$match = $Regex.Match($_,$i)
$m = $match.Groups[$Group]
}
}
Write-Host $_.SubString($i)
}
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Out-String',[System.Management.Automation.CommandTypes]::Cmdlet)
$wrappedCmdParameters = @{}
if ( $PSBoundParameters.ContainsKey("InputObject") ) { $wrappedCmdParameters.Add("InputObject",$InputObject) }
$wrappedCmdParameters.Add("Stream", $True)
$wrappedCmdParameters.Add("Width", $width)
$scriptCmd = {& $wrappedCmd @wrappedCmdParameters | & $process_block }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
Process {
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
End {
try {
$steppablePipeline.End()
Remove-Variable Regex
} catch {
throw
}
}
}
Export-ModuleMember -Function Select-MatchedString -Alias hGrep
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment