How to Grep in PowerShell

Hello!

In oldschool Linux shells, you search files for a string with grep. You’re probably used to commands like this (example results from an OSS repo):

grep -r things .
./terraform.tfstate.backup:              "./count_things.py"
./count_things.py:def count_things(query):
./count_things.py:    count_things()
./terraform.tf:  program = ["python", "${path.module}/count_things.py"]

It outputs strings that concatenate the filename and the matching line. You can pipe those into awk or whatever other command to process them. Standard stuff.

You can achieve the same results in PowerShell, but it’s pretty different. Here’s the basic command:

Get-ChildItem -Recurse | Select-String 'things'

count_things.py:7:def count_things(query):
count_things.py:17:    count_things()
terraform.tf:6:  program = ["python", "${path.module}/count_things.py"]
terraform.tfstate.backup:25:              "./count_things.py"

This part is similar. Get-ChildItem recurses through the filesystem and passes the results to Select-String, which searches those files for the string things. The output looks the same. File on the left, matching line on the right. That’s just friendly formatting, though. Really what you’re getting is an array of objects that each represent one match. Posh summarizes that array with formatting that’s familiar, but actually processing these results is completely different.

We could parse out details the Linux way by piping into Out-String to convert the results into strings, splitting on :, and so on, but that’s not idiomatic PowerShell. Posh is object-oriented, so instead of manipulating strings we can just process whichever properties contain the information we’re searching for.

First, we need to know what properties are available:

Get-ChildItem -Recurse | Select-String 'things' | Get-Member

   TypeName: Microsoft.PowerShell.Commands.MatchInfo

Name               MemberType Definition
----               ---------- ----------
Equals             Method     bool Equals(System.Object obj)
GetHashCode        Method     int GetHashCode()
GetType            Method     type GetType()
RelativePath       Method     string RelativePath(string directory)
ToEmphasizedString Method     string ToEmphasizedString(string directory)
ToString           Method     string ToString(), string ToString(string directory)
Context            Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename           Property   string Filename {get;}
IgnoreCase         Property   bool IgnoreCase {get;set;}
Line               Property   string Line {get;set;}
LineNumber         Property   int LineNumber {get;set;}
Matches            Property   System.Text.RegularExpressions.Match[] Matches {get;set;}
Path               Property   string Path {get;set;}
Pattern            Property   string Pattern {get;set;}

Get-Member tells us the properties of the MatchInfo objects we piped into it. Now we can process them however we need.

Select One Property

If we only want the matched lines, not all the other info, and we can filter out the Line property with Select-Object.

Get-ChildItem -Recurse | Select-String 'things' | Select-Object 'Line'

Line
----
def count_things(query):
    count_things()
  program = ["python", "${path.module}/count_things.py"]
              "./count_things.py"

Sort Results

We can sort results by the content of a property with Sort-Object.

Get-ChildItem -Recurse | Select-String 'things' | Sort-Object -Property 'Line'

terraform.tfstate.backup:25:              "./count_things.py"
count_things.py:17:    count_things()
terraform.tf:6:  program = ["python", "${path.module}/count_things.py"]
count_things.py:7:def count_things(query):

Add More Filters

Often, I search for a basic pattern like ‘things’ and then chain in Where-Object to filter down to more specific results. It can be easier to chain matches as I go than to write a complex match pattern at the start.

Get-ChildItem -Recurse | Select-String 'things' | Where-Object 'Line' -Match 'def'

count_things.py:7:def count_things(query):

We’re not limited to filters on the matched text, either:

Get-ChildItem -Recurse | Select-String 'things' | Where-Object 'Filename' -Match 'terraform'

terraform.tf:6:  program = ["python", "${path.module}/count_things.py"]
terraform.tfstate.backup:25:              "./count_things.py"

There are tons of things you can do. The main detail to remember is that you need Get-Member to tell you what properties are available, then you can use any Posh command to process those properties.

Enjoy freedom from strings!

Adam

Need more than just this article? I’m available to consult.

You might also want to check out these related articles: