Utility: count - how many lines, words, and bytes
Status: Built + QEMU-verified (osdev test files). A shell built-in FILTER, like
match: the direct form count <path> and the pipe form <producer> | count. Trails
CLAUDE.md; does not amend it.
1. What it is - and why not "wc"
count reports how many lines, words, and bytes its input has. It is the tool
every other system calls wc ("word count") - a cryptic abbreviation that doesn't even
name its most common use (counting lines). GodspeedOS names utilities for what they do
(write not touch, match not grep), so the counter is count: a plain verb a
layperson reads correctly. Its natural partner is a pipe - "how many?" after a filter:
find *.txt | count, read /log | match error | count.
2. Usage
count 0.4.0 - count lines/words/bytes (byte stream) or ROWS (record stream)
usage:
<producer> | count count piped bytes, or rows of a record stream
count <path> count a file
count version print the version
count help print this message
<path> = [index:]label/path | /abs | rel (see docs/drives.md §4.1)
3. Behaviour
Output is one labelled line: N lines, M words, K bytes (singular when a count is 1, e.g.
1 line, 1 word, 6 bytes). Unlike wc's three bare numbers, each is named - no guessing which
column is which.
- lines - newline count, plus one for a final unterminated line (so a file with no trailing newline still counts its last line).
- words - runs of non-whitespace bytes.
- bytes - the raw size.
On a record stream it counts ROWS and prints the bare number (status | count → 14), since
lines, words and bytes mean nothing for a table.
count consumes input; it is never a pipe producer. In a pipe it is normally the last stage
(it collapses many lines into one summary), but being a filter it can also feed onward
(find *.txt | count | write /n.txt writes the summary to a file).
4. Implementation
A shell built-in FILTER (run_filter_builtin, alongside match): it runs in-process, so it
is not subject to the 4 KiB pipe service-boundary cap and can count a full 16 KiB stage
buffer. The pipe form consumes the previous stage's buffer (write_count in
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 rather than counting part of it (pipe it instead: read <path> | count).
count with neither a path nor piped input prints count: a path is required (or pipe input: <producer> | count).
5. Later (separate so it can grow)
count lines/count words/count bytes- emit just the one bare number, for when the value feeds something else (a future filter that takes a count).- Counting across multiple files, once a multi-file producer exists.
6. Conformance
Conforms to 0_conventions.md: its own count help (usage with a real example per row) and
count version (number + creator credit), via the shared help_block helper.
Conforms to rule 10 (0_conventions.md §1.10) since 2026-10-13 (backlog/fixed/89). It found 2026-10-09
that the fs handle was lent no notice; every fs handle the shell makes is now lent the command's
watch (gs::call::Watch), so the command says [q] quit once it has run 2 s, and q, Q or ESC stops the command
at its next request (not measured on its own by osdev test quit; read, which reads a file the same way, is).