grep1-8 Exercise 3: Three greps in PowerShell ============================================== (Run in the practice repository from the chapter. Make it first with the block at the top of the chapter, then change into the proj folder.) Write-Output "Python files with TODO (case-sensitive):" Get-ChildItem -Recurse -File -Include *.py | Select-String -Pattern TODO -CaseSensitive Write-Output "lines without TODO in src\db.py:" Select-String -Path src\db.py -Pattern TODO -NotMatch Write-Output "files that contain FIXME:" Get-ChildItem -Recurse -File | Select-String -Pattern FIXME -List | ForEach-Object { Resolve-Path -Relative $_.Path } Output: Python files with TODO (case-sensitive): src\db.py:1:# TODO: use a connection pool lines without TODO in src\db.py: src\db.py:2:def connect(): src\db.py:3: # HACK: retry forever src\db.py:4: return None files that contain FIXME: .\src\app.js WHY THIS WORKS AS AN ANSWER --------------------------- Get-ChildItem -Recurse -File -Include *.py finds the files (recursion is its job, not Select-String's) and Select-String reads each. -CaseSensitive restores grep's behaviour, which is case-insensitive in PowerShell by default. -NotMatch is grep -v. -List returns only the first match in each file, the nearest thing to grep -l; Resolve-Path -Relative turns the full path into the short one grep would print. Checking against grep: grep -rn TODO --include='*.py' . finds src/db.py:1, and grep -v TODO src/db.py prints the other three lines, and grep -rl FIXME . prints src/app.js. The answers agree. Remember that Select-String does not read .gitignore and does not skip the build folder, so on a real project add the exclusions yourself (a Where-Object on the path), or use rg or git grep.