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? We’re available to consult.
You might also want to check out these related articles: