hibr is a shell that finally behaves the way you already expect a shell
to behave. Familiar bash syntax, its sharpest edges resolved instead of
carried forward, in about 17,500 lines of C and a 398 KB binary — under
two-thirds of bash's memory, and about two and a half times its speed on a
tight loop. Built with tcc, depending on nothing but libc and libdl.
The name says what it is. Highly: two-thirds of bash's memory, tight
loops two and a half times faster, in a 398 KB binary — the numbers below. Intuitive:
the things that make bash surprising the first time you hit them —
test's word-splitting traps, $BASH_REMATCH's awkward capture, a dozen more
in the decision records — fixed, so the shell does what
a reader would already guess it does. Bash-like: familiar syntax you
already know, not a rewrite you have to learn, and not a claim to be bash
itself. Runtime: the module ABI lets a module add a protocol, not just
a command — register a scheme and /dev/<name>/… works anywhere a filename
does — and results come back without forking, text and JSON are manipulated
without pipelines, and the prompt reads git's object store with no subprocess
at all.
It is also حِبر — Arabic for ink. Every shell session, every script, every automated thing this runs is still just ink on a page in the oldest sense: it is what you write computing with.
It is not a drop-in replacement for bash. Some behaviour differs on purpose, where bash is error-prone; every one of those divergences is written down in the decision records. What it adds — nested maps, JSON, regex capture, native networking and TLS, typed functions, results without forking, declared command-line arguments, protocol-level modules, and a prompt that reads git's object store without forking anything — is the reason to use it.
hibr script.sh args...
hibr -c 'echo $((6 * 7))'
hibr --explain script.sh # name the mistakes in it, run nothing
hibr --plan script.sh # follow it, change nothing, list what it would do
man hibr # every flag, variable and exit status
Every route installs the same things: hibr, its modules, man hibr, and
the text desktop with a desktop launcher. hibr needs only libc and libdl;
libssl and libpng are used if they are there, for TLS and images, and
nothing fails without them until it is asked for.
macOS (or Linux), with Homebrew:
brew tap osakka/hibr
brew install hibr
Debian 12, Ubuntu 22.04, or newer (amd64), from the signed apt repository at osakka.github.io/hibr-apt:
curl -fsSL https://osakka.github.io/hibr-apt/hibr.gpg \
| sudo tee /usr/share/keyrings/hibr.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/hibr.gpg] https://osakka.github.io/hibr-apt stable main" \
| sudo tee /etc/apt/sources.list.d/hibr.list
sudo apt update && sudo apt install hibr
apt upgrade keeps it current from then on.
Fedora 36, RHEL 10, openSUSE Leap 15.6 or newer (x86_64), from the
signed dnf repository at osakka.github.io/hibr-rpm
(zypper addrepo takes the same file on openSUSE):
sudo curl -fsSL -o /etc/yum.repos.d/hibr.repo https://osakka.github.io/hibr-rpm/hibr.repo
sudo dnf install hibr
Arch Linux (x86_64), from the signed pacman repository at
osakka.github.io/hibr-arch: trust its
key once, add it to /etc/pacman.conf, and install.
curl -fsSL https://osakka.github.io/hibr-arch/hibr.asc | sudo pacman-key --add -
sudo pacman-key --lsign-key A1E3CA788A8FB02052DA22B413035BE1B1674431
printf '[hibr]\nServer = https://osakka.github.io/hibr-arch/$arch\n' | sudo tee -a /etc/pacman.conf
sudo pacman -Syu hibr
One key signs all three repositories, and the three packages hold the same
files, byte for byte; dnf upgrade and pacman -Syu keep them current.
From source, anywhere with a C compiler:
git clone https://github.com/osakka/hibr && cd hibr
make # with tcc, the default
make CC=gcc OPT=-O2 # or with gcc: smaller and faster code
make check # the test suite, if you want it
sudo make install # to /usr/local; PREFIX=$HOME/.local for yours alone
PREFIX is compiled into the binary as the place it looks for modules;
make install rebuilds by itself when it is given a different one from the
build before it. The desktop it installs is the window manager and what
manages it, with Files, Terminal and five accessories, and one theme and
two colour schemes; everything else comes from the catalogue, and
catalogue install apps/ places every application in it (make install SHIP=all places the lot from this tree instead). make TLS=0 leaves TLS out
entirely. deploy.sh does the same install but also
runs every suite first, keeps the previous version to roll back to, and can
update itself from the tree on a timer.
To take it out again: brew uninstall hibr, sudo apt remove hibr, or
sudo make uninstall with the same PREFIX it went in with.
New to it? From bash to hibr, in ten minutes is the place to start.
Measured on 0.68, on one host; the loop is
while [ $i -lt 50000 ]; do i=$((i+1)); done. Times are the best of eleven
runs. Memory is the shell's own VmHWM, read without forking, as the median
of twenty-five runs — a single reading is worthless here, because run-to-run
spread is about 180 kB either way.
| hibr | dash | bash | |
|---|---|---|---|
| binary, stripped | 398 KB | 122 KB | 1235 KB |
| resident memory at startup | 1852 kB | 1688 kB | 2900 kB |
| resident memory after the loop | 1888 kB | 1656 kB | 2944 kB |
| the loop | 87 ms | 79 ms | 211 ms |
5000 function calls, results via := |
19 ms | — | — |
the same through $( ) |
1145 ms | 1018 ms | 1836 ms |
startup, -c true |
1.67 ms | 1.70 ms | 2.30 ms |
Read honestly. Against bash hibr is under two-thirds of the memory and two and a half times the speed on a tight loop. Against dash it is level on startup and within 10% on the loop, and about 160 kB heavier — and dash is a far smaller language, so being close is the claim, not being ahead. The loop gap was 1.7x before a round of profiling.
Where the memory goes is worth knowing: the heap at startup is 36 kB of the 1852, so memory here means the binary, and the binary means how much language there is. There is no allocator trick left that would move it.
The row that is not a near-miss is the fifth. Returning a value through $( )
costs a fork per call in every shell; := costs none, which is where the 60x
comes from. That is the argument for the whole in-process design, in one line.
A full prompt with git branch, working-tree status and upstream distance cost
35 ms in a 2,600-file repository when it was measured — against 32 ms for
git status --porcelain=v2 --branch alone, with no fork on top.
h[users][omar][role]=admin # maps nest, no new syntax
json parse doc "$body"; json get doc .items[0].name
fn add(int a, int b) -> int { ret $((a + b)); }
sum := add 10 32 # a result, without forking
[[ $line =~ ^([a-z]+)=(.*)$ ]] && echo "${M[1]} is ${M[2]}"
exec 3<>/dev/tls/api.example.com/443 # sockets are descriptors
opt -o --output out path! "Where to write" # declared arguments
args "$@"| From bash to hibr, in ten minutes | Start here: install, then each thing hibr adds, one checked example each |
| The language | The whole language, and every place it differs from bash |
| Builtins | All 73, what each takes and what it gives back |
| hibr, for a language model | The whole language on one page, for a model to read; llms.txt points to it |
| The desktop | Windows, menus and applications in a terminal, all of it hibr: a first hour, in five pictures |
| Everything else | Data, networking, the prompt, interactive use, full-screen programs, the desktop, modules, deployment, testing |
man hibr |
Every flag, environment variable, file and exit status |
Every example in these pages is run on every build and its output compared
with what the page prints (tests/531-doc-examples.t), so the documentation
cannot quietly drift from the shell.
Each has a record of its own explaining the reasoning and the cost, all indexed in the decisions. The ones met first:
set -eis scoped: a failure inside a function called from a condition still stops the script, and a failing stage of a pipeline fails it, so there is nopipefail.- A quoted subscript is a literal key,
h["content-type"]; arrays are sparse maps, and maps nest. - Regex captures go to
M, and=~fillsBASH_REMATCHas well. retdoes not print; functions hand back a value through:=without forking.- Brace expansion is literal-only
—
{$a,$b}is left as written.
bash's compound coprocess, coproc name { …; }: hibr's coproc takes a
command, coproc name cmd args… -- see
0018.
The line editor stores bidirectional text in logical order and leaves
reordering to the terminal, so the cursor moves in logical order through Arabic
text.
include/ |
Public header and module ABI, internal declarations, regex declarations |
src/ |
The shell itself — lexer, parser, expansion, execution, builtins, editor, networking |
mods/ |
Reference modules, including the prompt and its git implementation |
tests/ |
The harness, the scripts compared against bash or recorded, the full-screen suites, and the suite hibr runs on itself |
examples/ |
Complete scripts showing the pieces working together |
docs/ |
Everything above, in detail |
Conventions for working on the code — naming, memory discipline, and the traps already found — are in CLAUDE.md. History is in CHANGELOG.md.
LGPL-3.0-or-later: the GNU Lesser General Public License, version 3 or any later version. See LICENSE, and COPYING for the GNU General Public License it adds its permissions to.
The name "hibr" is a trademark of Omar Sakka. The licence covers the code, not the name: a modified version or a fork is welcome under the LGPL with a name of its own. See TRADEMARKS.