Utility: sort - order the lines

Status: Built + QEMU-verified (osdev test files). A shell built-in FILTER, like match/count: the direct form sort <path> and the pipe form <producer> | sort. Trails CLAUDE.md; does not amend it.


1. What it is

sort orders its input lines and prints them. Unlike grep/wc, the POSIX name sort is already plain English, so it keeps it - naming for what it does is the rule, not renaming for its own sake. Its natural use is a pipe (find *.txt | sort, read /names | sort reverse).

2. Usage

sort 0.4.0 - order the lines (ascending, or reverse)

usage:
  <producer> | sort         sort piped lines
  sort <path>               sort a file's lines
  sort reverse [path]       sort descending
  sort version              print the version
  sort help                 print this message

<path> = [index:]label/path | /abs | rel   (see docs/drives.md §4.1)

3. Behaviour

Lines are ordered lexicographically by bytes (ascending), or descending with reverse. reverse is a keyword wherever it appears after the verb (sort reverse /f and sort /f reverse both work), so it reads the same piped or direct. Blank lines are dropped. Equal lines keep no defined relative order (the sort is unstable - there is no heap for a stable merge), but since they are equal that is invisible.

Bounded (§26.6): sort orders the first SORT_MAX_LINES (1024) lines and says so if there are more - it never silently drops the rest.

sort consumes input; it is never a pipe producer. Being a filter it can sit mid-pipe (find *.txt | sort | count) or end one.

On records - sort by a column

sort spans both pipe worlds (docs/records.md): on a text stream it line-sorts as above; on a record stream (e.g. after status) it orders rows by a column - sort mem, sort reverse name. The column name follows the verb, with reverse leading so the column lands at the end and nothing dangles (sort reverse mem, same shape as match except). The comparison is numeric when the column's values are numeric, else textual. The record verbs proper (where/select/to/from) live in utilities/31_records.md; sort is documented here because its text form predates them.

4. Implementation

A shell built-in FILTER (run_filter_builtin, with match/count): it runs in-process, so it is not subject to the 4 KiB pipe service-boundary cap and can sort a full 16 KiB stage buffer. It records each line as a (start, end) pair into a fixed SORT_MAX_LINES array on the stack and sort_unstable_bys the index array (no heap - sort_unstable is in-place), then emits the lines in order. 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> | sort). With neither a path nor piped input it prints sort: a path is required (or pipe input: <producer> | sort).

5. Later (separate so it can grow)

  • sort unique - drop duplicate lines (needs a stable or post-pass dedupe).
  • sort number - numeric order rather than lexicographic.
  • A larger / streaming sort once true pipe streaming lands (the 1024-line bound is the store-and-forward limit, not a fundamental one).

6. Conformance

Conforms to 0_conventions.md: its own sort help (usage with a real example per row), the sort reverse help subcommand help, and sort 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).