Home > Enterprise >  Get the line number of last occurrence of a particular string with [Linq.Enumerable] in PowerShell
Get the line number of last occurrence of a particular string with [Linq.Enumerable] in PowerShell

Time:01-28

Is there a way to get the line index of the last occurrence in file, not the line itself?

$delegate = [Func[string,bool]] {$args[0] -match $myString}
$lastCheckIn  = [Linq.Enumerable]::Last([System.IO.File]::ReadLines($myFile), $delegate)

I thought of using [Linq.Enumerable]::Count but couldn't find a method to return a sequence from the start to $lastCheckIn.

CodePudding user response:

Limiting the approach to Linq, you can try the following.

$lines = 'aaa','bbb','ccc','ddd','aaa','bbb','ccc','ddd'
$searchPattern = 'c '

$selectDelegate = [Func[object, int32, object]] { @{line=$args[0]; index=$args[1] } }
$whereDelegate = [Func[object,bool]] { $args[0].line -match $searchPattern }

$objects = [Linq.Enumerable]::Select($lines, $selectDelegate)
$lastObject = [Linq.Enumerable]::Last($objects, $whereDelegate)
$lastObject.index
# result = 6 (zero-based index)

# Without Linq
$lines | Select-String $searchPattern | Select-Object -Last 1 -ExpandProperty LineNumber
# result = 7 (one-based index)

This first builds up a collection of line/index pairs, filters for the last match, and then extract the index. I've also included a non-linq powershell equivalent.

CodePudding user response:

I believe this should be faster and easier to implement, maintain and understand:

$pattern = 'yourpatternhere'
$content = [System.IO.File]::ReadAllLines('path/to/file.ext')
$tail = $content.Count

while($tail--)
{
    if($content[$tail] -match $pattern)
    {
        "$pattern was found on line: $tail"
        break
    }
}

CodePudding user response:

T N's helpful answer shows an effective LINQ solution as well as a PowerShell-idiomatic alternative.

There are two alternatives for improved performance:

[Array]::FindLastIndex(
  (Get-Content -ReadCount 0 $myFile), # -ReadCount 0 returns all lines as single array
  [Predicate[string]] { $args[0] -match $myString }
)
  • An optimized LINQ solution, with lazy enumeration:
$i = $index = -1
$null = [Linq.Enumerable]::LastOrDefault(
  [IO.File]::ReadLines($myFile),
  [Func[string, bool]] { 
       $script:i; 
     if ($args[0] -match $myString) { $script:index = $script:i; return $true } 
  }
)
$index # output the index of the last match, if not found, -1

Caveat: This approach to finding the index only works as intended with lazy enumerables as input, as only they necessitate forward enumeration until the very last element.

By contrast, list-like enumerables (those that implement the System.Collections.IList interface or its generic counterpart), are enumerated backwards, from the end of the list, for optimized performance.

  •  Tags:  
  • Related