nl — Number the lines of a file or stdin, with formatting controls across all 5 shells
Equivalents in every shell
nl file.txtPrints lines numbered 1..N — BUT blank lines are NOT numbered by default (`-b a` to include them: `nl -ba file.txt`). Width / separator / starting number all tunable via `-w`, `-s`, `-v`.
nl file.txtSame external binary. Zsh has no built-in equivalent; for simple line counting `cat -n file.txt` is the cross-shell, no-config alternative.
nl file.txtSame external binary. `cat -n` is the universal alternative — works on every Unix without `-ba`-style flag-divergence headaches.
Get-Content file.txt | ForEach-Object { $i = 0 } { $i++; "{0,6}`t{1}" -f $i, $_ }No native `nl`. The idiom uses `ForEach-Object` with a `-Begin` initialiser (the `{$i=0}` block). Simpler: `(Get-Content file.txt) | Select-Object @{Name="Line";Expression={$_}}, @{Name="Number";Expression={$i++; $i}}`. For pwsh 7+: `Get-Content file.txt | ForEach-Object -PipelineVariable line { $i++; "$i`t$line" }`.
findstr /N "^" file.txt`findstr /N "^"` matches every line (regex `^` = line start) and prepends the line number with a colon: `1:first line`. No padding control. For tab-delimited output, shell out to pwsh.
Worked examples
Number every line including blank lines
nl -ba file.txtnl -ba file.txtGet-Content file.txt | ForEach-Object { $i = 0 } { $i++; "$i`t$_" }findstr /N "^" file.txtStart numbering from 100, increment by 10
nl -v 100 -i 10 file.txtGet-Content file.txt | ForEach-Object { $i = 90 } { $i += 10; "$i`t$_" }Number stdin from a piped command
grep "TODO" *.md | nlgrep "TODO" *.md | nlSelect-String "TODO" *.md | ForEach-Object { $i = 0 } { $i++; "$i`t$($_.Line)" }findstr /N "TODO" *.mdGotchas
- `nl` does NOT number blank lines by default — a 100-line file with 20 blank lines produces a numbering sequence that skips to 80, not 100. Add `-b a` (include all) to count every line. The default behaviour `-b t` (text only) trips up everyone the first time.
- `cat -n` is the cross-shell-portable alternative and numbers EVERY line including blanks — semantics differ from `nl` (which is closer to `grep -v "^$" | nl`). If you want blank-line-skipping, `cat -n` won't do it; if you want every line, `nl` needs `-ba`.
- BSD `nl` (older macOS) has the same flag surface but `-w` width defaults to 6 (GNU) vs 6 (BSD) — usually compatible. Edge case: `-d` line-delimiter regex syntax differs. For scripts that need stable output across macOS + Linux, prefer `cat -n` or pipe to `awk` for full control.
- pwsh stateful counter idioms (`{ $i = 0 } { $i++; ... }`) are TWO separate scriptblocks — the first is `-Begin`, the second `-Process`. Confused-form `ForEach-Object { $i = 0; $i++; "$i`t$_" }` resets `$i` to 0 on EVERY line (so every line numbers as 1).
- cmd `findstr /N "^"` prepends `<line-number>:` with a literal colon — to get just the number + tab, post-process with `for /F`: `for /F "tokens=1,* delims=:" %A in ('findstr /N "^" file.txt') do @echo %A^I%B` (uses `^I` for embedded tab).