PowerShell equivalent of find

TL;DR: gci -r -fi <filename-pattern>

My favorite use of find in bash is to find files whose name matches a pattern. For instance, find all the jar files under this directory:

bash# find . -name '*.jar'

In PowerShell, there’s a program called “find” but it ain’t the same program.

Short Answer

Instead, use Get-ChildItem. The arguments are different.

  • -Recurse (abbreviates to -r) says, go down all the directories.
  • -Filter <string> (abbreviates to -fi) selects by name. This supports * and ? wildcards, NOT regex.

Long version, which I would use in programs:

pwsh> Get-ChildItem -Recurse -Filter '*.jar'

or shorthand, for typing at the prompt:

pwsh> gci -r -fi *.jar

but I want regex

The -Filter command-line option here is an optimization. For programmatic filtering: use Get-ChildItem -Recurse to gather all the files under the current directory, and pipe them to a Where-Object (abbreviates to where) filter.

You can test properties, like matching the name against a regex:

gci -r | where Name -match '\.jar$'

like checking the size of the file, excluding too-small ones:

gci -r | where Length -gt 3000

like only the files I’ve looked at in the last hour:

gci -r | where LastAccessTime -gt (Get-Date).AddHours(-1)

In my PowerShell in Windows Terminal, I get tab-completion for the property names! This is based on the type of objects returned by the gci command before the pipe. Whoa cool!

For more flexibility, you can break into a code block, referencing the input with $_. How about… length greater than 3kb or else I wrote to it since a specific day:

gci -r | where { $_.Length -gt ( 3 * 1024 ) -or $_.LastWriteTime -gt "04/23/2020" }

but that’s so long

The query for “size is large enough or I’ve accessed it in the last hour” works in bash too (I think):

bash# find . -size +3k -o -amin +60

There are two reasons I like the PowerShell version better than bash’s find:

  • it scales up in complexity; you can write a whole program in there if you need to. find offers the conditions that it offers, and the combinations that it offers. (If you can say “name is *.jar and size is large or date is recent”, I couldn’t figure out how.)
  • The arguments to find are specific to find. Once you learn them, that knowledge doesn’t help you with any other command in bash. In PowerShell, the condition is expressed in the same language as everything else in PowerShell. That scales, in your head.

There’s one reason I like bash’s find better than PowerShell syntax:

I can type find . -name '*.jar' without thinking about it.

I have it memorized. It is familiar. That’s a property of me; it’s my shared history with bash that makes me productive in it, NOT its superiority.

So I don’t recommend going deep on bash. I recommend getting productive in PowerShell instead, because learning scales in a language with deep consistency.

Addendum: condense the output

The output of Get-ChildItem is a bunch of FileInfo objects. The default printing takes up a lot of room. You can get it to print the path (which is called FullName):

Get-ChildItem -Recurse -Include '*.json' | Select FullName

You can also get other properties from Select. To see them all, pipe to Get-Member instead. That lists available properties.