Searching for Strings
Recently, I saw a message on social media about the struggles of working with Select-String
in PowerShell. This is a useful cmdlet, but I'll admit it can be tricky to work with and the structured output may feel confusing. I thought it would be helpful to look at this cmdlet in detail and provide some examples of how to use it effectively.
Basics
Select-String
is the grep equivalent of PowerShell. When you are trying to find something such as a keyword or phrase. Typically, you would use it to search through text files, but it can also be used to search through the output of other cmdlets. I'm not going to go through every possible use case and parameter, so be sure to spend a few minutes reading the full help and examples.
The cmdlet will process input, typically text files, and look for pattern matches. Here's a very simple example.
PS C:\> Get-Content $profile | Select-String "Import-Module"
Import-Module $item -Force
Import-Module Terminal-Icons
#Import-Module WTToolBox
Import-Module C:\scripts\PSPodcast\PSPodcast.psd1 -force -ErrorAction Stop
"Import-Module",
| When run in PowerShell 7 the matched text will be highlighted.
I want to search my profile script for the Import-Module
command. Although typically, you will want to search through a collection of files.
PS D:\PSBehind\manuscript> dir *.md | Select-String gridview | tee -variable a
parameter-planning.md:103:237 Passthru {Add-TickleEvent,Copy-FromGridView,Copy-HistoryCommand...
parameter-planning.md:109:109 Force {Copy-FromGridView,Copy-PSFunction,Export-PSStyleFileI...
proper-powershell-practice.md:31:Out-GridView -title "Running"
richer-logging-for-powershell.md:143:PS C:\> Import-Csv $log.fullname | Out-GridView
In this example I'm search the manuscript files for the "Behind the PowerShell Pipeline" book, looking for instances of 'gridview`. Notice that you don't need to get the content of the files. But you can.
PS D:\PSBehind\manuscript> Get-Content *.md | Select-String 'gridview' | tee -variable b
237 Passthru {Add-TickleEvent, Copy-FromGridView, Copy-HistoryCommand...
109 Force {Copy-FromGridView, Copy-PSFunction, Export-PSStyleFileI...
Out-GridView -title "Running"
PS C:\> Import-Csv $log.fullname | Out-GridView
Curiously, the output is formatted differently, even though the information is essentially the same. In the first example, the output is a little more structured. It shows you the file name, the line number and the matching line. In the second example, by content, the output is only the matching line. However, that is only what is displayed. The actual object output is almost the same.
PS D:\PSBehind\manuscript> $a[0] | select *
IgnoreCase : True
LineNumber : 103
Line : 237 Passthru {Add-TickleEvent, Copy-FromGridView, Copy-HistoryCommand...
Filename : parameter-planning.md
Path : D:\PSBehind\manuscript\parameter-planning.md
Pattern : gridview
Context :
Matches : {0}
PS D:\PSBehind\manuscript> $b[0] | select *
IgnoreCase : True
LineNumber : 6388
Line : 237 Passthru {Add-TickleEvent, Copy-FromGridView, Copy-HistoryCommand...
Filename : InputStream
Path : InputStream
Pattern : gridview
Context :
Matches : {0}
Selecting the string from the file shows me the line number in parameter-planning.md. However when streaming strings, the line number is the position in the stream, i.e 6388. This isn't useful other than letting me know it exists. Any maybe that's all you need.
Performance wise, streaming strings is a little more resource intensive. My example tool 112ms to process 46 files. Compared to processing files which took 0.041ms. While it may conceptually make sense pipe files to Select-String
, if you use the -Path
parameter, it should be event faster.
Select-String -Path *.md -pattern gridview
I get the same result, but it only took 0.025ms. Granted, I am only processing a handful of files. But if you are searching thousands of files, it could make a difference. Why don't I test this.
dir c:\scripts\*.ps1 -Recurse -ov f | select-string system.drawing
This took almost 13 seconds to process 5736 files.
Select-String -path $f -pattern system.drawing
This took 1.6seconds! One major difference is that I already had the file list created. In the first example, I had to pipeline the files. This was only because I wanted to recurse through folders. Even getting the files first is a performance improvement.
$f = dir c:\scripts\*.ps1 -recurse
Select-String -path $f -pattern system.drawing
This took 7.1 seconds.