Skip to content
shellmap

nlNumber the lines of a file or stdin, with formatting controls across all 5 shells

Equivalents in every shell

Bashunix
nl file.txt

Prints 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`.

Zshunix
nl file.txt

Same external binary. Zsh has no built-in equivalent; for simple line counting `cat -n file.txt` is the cross-shell, no-config alternative.

Fishunix
nl file.txt

Same external binary. `cat -n` is the universal alternative — works on every Unix without `-ba`-style flag-divergence headaches.

PowerShellwindows
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" }`.

cmd.exewindows
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

Bash
nl -ba file.txt
Fish
nl -ba file.txt
PowerShell
Get-Content file.txt | ForEach-Object { $i = 0 } { $i++; "$i`t$_" }
cmd.exe
findstr /N "^" file.txt

Start numbering from 100, increment by 10

Bash
nl -v 100 -i 10 file.txt
PowerShell
Get-Content file.txt | ForEach-Object { $i = 90 } { $i += 10; "$i`t$_" }

Number stdin from a piped command

Bash
grep "TODO" *.md | nl
Fish
grep "TODO" *.md | nl
PowerShell
Select-String "TODO" *.md | ForEach-Object { $i = 0 } { $i++; "$i`t$($_.Line)" }
cmd.exe
findstr /N "TODO" *.md

Gotchas

  • `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).

WSL & PowerShell Core notes

pwshNo native pwsh equivalent — the stateful `ForEach-Object -Begin -Process` pattern is the idiomatic cross-OS form. Works identically on Windows / Linux / macOS pwsh. For sub-millisecond performance on huge files, switch to `.NET`: `[System.IO.File]::ReadLines("file.txt") | ForEach-Object -Begin {$i=0} -Process {$i++; "$i`t$_"}` avoids materialising the whole file.
WSLWSL `nl` is GNU coreutils. Useful when calling from a Windows script that needs proper line-numbering without the pwsh boilerplate: `wsl nl -ba /mnt/c/Users/.../file.txt`. Note the `/mnt/c` path indirection — convert via `wslpath` for clarity.

Related commands