where-object — Filter pipeline objects by property — like awk or grep across all 5 shells
Equivalents in every shell
command | awk '$3 > 100'For text streams, `awk` is the closest equivalent — filter rows by column predicate (`$3 > 100`). For pure line-matching the right tool is `grep "pattern"`. The big semantic gap: bash pipes carry bytes/lines, so the predicate is always re-parsed text. PowerShell passes typed objects, so `Where-Object` reads properties directly without re-parsing.
command | awk '$3 > 100'Same `awk` / `grep` toolchain. Zsh adds `(#...)` glob qualifiers (`*(.L+100)` = regular files larger than 100 bytes) — closest in-shell predicate to PowerShell `Where-Object Length -gt 100` without piping. For arbitrary pipeline filtering, zsh still reaches for awk.
command | awk '$3 > 100'Same external `awk` / `grep`. Fish has no native typed-object pipeline — its `string match` builtin (`string match -r 'pattern'`) is a fast in-shell `grep` for line filtering, but column-predicate filtering still goes through awk.
Get-Process | Where-Object { $_.CPU -gt 100 }PowerShell-native cmdlet (aliases `?` and `where`). Filters pipeline objects by a `ScriptBlock` predicate where `$_` is the current object. PS 3.0+ adds a faster comparison syntax: `Get-Process | Where-Object CPU -gt 100` (no `$_`, no braces) — works for single-condition filters. Output type matches the input type.
command | findstr "pattern"`findstr` filters lines by literal/regex match (`/I` case-insensitive, `/V` invert, `/R` regex). No column-aware predicate exists in cmd.exe — for arithmetic comparisons (`length > 100`) you need PowerShell or WSL. `for /f "tokens=3" %i in ...` parses columns but mixing it with filtering is verbose.
Worked examples
Filter processes using more than 100 MB of memory
ps -eo rss,comm | awk '$1 > 102400'Get-Process | Where-Object { $_.WorkingSet64 -gt 100MB }Filter files larger than 1 MB in the current directory
find . -maxdepth 1 -type f -size +1Mprint -l *(.L+1048576)Get-ChildItem | Where-Object Length -gt 1MBforfiles /m * /c "cmd /c if @fsize gtr 1048576 echo @file"Show only services that are currently running
systemctl list-units --type=service --state=runningGet-Service | Where-Object Status -eq RunningGotchas
- The legacy `Where-Object { $_.Prop -eq "value" }` `ScriptBlock` syntax and the PS 3.0+ `Where-Object Prop -eq "value"` shorthand are NOT identical: the shorthand only supports ONE comparison and the parameter set requires a comparison operator (`-eq`, `-gt`, `-like`, `-match`, etc.). Compound predicates (`$_.A -eq 1 -and $_.B -gt 2`) require the `ScriptBlock` form.
- `Where-Object` evaluates a `ScriptBlock` per object — slow on large streams compared to `.Where()` (the array method): `(Get-Process).Where({$_.CPU -gt 100})` is ~5× faster but loses streaming (the entire input is materialised first). For one-shot pipelines, prefer `Where-Object`; for tight loops on already-materialised collections, use `.Where()`.
- Comparison operators are case-INSENSITIVE by default (`-eq`, `-like`, `-match`). For case-sensitive comparisons prepend `c`: `-ceq`, `-clike`, `-cmatch`. This bites bash users who expect `=` to be byte-exact.
- `-match` and `-notmatch` set the `$matches` automatic variable as a side effect — fine in interactive use, but in scripts that pipe through multiple `Where-Object -match` stages, `$matches` ends up holding the LAST match, which can surprise later code. Use `-like` (wildcard) or `-cmatch` if you don't want this side effect.
- `$null` filtering: `Where-Object { $_ }` drops `$null`, `$false`, `0`, `''` (truthy filter) — useful but trips users expecting `WHERE NOT NULL` semantics. For explicit null filtering, write `Where-Object { $null -ne $_ }` (note the `$null` on the LEFT — PowerShell idiom to dodge a known `-ne` array gotcha).