Finding files, do I even need to describe how important this is?
From finding README files in a project
$ find . -name README*
./app/README.md
./README.md
./ipsum.md/README.md
to locating that one config file you never can remember the location of.
$ find . -name dnsconf.yaml
./app/module1/dnsconf.yaml
Info
All commands in this post are run against a open source repository to allow you to reproduce output & play with the commands.
find
$ find . -name README*
./app/README.md
./README.md
./ipsum.md/README.md
This will list all files matching the README*
pattern in my current working directory.
With PowerShell we will use Get-ChildItem
and define our search term in the -Filter
parameter.
Additionally we give it the parameter to recurse, wich it would not do by default.
PS> Get-ChildItem -Path "." -Recurse -Filter "README*"
Directory: /Users/mkamner/projects/mkamner/scripts
Mode LastWriteTime Length Name
---- ------------- ------ ----
----- 12/06/2020 07:23 1397 README.md
Directory: /Users/mkamner/projects/mkamner/scripts/app
Mode LastWriteTime Length Name
---- ------------- ------ ----
----- 12/31/2020 14:29 115 README.md
Directory: /Users/mkamner/projects/mkamner/scripts/app/module3
Mode LastWriteTime Length Name
---- ------------- ------ ----
----- 12/31/2020 14:33 0 README
Directory: /Users/mkamner/projects/mkamner/scripts/ipsum.md
Mode LastWriteTime Length Name
---- ------------- ------ ----
----- 12/31/2020 14:15 55 README.md
As you can see PowerShell found one additional file: ./app/module3/README
, this is because of the different behavior of *
in Bash and PowerShell.
To be honest, the PowerShell behavior actually works in our favor here.
Either way, the output is far from great! Let’s fix this with Format-Table
.
PS> Get-ChildItem -Path "." -Recurse -Filter "README*" | Format-Table FullName
FullName
--------
/Users/mkamner/projects/mkamner/scripts/README.md
/Users/mkamner/projects/mkamner/scripts/app/README.md
/Users/mkamner/projects/mkamner/scripts/app/module3/README
/Users/mkamner/projects/mkamner/scripts/ipsum.md/README.md
find -type
By default find returns files and folders alike.
$ find . -name "*.md"
./app/README.md
./README.md
./ipsum.md
./ipsum.md/5.md
./ipsum.md/1.md
./ipsum.md/0.md
./ipsum.md/4.md
./ipsum.md/README.md
./ipsum.md/9.md
./ipsum.md/8.md
./ipsum.md/3.md
./ipsum.md/7.md
./ipsum.md/6.md
./ipsum.md/2.md
PowerShell does the same.
PS> Get-ChildItem -Path "." -Recurse -Filter "*md" | Format-Table FullName
FullName
--------
/Users/mkamner/projects/mkamner/scripts/ipsum.md
/Users/mkamner/projects/mkamner/scripts/README.md
/Users/mkamner/projects/mkamner/scripts/app/README.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/0.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/1.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/2.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/3.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/4.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/5.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/6.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/7.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/8.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/9.md
/Users/mkamner/projects/mkamner/scripts/ipsum.md/README.md
For find this can be altered by using the -type
parameter and setting it to either
-
d
for directories$ find . -name "*.md" -type d ./ipsum.md
-
or
f
for files$ find . -name "*.md" -type f ./app/README.md ./README.md ./ipsum.md/5.md ./ipsum.md/1.md ./ipsum.md/0.md ./ipsum.md/4.md ./ipsum.md/README.md ./ipsum.md/9.md ./ipsum.md/8.md ./ipsum.md/3.md ./ipsum.md/7.md ./ipsum.md/6.md ./ipsum.md/2.md
PowerShell can replicate this with two unique parameters.
-
-Directory
PS> Get-ChildItem -Path "." -Recurse -Filter "*md" -Directory | Format-Table FullName FullName -------- /Users/mkamner/projects/mkamner/scripts/ipsum.md
-
and
File
PS> Get-ChildItem -Path "." -Recurse -Filter "*md" -File | Format-Table FullName FullName -------- /Users/mkamner/projects/mkamner/scripts/README.md /Users/mkamner/projects/mkamner/scripts/app/README.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/0.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/1.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/2.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/3.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/4.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/5.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/6.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/7.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/8.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/9.md /Users/mkamner/projects/mkamner/scripts/ipsum.md/README.md
Tipps & Tricks
- To include hidden files and folders (starting with
.
) you should add the parameter-Force
- You can control how deep the folder structure should be searched with
-Depth
Shortcuts
Writing out Get-ChildItem
every time is nothing to be desired, thankfully we can take some shortcuts.
- Shorten
Get-ChildItem
by using it’s aliasgci
- Positional arguments
-Path
can be provided as positional argument 0-Filter
can be provided as positional argument 1
- Omit
""
around your strings as long as they have no spaces inside - Commands are always case insensitive, this is more of a personal preference
- Although not required
Format-Table
can also be shortened toft
.
With this shortcuts our commands are way shorter.
Get-ChildItem -Path "." -Recurse -Filter "README*"
gci . README* -Recurse
Get-ChildItem -Path "." -Recurse -Filter "README*" | Format-Table FullName
gci . README* -Recurse | ft FullName
Get-ChildItem -Path "." -Recurse -Filter "*md" | Format-Table FullName
gci . *md -Recurse | ft FullName
Get-ChildItem -Path "." -Recurse -Filter "*md" -Directory | Format-Table FullName
gci . *md -Recurse -Directory | ft FullName
Get-ChildItem -Path "." -Recurse -Filter "*md" -File | Format-Table FullName
gci . *md -Recurse -File | ft FullName
Taking It Further
This covers basic examples for replacing find in PowerShell.
Thanks to PowerShells object oriented piping system the information we see on screen is not everything we get to work with.
Lets take a look with one on the previous examples
and plug it into the fictional pipeline to search all config files for occurrences of 1.1.1.1
.
We will use commands from my How To Grep With PowerShell post
to create a report of all files matching *conf*
and containing 1.1.1.1
.
Get-ChildItem -Path "." -Recurse -Filter "*conf*" |
Foreach-Object {Select-String -Path $_.PSPath -Pattern "1.1.1.1" | Select-Object Path,LineNumber,Line} |
Export-Csv -Path cloudflare_dns.csv -NoTypeInformation
Obviously this is just a example demonstrating the many possibilities in having a rich object returned. You could write them to a variable for later use, loop over them or any other thing a real programming language allows you to do.
For quick reference this are all values accessible inside every object returned.
PSPath : Microsoft.PowerShell.Core\FileSystem::/Users/mkamner/projects/mkamner/scripts/app/conf
PSParentPath : Microsoft.PowerShell.Core\FileSystem::/Users/mkamner/projects/mkamner/scripts/app
PSChildName : conf
PSDrive : /
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
Mode : -----
ModeWithoutHardLink : -----
VersionInfo : File: /Users/mkamner/projects/mkamner/scripts/app/conf
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language:
BaseName : conf
Target :
LinkType :
Length : 27
DirectoryName : /Users/mkamner/projects/mkamner/scripts/app
Directory : /Users/mkamner/projects/mkamner/scripts/app
IsReadOnly : False
FullName : /Users/mkamner/projects/mkamner/scripts/app/conf
Extension :
Name : conf
Exists : True
CreationTime : 12/31/2020 14:32:17
CreationTimeUtc : 12/31/2020 13:32:17
LastAccessTime : 12/31/2020 14:56:07
LastAccessTimeUtc : 12/31/2020 13:56:07
LastWriteTime : 12/31/2020 14:56:05
LastWriteTimeUtc : 12/31/2020 13:56:05
Attributes : Normal
The LastWriteTime
value is usually the most interesting to me, as it allows me to quickly filter on only files changed in the last X days or hours.
PS> Get-ChildItem -Path "." -Recurse -Filter "*conf*" | Where-Object {$_.LastWriteTime -gt (Get-Date).addDays(-1)} | Format-Table FullName
FullName
--------
/Users/mkamner/projects/mkamner/scripts/app/conf
/Users/mkamner/projects/mkamner/scripts/app/module2/conf.yaml
/Users/mkamner/projects/mkamner/scripts/app/module1/dnsconf.yaml
Replacing find is not all Get-ChildItem
can do, a great ressource to dive in even deeper is the official documentation from Microsoft.
If you want to further level up your file interactions with PowerShell I would recommend two posts:
Feel free to ask me about your PowerShell problem over on Twitter!
This is a multi part series, read more.