cheat sheet
findstr
Search for string patterns inside files or piped input on Windows — the built-in grep equivalent for cmd.exe, with support for regular expressions, case-insensitive matching, and recursive directory searches.
findstr — Search Text in Files
What it is
findstr is a built-in Windows command that searches one or more files — or piped input — for lines containing a given string or regular expression pattern. It is the native cmd.exe equivalent of Unix grep: use it to scan log files, filter command output, check configuration files, and locate text across directory trees. For more powerful regex, PowerShell's Select-String is the modern alternative.
Availability
findstr ships as C:\Windows\System32\findstr.exe on Windows XP and later.
findstr /?
Output:
Searches for strings in files.
FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/F:file]
[/C:string] [/G:file] [/D:dir list] [/A:color attributes] [/OFF[LINE]]
strings [[drive:][path]filename[ ...]]
Syntax
findstr [options] "pattern" [file ...]
Output: (matching lines, one per line)
Essential options
| Switch | Meaning |
|---|---|
/I | Case-insensitive match |
/R | Treat pattern as a regular expression |
/S | Search subdirectories recursively |
/N | Prefix matching lines with line number |
/M | Print only filenames that contain a match |
/V | Invert match — print lines that do NOT match |
/L | Literal string match (default when not /R) |
/B | Match at the beginning of a line |
/E | Match at the end of a line |
/X | Match the entire line exactly |
/C:string | Explicit search string (allows spaces without quotes) |
/G:file | Read search strings from a file, one per line |
/F:file | Read file list from a file |
/O | Print character offset before matching line |
Basic string search
findstr without flags searches for the literal string and prints each matching line. The search is case-sensitive by default.
findstr "error" app.log
Output:
2026-04-28 09:12:33 ERROR Connection refused on port 5432
2026-04-28 09:15:00 ERROR Retry limit exceeded
rem Case-insensitive search
findstr /I "error" app.log
Output:
2026-04-28 09:12:33 ERROR Connection refused on port 5432
2026-04-28 09:15:00 error: null pointer dereference
Filtering piped output
findstr commonly appears at the end of a pipeline to filter another command's output — the cmd.exe equivalent of piping to grep.
netstat -an | findstr ":8080"
Output:
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING
tasklist | findstr /I "chrome"
Output:
chrome.exe 6124 Console 1 512,340 K
chrome.exe 6130 Console 1 98,456 K
Recursive directory search (/S)
/S walks the directory tree starting from the current directory (or a specified path) and searches every matching file.
findstr /S /I "TODO" *.txt
Output:
notes\project.txt:4:TODO: fix the auth bug
docs\readme.txt:22:TODO: add installation section
rem Show only filenames, not matching lines
findstr /S /M /I "password" *.config
Output:
config\app.config
config\database.config
Line numbers (/N) and offsets (/O)
/N prefixes each matching line with its 1-based line number. /O prefixes with the character offset from the beginning of the file — useful for pinpointing a match inside a large binary-adjacent text file.
findstr /N "FAILED" build.log
Output:
47:FAILED: compile step returned exit code 1
103:FAILED: link step — missing symbol
Regular expressions (/R)
/R enables a limited regex dialect. Supported metacharacters: . (any char), * (zero or more of preceding), ^ (start of line), $ (end of line), [abc] (character class), [^abc] (negated class), \< (word start), \> (word end).
rem Lines starting with a 4-digit year
findstr /R "^[0-9][0-9][0-9][0-9]-" app.log
Output:
2026-04-28 09:12:33 ERROR Connection refused
2026-04-28 09:15:00 INFO Service started
rem Match whole word "error" (not "errors")
findstr /R "\<error\>" app.log
Output:
2026-04-28 09:15:00 error: null pointer dereference
Multiple patterns
Search for multiple literal strings in a single pass by separating patterns with a space (when the pattern list is the non-/C: form) or by using /G:file.
rem Two patterns — lines matching either "error" OR "warn"
findstr /I "error warn" app.log
Output:
2026-04-28 09:12:33 ERROR Connection refused
2026-04-28 09:14:00 WARN Retry 1 of 3
2026-04-28 09:15:00 ERROR Retry limit exceeded
rem Patterns from a file (one per line)
findstr /G:patterns.txt app.log
Output:
(lines matching any pattern in patterns.txt)
Invert match (/V)
/V prints lines that do not contain the pattern — useful for filtering out noise lines such as debug output or comment lines.
rem Skip blank lines and REM comment lines
findstr /V /R "^$\|^rem" script.bat
Output:
@echo off
set PATH=%PATH%;C:\Tools
call build.bat
Common pitfalls
- Space-separated patterns are OR, not AND —
findstr "foo bar"matches lines containingfooORbar, not both; pipe twofindstrcalls to AND:findstr "foo" file | findstr "bar". - Regex support is very limited — no
+,?,{n,m}, or lookaheads; use PowerShellSelect-Stringfor full PCRE-like patterns. - Case-sensitive by default — always add
/Iunless you need exact case, or you'll miss mixed-case occurrences. /Ssearches from the current directory — if no path is specified, the working directory is the root of the recursive search;cdto the right folder first or specify a path.- Wildcard
*.logrequires at least one file match —findstr /S "text" *.logfails silently if no.logfiles exist in the tree; pair withif errorlevel 1to detect. - Quotes around pattern are required when pattern contains spaces —
findstr connection refused file.logis parsed as two separate patterns; use/C:"connection refused"for a literal phrase.
Real-world recipes
Find which config file sets a specific key
findstr /S /M /I "ConnectionString" C:\App\*.config
Output:
C:\App\config\database.config
C:\App\config\legacy.config
Search log files for errors in the last hour (combined with more)
findstr /N /I "error\|exception\|fatal" C:\Logs\app.log | more
Output:
47:ERROR Connection refused
102:EXCEPTION NullPointerException in handler
(-- More --)
Extract lines containing an IP address pattern
findstr /R "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" access.log
Output:
192.168.1.100 GET /index.html 200
10.0.0.5 POST /api/login 401
Confirm a process is running before killing it
@echo off
tasklist | findstr /I "myapp.exe" >NUL
if errorlevel 1 (
echo myapp.exe is not running.
) else (
taskkill /F /IM myapp.exe
echo myapp.exe terminated.
)
Output:
myapp.exe terminated.
Complete switch reference
findstr exposes more switches than the essentials table covers. The full list:
| Switch | Meaning |
|---|---|
/B | Anchor pattern at the beginning of a line |
/E | Anchor pattern at the end of a line |
/L | Literal string search (default for non-/R patterns) |
/R | Treat each pattern as a regular expression |
/S | Search all matching files in subdirectories |
/I | Case-insensitive match |
/X | Match entire lines exactly |
/V | Invert — print lines that do NOT match |
/N | Prefix each line with its line number |
/M | Print only filenames containing a match |
/O | Prefix each line with its character offset from start of file |
/P | Skip files containing non-printable characters |
/F:file | Read list of files to search from file |
/C:string | Use string as a single literal search term (allows spaces, treats as one) |
/G:file | Read search patterns from file, one per line |
/D:dir1;dir2 | Search this semicolon-delimited list of directories |
/A:attr | Colour attribute for matched output (two hex digits — FG and BG) |
/OFF[LINE] | Do not skip files marked with the Offline attribute |
/? | Help |
The /A colour attribute is two hex digits: background nibble then foreground nibble. /A:4F means bright white text on red background.
rem Coloured output — red background, white text on matches
findstr /N /A:4F "ERROR" app.log
Output: (matches displayed with red highlight)
Regex dialect — what findstr supports
findstr /R supports a strict subset of POSIX-BRE-like regex. Compared to grep, several quantifiers and shorthand classes are missing.
| Metacharacter | Meaning | Notes |
|---|---|---|
. | Any single character | |
* | Zero or more of previous (greedy) | |
^ | Start of line | |
$ | End of line | |
[abc] | Any of a, b, c | |
[^abc] | None of a, b, c | |
[a-z] | Range | |
\< | Word start | GNU-style, not POSIX |
\> | Word end | |
\xHH | Hex character literal | undocumented; works in modern Windows |
Notable absences from findstr regex:
+(one or more) — usexx*instead (one literal then*).?(zero or one) — no replacement; rewrite as alternation, or use literal mode.{n,m}(counted repetition) — no replacement.|(alternation) — handled separately by space-separated patterns at the command-line level.()(grouping) — not supported; effectively no captures.\d,\w,\sshorthand classes — use[0-9],[A-Za-z0-9_],[ \t].- Look-ahead / look-behind — not supported.
rem One-or-more digits in findstr regex (no + — use xx*)
findstr /R "[0-9][0-9]*" data.txt
Output: (lines containing one or more digits)
rem Match an IPv4 address (no shorthand; long but works)
findstr /R "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" access.log
Output: (lines containing IP-like patterns)
rem Whole word boundaries with \< and \>
findstr /R "\<TODO\>" *.py
Output:
auth.py:42: # TODO: rate limit
api.py:118: # TODO: validate input
Pattern precedence and the /C: trap
When a pattern argument contains spaces, findstr treats it as multiple space-separated patterns by default — each is matched as a literal substring with OR semantics. This is the source of nearly every "why doesn't this match?" complaint about findstr.
rem WRONG — searches for either "connection" OR "refused"
findstr "connection refused" app.log
Output (matches both words separately):
2026-05-22 connection established
2026-05-22 client refused service
2026-05-22 connection refused on port 5432
rem RIGHT — /C: tells findstr to treat the argument as one literal string
findstr /C:"connection refused" app.log
Output:
2026-05-22 connection refused on port 5432
rem Multiple /C: phrases — OR of complete phrases
findstr /C:"connection refused" /C:"timeout exceeded" /C:"out of memory" app.log
Output: (lines matching any of the three phrases)
For complex pattern sets, /G:file reads patterns from a file — one pattern per line. This avoids quoting issues entirely:
rem patterns.txt
connection refused
timeout exceeded
out of memory
Output: (none — this is the contents of patterns.txt)
findstr /G:patterns.txt app.log
Output: (matching lines)
Anchors and exact-match flags
The line-position anchors /B, /E, and /X are simpler and more reliable than building anchors into regex patterns.
| Switch | Equivalent regex | Use case |
|---|---|---|
/B "INFO" | ^INFO | Lines starting with INFO |
/E "OK" | OK$ | Lines ending with OK |
/X "DONE" | ^DONE$ | Lines that are exactly "DONE" |
rem Lines starting with a 4-digit year
findstr /B /R "[0-9][0-9][0-9][0-9]-" app.log
Output:
2026-05-22 09:12:33 ERROR Connection refused
2026-05-22 09:15:00 INFO Service started
rem Find unfinished tasks — line ending in "PENDING"
findstr /E "PENDING" tasks.txt
Output:
Task 5: Implement OAuth callback PENDING
Task 9: Migrate database PENDING
rem Find exact "OK" markers (not "OKAY" or "FAILED OK")
findstr /X "OK" status.log
Output:
OK
OK
OK
Reading file lists (/F)
/F:filelist reads filenames from a text file (one per line) and searches each of them. This is the right way to constrain a recursive search to specific files generated elsewhere — typically by dir /B /S or where.
rem Build a file list then search those files
dir /B /S C:\Projects\myapp\*.py > pylist.txt
findstr /N /F:pylist.txt /C:"def main"
Output:
C:\Projects\myapp\main.py:12:def main():
C:\Projects\myapp\tools\runner.py:5:def main_loop():
Use /F in conjunction with where for cross-drive searches:
where /R C:\Tools *.bat > batfiles.txt
findstr /F:batfiles.txt /I /N "@echo off"
Output: (matches across discovered files)
Directory list (/D)
/D:dir1;dir2;dir3 searches in a semicolon-delimited list of directories without recursion into their subfolders. Useful when you want to scan several specific folders without the full overhead of /S.
findstr /D:"C:\Logs;C:\App\Logs;C:\Service\Logs" /I "fatal" *.log
Output: (matches across the three folders)
Character offset (/O)
/O reports the byte position of each matching line from the start of the file. This is useful for tools that consume positional data — for instance, to seek into a file at the exact match position.
findstr /O /N "ERROR" app.log
Output:
1234:47:2026-05-22 09:12:33 ERROR Connection refused
4567:103:2026-05-22 09:15:00 ERROR Timeout
The number before the line number is the byte offset.
findstr vs find
Windows also ships find.exe (not the same as Unix find). The two overlap but findstr is strictly more capable.
| Feature | find | findstr |
|---|---|---|
| Literal string search | yes (default) | yes (/L or default) |
| Regex | no | yes (/R) |
| Recursive directory walk | no | /S |
| Multiple patterns | no | space-separated or /G: |
| Case-insensitive | /I | /I |
| Invert match | /V | /V |
| Show line numbers | /N | /N |
| Count matches | /C | no (use find /C /V "" after findstr) |
| File list input | no | /F: |
| Anchors | no | /B /E /X |
| Word boundary | no | \< \> regex |
rem find — only counts matches
find /C /V "" file.txt
Output:
---------- FILE.TXT: 42
rem Common idiom: pipe findstr through find /C to count matches
findstr /I "error" app.log | find /C /V ""
Output:
17
The only place to prefer find over findstr is for simple counts via find /C /V "". Everything else, findstr does better.
findstr vs grep
findstr is the cmd.exe analogue of grep, but the two diverge significantly. The biggest practical differences are regex dialect, multi-pattern semantics, and recursive ignore handling.
| Feature | findstr | grep (GNU) | ripgrep (rg) |
|---|---|---|---|
| Default mode | literal | regex (BRE) | regex (Rust regex / PCRE2) |
| Regex flavours | limited BRE-like | BRE, ERE (-E), PCRE (-P) | Rust regex, PCRE2 (-P) |
| Quantifiers | * only | *, +, ?, {n,m} (ERE/PCRE) | full |
| Alternation | space-separated patterns | ` | ` in ERE/PCRE |
Grouping () | no | yes | yes |
| Word boundary | \< \> | \b, \< \>, -w | \b, -w |
Shorthand \d \w \s | no | PCRE only | yes |
| Case-insensitive | /I | -i | -i, smart-case by default |
| Recursive | /S | -r | default |
| File globs | per file argument | --include, --exclude | -g (gitignore-style) |
.gitignore respect | no | no | yes |
| Multi-line patterns | no | -z (NUL separator) | --multiline |
| Context lines | no | -A -B -C | -A -B -C |
| Show match only | no | -o | -o |
| Filenames only | /M | -l | -l |
| Invert | /V | -v | -v |
| Line numbers | /N | -n | default in recursive mode |
| Color output | /A:hex | --color | smart default |
| Multi-threaded | no | no | yes |
| Speed (large tree) | slow | fast | very fast |
Recipe parity table
The same job in each tool:
rem Recursive search for "TODO" in .py files
findstr /S /R "TODO" *.py
grep -rn --include='*.py' 'TODO' .
rg -t py TODO
Output:
src\app.py: # TODO: handle empty input
src\utils.py: # TODO: refactor to async
tests\test_app.py: # TODO: cover error path
rem Whole-word match, case-insensitive
findstr /I /R "\<error\>" app.log
grep -wi 'error' app.log
rg -wi error app.log
Output:
[2026-05-20 09:14:22] ERROR connection refused
[2026-05-20 09:14:55] Error: timeout exceeded
rem Lines NOT containing "debug" or "trace"
findstr /V /R "debug trace" app.log
grep -vE 'debug|trace' app.log
rg -v -e debug -e trace app.log
Output:
[2026-05-20 09:14:22] INFO server started on :8080
[2026-05-20 09:14:55] ERROR connection refused
[2026-05-20 09:15:01] WARN slow query (842ms)
rem Find files containing both "foo" AND "bar"
findstr /M "foo" *.log > foo.txt && for /f %f in (foo.txt) do findstr /M "bar" %f
grep -l 'foo' *.log | xargs grep -l 'bar'
rg -l foo | xargs rg -l bar
Output:
app.log
worker.log
rem Extract IPv4 addresses
findstr /R "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" access.log
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log
rg -o '(\d{1,3}\.){3}\d{1,3}' access.log
Output:
10.0.0.5
10.0.0.12
192.168.1.42
When to use which
findstr— when you are already incmd.exeand don't want to install anything. Sufficient for simple log scans and config greps.grep(in WSL or Git Bash) — when you need real regex, context lines, or you are scripting cross-platform.ripgrep— when speed matters, when you want gitignore-aware recursion, or when you need PCRE2 with multi-line matching. The modern default for codebase search on any platform.
See sections/linux/grep.md and sections/linux/ripgrep.md for deep dives.
PowerShell equivalent: Select-String
Select-String is the modern PowerShell cmdlet for pattern search and the right replacement for findstr in any new script written in PowerShell. It supports full .NET regex, pipeline composition, structured output (with Filename, LineNumber, Matches), and context lines.
# Basic literal search
Select-String -Path app.log -SimpleMatch "ERROR"
Output:
app.log:3:2026-05-22 09:12:33 ERROR Connection refused
app.log:7:2026-05-22 09:15:00 ERROR Retry limit exceeded
# Regex search (default), case-insensitive (default)
Select-String -Path *.log -Pattern '\bERROR\b'
Output: (matches across all .log files)
# Recursive — combine with Get-ChildItem
Get-ChildItem -Path C:\Projects\myapp -Recurse -Include *.py |
Select-String -Pattern 'def\s+main'
Output: (matches across the tree)
# Context lines (before and after)
Select-String -Path app.log -Pattern 'ERROR' -Context 2,3
Output:
> 2026-05-22 09:12:33 ERROR Connection refused
2026-05-22 09:12:34 INFO Retrying
2026-05-22 09:12:35 INFO Backoff 1s
2026-05-22 09:12:36 INFO Backoff 2s
# Invert match — non-matching lines
Select-String -Path app.log -Pattern 'DEBUG' -NotMatch
Output: (lines NOT matching DEBUG)
# Multiple patterns (OR)
Select-String -Path app.log -Pattern 'ERROR', 'FATAL', 'PANIC'
Output: (any matching line)
# Capture groups
Select-String -Path *.log -Pattern 'user=(\w+)' |
ForEach-Object { $_.Matches.Groups[1].Value } |
Sort-Object -Unique
Output:
alicedev
bobops
charlie
# Filenames only (like /M)
Select-String -Path C:\Configs\*.ini -Pattern 'ConnectionString' -List |
Select-Object -ExpandProperty Path
Output:
C:\Configs\app.ini
C:\Configs\db.ini
# Count matches per file
Get-ChildItem C:\Logs\*.log |
ForEach-Object {
[PSCustomObject]@{
File = $_.Name
Count = (Select-String -Path $_.FullName -Pattern 'ERROR').Count
}
} |
Sort-Object Count -Descending
Output:
File Count
---- -----
app.log 47
worker.log 12
auth.log 3
Select-String advantages over findstr
- Real regex — full .NET regex including
\d,\w, lookarounds, capture groups, alternation, counted repetition. - Pipeline output — emits
MatchInfoobjects withPath,LineNumber,Line,Matchesproperties. - Context lines —
-Context Before,After. - Encoding awareness — pass
-Encoding utf8for non-default text encodings. - Capture group access —
.Matches.Groups[n].Valuegives you parsed values, not just lines.
Select-String drawbacks
- Slower startup than
findstr(PowerShell init overhead). - No multi-threading — sequential file processing.
- No
.gitignoreawareness. For codebase search,ripgrepis still faster.
findstr in pipelines and cmd quoting
findstr is the workhorse filter at the end of cmd.exe pipelines, used much like grep at the end of bash pipelines. Quoting differs between cmd and bash: cmd uses double quotes ("..."), single quotes are literal characters, and escape with ^ outside quotes or doubled-up "" inside.
rem Filter tasklist
tasklist /V | findstr /I "chrome"
rem Filter netstat
netstat -ano | findstr ":443.*LISTENING"
rem Filter ipconfig
ipconfig /all | findstr "IPv4"
rem Pipe service list
sc query | findstr /I "running"
rem Echo a literal pipe character inside a findstr argument
echo a^|b | findstr "^|"
Output (ipconfig /all | findstr "IPv4"):
IPv4 Address. . . . . . . . . . . : 192.168.1.50(Preferred)
Quoting traps in cmd
rem WRONG — apostrophes are literal in cmd
findstr 'pattern' file.txt
rem RIGHT — use double quotes
findstr "pattern" file.txt
rem To include a literal double quote in pattern, double it up
findstr "say \"hi\"" file.txt rem some Windows versions
findstr "say ""hi""" file.txt rem most reliable
rem Pipes inside the pattern require escaping (or just use /C:)
findstr /C:"foo|bar" file.txt
Output: (per-file matches)
Performance considerations
findstr is single-threaded and operates on one file at a time even with /S. On large trees it is significantly slower than ripgrep or even grep under WSL. Specific performance traps:
- Regex mode (
/R) is slower than literal mode. If you don't need regex, omit/R. - Wildcards with
/Srescan the directory tree per pattern argument when multiple patterns are passed without/G:. - Searching with
/Ion multibyte text is locale-dependent and slower than case-sensitive search.
For ad-hoc scripts that scan more than ~100 MB of text, prefer Select-String (which is comparable to findstr in speed but pipelines results) or install ripgrep for 10× speedups.
Common scenarios
List ports being listened on
netstat -ano | findstr "LISTENING" | findstr /R ":[0-9][0-9]*"
Output:
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 1432
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 6124
Find which executable is using a port
netstat -ano | findstr ":3000" | findstr "LISTENING"
Output:
TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 9876
Then look up the PID:
tasklist | findstr "9876"
Output:
node.exe 9876 Console 1 85,432 K
Grep registry export for a key
reg export HKLM\SOFTWARE\Microsoft\Windows tmp.reg
findstr /I /C:"DisableTaskMgr" tmp.reg
Output:
"DisableTaskMgr"=dword:00000000
Find scripts containing a specific command
findstr /S /M /I /C:"taskkill" C:\Scripts\*.bat
Output:
C:\Scripts\cleanup.bat
C:\Scripts\nightly.bat
Count error severities in today's log
findstr /I "ERROR" C:\Logs\today.log | find /C /V ""
findstr /I "WARN" C:\Logs\today.log | find /C /V ""
findstr /I "INFO" C:\Logs\today.log | find /C /V ""
Output:
17
142
1893
Filter PowerShell transcript for cmdlet invocations
findstr /R "^PS C:" transcript.txt
Output:
PS C:\Users\alicedev> Get-Service
PS C:\Users\alicedev> Restart-Service Spooler
PS C:\Users\alicedev> Stop-Process -Name notepad
Common pitfalls (extended)
findstrreturns code 0 for "no match" — actually it returns 1. Useif errorlevel 1to detect no-match; the conventionif not errorlevel 1means "match found".- CR-LF vs LF line endings —
findstrhandles both, but the trailing\ris included in the matched line. Anchors like/Emay behave unexpectedly if the line ends with\r\nand you anchored to a non-CR character. - UTF-8 BOM at start of file breaks
/B(start-of-line) anchor for the first line. Strip BOM with PowerShellGet-Contentthen re-save. /Sfollows reparse points and junctions — can loop on circular junctions. There is no--no-followswitch; usewhereordir /S /Bwith explicit filtering./G:patterns.txtfile must have CRLF line endings — patterns saved with LF-only endings (e.g. bysedin Git Bash) get concatenated into one massive pattern./A:colour codes don't work in PowerShell pipelines — colour escape sequences leak through to consumers. Use only in interactive cmd sessions.
Sources
References consulted while writing this article. Links open in a new tab.
- Microsoft Learn — findstr command reference — Authoritative flag list and parameter semantics used to build the Essential options table.
- SS64 — findstr — Cross-version comparison and historical syntax notes.
Tips
Default
findstris case-sensitive. Always add/Ifor log scans unless you specifically need exact case — Windows logs use mixed-case ("Error", "ERROR", "error") interchangeably.
Use
/C:"phrase with spaces"for any pattern containing spaces. Without/C:, the words become OR-separated patterns and you'll get unexpected matches.
Pipe through
find /C /V ""to count matching lines:findstr "error" file | find /C /V "". This is the cmd.exe equivalent ofgrep -c.
For any non-trivial pattern search, jump to
Select-Stringin PowerShell. The full .NET regex engine and structured output are worth the slightly longer command line.
[!WARN]
findstr's regex dialect is missing+,?,{n,m},|,(),\d,\w,\s, and lookarounds. If you're hitting those limits, switch toSelect-Stringorripgrep— don't try to work aroundfindstr's limitations.
[!WARN] Multiple space-separated patterns are OR, not AND. To AND, pipe
findstrinto itself:findstr "foo" file | findstr "bar".