tac — Concatenate and print files in reverse line order across all 5 shells
Equivalents in every shell
tac file.txtGNU coreutils — Linux ships it by default, macOS does NOT. The name is `cat` spelled backwards. Reads the whole file into memory by default; use `tac -s SEP` to reverse on a non-newline separator (e.g. `tac -s "---" log.txt`).
tac file.txtSame external GNU tool. On macOS, install with `brew install coreutils` (provides `gtac`) or fall back to BSD `tail -r file.txt` — but `tail -r` reads the WHOLE FILE into memory and is slower on multi-GB inputs.
tac file.txtSame external. Fish has no builtin reverse; pipe through `tac` (Linux) or `tail -r` (macOS / BSD). For in-shell reversal of a list variable, slice with negative step: `for x in $list[-1..1]; echo $x; end`.
$l = Get-Content file.txt; $l[($l.Count-1)..0]No native `tac`. The idiomatic form is index-slice in reverse: `$lines = Get-Content file.txt; $lines[($lines.Count-1)..0]`. Alternative: `[Array]::Reverse($lines); $lines` (in-place mutation). Avoid `Sort-Object` for reversal — it is O(N log N) where reversal is O(N).
powershell -NoProfile -Command "$l=Get-Content file.txt; $l[($l.Count-1)..0]"No native reverse. Shell out to PowerShell (one-liner above) or use a `for /f` loop with a manual stack: push each line to a temp file, then dump in pop order. The PowerShell form is far simpler — cmd has no idiomatic equivalent.
Worked examples
Print a file in reverse line order
tac file.txttac file.txttac file.txt$l = Get-Content file.txt; $l[($l.Count-1)..0]powershell -NoProfile -Command "$l=Get-Content file.txt; $l[($l.Count-1)..0]"Print the last 50 log lines, newest first
tail -n 50 app.log | tactail -n 50 app.log | tactail -n 50 app.log | tac$l = Get-Content app.log -Tail 50; $l[($l.Count-1)..0]Reverse log entries separated by --- markers (not by newline)
tac -s "---" log.txttac -s "---" log.txtGotchas
- macOS does NOT ship `tac` — it is GNU-only. BSD systems use `tail -r file.txt` for the same effect, but `tail -r` reads the entire file into memory and rejects pipes on some BSDs (you must redirect from a real file). For portable scripts, prefer `awk '{a[NR]=$0} END{for(i=NR;i>0;i--) print a[i]}'` — runs on every system.
- `tac -s SEP` reverses on SEP, not newlines — `tac -s ""` is a no-op on most inputs. The separator is treated as a regex (`-r` makes it explicit). To reverse by paragraph (blank-line-separated), use `tac -s $'\n\n'` in bash; the literal escape `"\n\n"` does NOT work — bash needs ANSI-C `$'...'` quoting for the real newline.
- `tac` reads the WHOLE input into memory before printing (it has to know the end). For multi-GB log files this is fine on a workstation but problematic on memory-constrained containers — pipe through `split -l 100000` first, reverse each chunk, then concatenate chunks in reverse order if memory is tight.
- PowerShell `[Array]::Reverse($arr)` MUTATES the array IN PLACE and returns `$null`. The naive `$x = [Array]::Reverse((Get-Content file.txt))` ends up with `$x = $null`. Use `$lines = Get-Content file.txt; [Array]::Reverse($lines); $lines` or the index-slice form `$lines[($lines.Count-1)..0]` for one-line clarity.
- Reversing a STREAM (e.g. `tail -f app.log | tac`) does NOT work as expected — `tac` waits for EOF before printing anything. For real-time newest-first viewing, page the file in reverse periodically: `watch 'tail -n 20 app.log | tac'` is the common pragmatic workaround.