Utilities: first / last - keep the first or last N lines
Status: Built + QEMU-verified (osdev test files). Two shell built-in FILTERS, like
match/count/sort: the direct form first [N] <path> / last [N] <path> and the pipe form
<producer> | first [N] / | last [N]. Trails CLAUDE.md; does not amend it.
1. What they are - and why not "head" / "tail"
first N keeps the first N lines of its input; last N keeps the last N. Other systems call
these head and tail. Those names are not ciphers like grep/wc - they are
guessable metaphors (a list has a head and a tail), so they are defensible. But first/last
clear the project's bar - "would a layperson guess right with no prior knowledge?" - with zero
metaphor to learn: read /log | last 20 is unambiguous to someone who has never touched a
terminal. They pair as cleanly as head/tail, and read naturally with the count.
2. Usage
first 0.4.0 - keep the first N lines (default 10)
last 0.4.0 - keep the last N lines (default 10)
usage:
<producer> | first [N] first N piped lines
<producer> | last [N] last N piped lines
first [N] <path> first N lines of a file
last [N] <path> last N lines of a file
<first|last> version print the version
<first|last> help print this message
N defaults to 10. <path> = [index:]label/path | /abs | rel
3. Behaviour
N is optional and defaults to 10 (read /log | last is "the last few"). A numeric arg is
the count, a non-numeric arg is the path, in either order (first 20 /log = first /log 20).
Blank lines are skipped (consistent with the other line filters).
firststreams the first N lines in one pass - no buffer, any N.lastkeeps the most recentTAKE_MAX(1024) line spans in a ring buffer, so it is correct even for input far larger than the ring; an N beyond 1024 is capped with a loud note (§26.6). The classic "watch a growing log" follow mode (tail -f) belongs to true pipe streaming - a deliberate future, seedocs/pipes.md.
Both consume input; neither is a pipe producer. Being filters they compose:
find *.txt | sort | first 5, read /log | match error | last 20.
4. Implementation
Shell built-in FILTERS (run_filter_builtin, with match/count/sort): they run
in-process, so they are not subject to the 4 KiB pipe service-boundary cap and can take
from a full 16 KiB stage buffer. cmd_take(last: bool) serves both verbs; the pipe path routes
through run_filter_builtin. The direct form reads the file itself (gs::fs::Fs::read_into,
streaming READ_AT) - no new fs surface - into a fixed FILTER_READ_MAX (8192-byte) buffer,
and refuses a larger file loudly (pipe it instead: read <path> | last 20). On a record stream
both refuse with a sentence pointing at where/select/sort <col>.
5. Later (separate so it can grow)
- A
followmode forlast(stream new lines as a file grows), once true pipe streaming lands. - Byte/character ranges, if a need appears.
6. Conformance
Both conform to 0_conventions.md: their own first/last help (usage with a real example
per row) and version (number + creator credit), via the shared help_block helper.