Extract line and byte ranges from a file — a Python-slice cheatsheet
TL;DR: slice selects line or byte ranges from a file or
stdin using Python's start:end:step notation. The line and byte ranges you would reach for
head, tail, sed, awk, or dd to produce
all collapse into one syntax. Indices are 0-based and end-exclusive, exactly like Python.
Install with cargo install slice-command, brew install chantsune/tap/slice,
or grab a prebuilt binary. Source and docs:
github.com/ChanTsune/slice ·
crates.io/crates/slice-command.
Not sure what a range selects? Run slice --explain <range> to print the 0-based
indices, 1-based positions, and element count without reading any input.
Print a range of lines (head, tail, sed, awk)
| Task | head · tail / sed · awk / dd | slice |
|---|---|---|
| First 5 lines | head -n 5 | slice :5 |
| Last 5 lines | tail -n 5 | slice -5: |
| All but the last 5 lines | head -n -5 | slice :-5 |
| All but the last line | sed '$d' / head -n -1 | slice :-1 |
| POSIX spelling is `sed '$d'`; `head -n -1` is GNU-only. | ||
| All but the first line | sed '1d' / tail -n +2 | slice 1: |
| From line N to the end | tail -n +3 | slice 2: |
| `tail -n +3` is 1-based, so line 3 onward maps to `2:`. | ||
| Lines 2 through 5 | sed -n '2,5p' / awk 'NR>=2&&NR<=5' | slice 1:5 |
| Line 7 only | sed -n '7p' / awk 'NR==7' | slice 6:7 |
| From line 10 to the end | sed -n '10,$p' | slice 9: |
Byte ranges from a file (head -c, tail -c, dd without dd)
| Task | head · tail / sed · awk / dd | slice |
|---|---|---|
| First 5 bytes | head -c 5 | slice -b :5 |
| Last 5 bytes | tail -c 5 | slice -b -5: |
| All but the last 5 bytes | head -c -5 | slice -b :-5 |
| From byte 6 to the end | tail -c +6 | slice -b 5: |
| `tail -c +6` is 1-based, so byte 6 onward maps to `5:`. | ||
| Bytes 5 through 14 | dd bs=1 skip=5 count=10 | slice -b 5:15 |
| First 4 bytes | dd bs=1 count=4 | slice -b 0:4 |
| From byte 10 to the end | dd bs=1 skip=10 | slice -b 10: |
| A block range (bs=4 skip=1 count=2) | dd bs=4 skip=1 count=2 | slice -b 4:12 |
Every Nth line (sed/awk only — slice does it too)
| Task | head · tail / sed · awk / dd | slice |
|---|---|---|
| Odd lines (1, 3, 5, ...) | sed -n '1~2p' / awk 'NR%2==1' | slice ::2 |
| `sed -n '1~2p'` is GNU-only; the awk form is portable, which is what `check` runs. | ||
| Even lines (2, 4, 6, ...) | sed -n '2~2p' / awk 'NR%2==0' | slice 1::2 |
| `sed -n '2~2p'` is GNU-only; the awk form is portable, which is what `check` runs. | ||
NUL-delimited records and other special cases
| Task | head · tail / sed · awk / dd | slice |
|---|---|---|
| Last NUL-delimited record (find -print0 style) | — | slice -z -1: |
| No single coreutils command extracts the last NUL record; slice does it with `-z`. | ||
When NOT to use slice
slice selects positional ranges and copies them through unchanged. These jobs need a different tool:
| Job | Use this instead | Why not slice |
|---|---|---|
| Columns within a line | cut -c 1-3 / cut -b 1-3 | `cut` works per line; `slice -b` indexes the whole stream, not each line. |
| Field extraction | cut -f 2,4 -d, | slice has no notion of fields or delimited columns. |
| Substitution | sed 's/foo/bar/g' | slice only selects ranges; it never transforms content. |
| Aggregation | awk '{sum+=$1} END {print sum}' | slice does not compute; it copies the selected bytes through. |
| Several disjoint ranges | sed -n '1,3p;8,10p' | one slice invocation selects one range. |
| Reverse or sort | tac / sort / tail -r | slice preserves input order; it cannot reorder. |
| Pattern addresses | sed -n '/START/,/END/p' | slice addresses by numeric position only, never by regex. |
Mapping sed/awk line numbers to slice
sed and awk count from line 1 and include both endpoints; slice counts from 0 and excludes the
end. So sed -n 'a,bp' becomes slice (a-1):b. The tail -n +N and
tail -c +N forms are also 1-based, so "from line N onward" is slice (N-1):.
Caveats
- Indices are 0-based and end-exclusive (Python), while sed/awk are 1-based and inclusive: `sed -n 'a,bp'` becomes `slice (a-1):b`.
- `tail -n +N` / `tail -c +N` are 1-based: line/byte N onward is `slice (N-1):`.
- Bounds follow Python, not coreutils, where they disagree: `slice -0:` selects the whole input (`tail -n 0` selects nothing) and `slice :-0` selects nothing (GNU `head -n -0` selects everything).
- `head -n -N` / `head -c -N` are GNU-only (BSD rejects negative counts); `slice :-N` behaves identically on every platform.
- Out-of-range bounds clamp to the input instead of erroring. slice is byte-oriented and binary-safe (no UTF-8 assumption), and records keep their trailing delimiter so output round-trips byte-for-byte.