Skip to content
shellmap

foreach-objectRun a script block on each pipeline object — like xargs across all 5 shells

Equivalents in every shell

Bashunix
command | xargs -I {} echo {}

`xargs` with `-I {}` runs the template per input record — closest 1:1 mapping to `ForEach-Object { ... $_ ... }`. For multi-line bodies the more readable form is `command | while read -r line; do ...; done`. `for x in $(command); do ...; done` exists but breaks on whitespace in input — `while read` is the safer default.

Zshunix
command | while read -r line; do echo $line; done

Same `xargs` / `while read` idioms. Zsh adds the `${(f)...}` parameter flag to split a multi-line string into an array (`for f in ${(f)$(command)}; do ...; done`) — handles embedded whitespace better than the bash `for` loop. For pipelines, `while read -r` is still the standard.

Fishunix
command | while read -l line; echo $line; end

Fish `while read -l line ... end` is the native loop-per-line form (no need for `done`). For one-liners, fish's `for x in (command)` works correctly on whitespace because fish's command substitution splits on newlines, not on `$IFS`. xargs is also available.

PowerShellwindows
Get-Process | ForEach-Object { $_.Name }

PowerShell-native cmdlet (aliases `%` and `foreach`). The script block runs once per pipeline object with `$_` as current. `-Begin`, `-Process`, `-End` script blocks let you set up + tear down state. PS 3.0+ adds a member-access shorthand: `Get-Process | ForEach-Object Name` (project a property) and `Get-Process | ForEach-Object Kill` (invoke a method) — no `$_`, no braces.

cmd.exewindows
for /f "delims=" %i in ('command') do @echo %i

`for /f` iterates over command output line-by-line. `delims=` (empty) keeps each line whole (default delimiter is space/tab — bites unquoted paths). In batch files use `%%i` (double-percent). For per-file iteration use `for %i in (*.log) do ...`. cmd's loop syntax is less flexible than PowerShell's but covers the common cases.

Worked examples

Print the name of each running process

Bash
ps -o comm= -e | xargs -I {} echo {}
Fish
ps -o comm= -e | while read -l name; echo $name; end
PowerShell
Get-Process | ForEach-Object Name

Delete every .tmp file under the current tree

Bash
find . -name "*.tmp" -print0 | xargs -0 rm -f
PowerShell
Get-ChildItem -Recurse -Filter *.tmp | ForEach-Object { Remove-Item $_.FullName }
cmd.exe
for /r %i in (*.tmp) do @del "%i"

Rename .txt files to .md

Bash
for f in *.txt; do mv -- "$f" "${f%.txt}.md"; done
Fish
for f in *.txt; mv $f (string replace -r .txt\$ .md $f); end
PowerShell
Get-ChildItem *.txt | ForEach-Object { Rename-Item $_ ($_.BaseName + '.md') }

Gotchas

  • The PS 3.0+ shorthand `... | ForEach-Object Name` looks like a `Select-Object Name` but is NOT the same: `ForEach-Object Name` emits the raw `Name` STRING value; `Select-Object Name` wraps it in a `PSCustomObject` with a single `Name` property. Pipe to `Format-Table` and they look similar; pipe to `Set-Content` and they diverge sharply.
  • `ForEach-Object` (the cmdlet) and the `foreach` statement (`foreach ($x in $coll) { ... }`) are DIFFERENT. The statement materialises the collection first (no streaming) and treats an empty input as zero iterations; the cmdlet streams but treats `$null` as one iteration with `$_ = $null`. Surprising on empty pipelines like `Get-ChildItem nonexistent\* | ForEach-Object { ... }`.
  • `-Begin` and `-End` blocks run ONCE before / after the stream — useful for headers / footers / aggregation. They run even if the pipeline is empty. Common bug: putting initialisation in `-Process` instead of `-Begin` resets state per object.
  • Inside the script block, `return` does NOT exit the loop — it just stops the current iteration (like `continue` in a normal loop). To stop a `ForEach-Object` early, throw or use `Select-Object -First N` upstream.
  • Parallel execution: PS 7+ adds `ForEach-Object -Parallel { ... } -ThrottleLimit N` — but each parallel thread runs in a FRESH runspace. Variables from the caller scope are NOT visible unless you use `$using:varname`. This is a frequent first-time-pwsh-7 stumble.

WSL & PowerShell Core notes

pwsh`ForEach-Object` is identical across pwsh platforms. The `foreach` alias is preserved on every OS so it can shadow the bash/fish `foreach` keyword in scripts run via `#!/usr/bin/env pwsh` — for clarity in cross-language code, prefer the `%` alias or the full cmdlet name.

Related commands