16 KiB
rdb
Ein Nachbau von /rdb, dem relationalen Datenbanksystem der Mark Williams Company für COHERENT (um 1991), in Ruby. Das Original-Handbuch und die Man-Pages stammen aus den freigegebenen MWC-Quellen (nesssoftware.com/home/mwc/source.php). Der Programmcode von /rdb selbst war darin nicht enthalten. Alle Befehle hier sind nach dem Handbuch neu geschrieben.
Einrichten
mise trust # einmal nach dem Clone: mise.toml freigeben
mise install # Ruby 3.4
mise run install # legt die Symlinks row, column, total ... in bin/ an
mise run test # 132 Tests und die Beispiele aus dem Handbuch
Ohne mise trust brechen in einem frisch geklonten Ordner alle mise-Befehle ab.
Wichtig: Nach jedem Clone zuerst die Symlinks anlegen. Im Repository liegt nur
bin/rdb. Die Befehlerow,column,total... sind Symlinks darauf und werden nicht mit eingecheckt (.gitignore). Ohnemise run install(oder ohne mise:bin/rdb install) gibt es sie nicht, und Aufrufe wierow ...enden mitcommand not found. Auchtestallund die Tests brauchen sie. Nach neuen Befehlen den Schritt wiederholen.
Danach steht bin/ im PATH, solange man im Projektordner ist (mise.toml).
Jeder Befehl ist ein Symlink auf bin/rdb, das am Aufrufnamen erkennt, was zu
tun ist. Es geht auch ohne Symlinks: rdb row 'Cost > 5' < demo/inventory.
Vier Namen verdecken im Projektordner Systembefehle: column, rename,
display und whatis.
Das Prinzip
Eine Tabelle ist eine Textdatei: Kopfzeile mit Spaltennamen, Strichzeile, dann eine Zeile pro Datensatz, Spalten durch Tabulator getrennt. Jeder Befehl liest eine Tabelle von stdin und schreibt eine Tabelle nach stdout. Befehle lassen sich mit Pipes kombinieren.
cd demo
row 'Cost > 5' < inventory | column Description Cost | sorttable -nr Cost
compute 'Value = Cost * Amount' < inventory | total Value
jointable inventory owners
Dasselbe gilt für Listen (ein Datensatz als Spalte<Tab>Wert-Zeilen, die
Datei beginnt mit einer Leerzeile). tabletolist und listtotable wandeln um,
die meisten Befehle lesen beides.
column -d Spalte ... löscht Spalten (alle anderen bleiben), column Spalte ... wählt sie aus.
row, compute und validate übergeben ihren Ausdruck an awk, nachdem sie
Spaltennamen durch $N ersetzt haben. Es gilt also awk-Syntax.
Spaltennamen unterscheiden Groß- und Kleinschreibung. Mit RDB_ICASE=1 wird
sie ignoriert (Details unter „Unterschiede zum Original“):
RDB_ICASE=1 compute 'wert = cost * amount' < inventory
Hilfe
rdb alle Befehle
rdb man search die Original-Man-Page als Text (auch Themen: backup, dbase, dashline ...)
rdb --version Versionsnummer (Änderungen siehe CHANGELOG.md)
rdb man -g search dieselbe Seite mit groff formatiert (Beispiele sauber eingerückt)
whatis column Beschreibung und Syntax
whatwill total Befehle, die zu einem Stichwort passen
commands Tabelle aller Befehle
Beispiele zu allen Befehlen
demo/testall.cmds enthält zu jedem Befehl ein Beispiel mit kurzer Erklärung.
testall führt es in einer Kopie von demo/ aus und zeigt jede Zeile samt Ausgabe:
testall | less alle Beispiele ansehen
testall > testall.old Lauf als Vergleich festhalten
testall | diff testall.old - später prüfen, was sich geändert hat (mise run testall)
Eine Zeile mit ## erscheint als Überschrift in der Ausgabe, eine mit # ist
ein stiller Kommentar. Neue Befehle brauchen hier ein Beispiel, sonst schlägt der Test
test_testall_mentions_every_command fehl.
Beispiele aus dem gedruckten Handbuch
doc/handbuch.txt ist der Text des gedruckten Handbuchs „COHERENT /rdb Relational
Database Manager“ (Mark Williams, 346 Seiten), per OCR aus dem Scan coherent.pdf.
Er ist nicht korrigiert: Tabulatoren fehlen, Tabellen können verrutscht sein. Jede
Seite ist mit PDF- und Buchseite markiert (Buchseite = PDF-Seite − 10). Kapitel 1–16
sind ein Lehrbuch, Kapitel 17 enthält dieselben Man-Pages wie man/.
demo/handbuch/ enthält die Beispiele aus dem Lehrbuch mit ihren Tabellen, ein
Unterordner je Kapitel. beispiele.txt hält zu jedem Beispiel den Befehl und die
Ausgabe, wie sie im Buch steht; test/handbuch_test.rb führt alles aus und vergleicht
(ohne Leerraum, das Buch richtet mit Leerzeichen aus). In der Datei bedeutet:
@ kap03 die folgenden Befehle laufen in diesem Unterordner
## S. 26 ... Überschrift mit Buchseite
$ befehl darunter die Ausgabe laut Buch
# ... Kommentar, z. B. ein Druckfehler des Buchs (die Ausgabe ist korrigiert)
! ... hier weichen wir bewusst ab; nicht verglichen, der Test meldet aber,
wenn die Ausgabe doch wieder zum Buch passt
Vorlagen: form und entry
demo/formist eine Vorlage fürreportundletter: sie setzt Spaltenwerte in einen Text ein. Sie hat keine Beschriftungen und nenntNamezweimal.demo/entryist eine Eingabemaske fürscreen: jedes Feld kommt einmal vor und hat eine Beschriftung.screen < entry > maske.sherzeugt das Skript. Starten:CLEAR=$(clear.rdb) sh maske.sh > tabelle 2>&1. Die Tabelle geht auf stderr (wie im Original), daher das2>&1. Mitformwürde die Maske leer und doppelt aussehen.
Eingabemasken: mask
mask ist die Variante von screen mit eigener Oberfläche (nur Ruby, kein ncurses). Es zeichnet die
Vorlage (von der Standardeingabe, wie screen; ein Dateiname geht auch) selbst und schreibt die Eingabe als Tabelle auf die Standardausgabe, also pipefähig:
mask -b < demo/maskform Rahmen (-b), Felder, Schaltflächen
mask < demo/maskform > neu Tabelle in eine Datei
mask < demo/maskform | append -h kunden oder direkt in eine Tabelle
mask -s < demo/maskform nur das Bild und die Feldpositionen, ohne Terminal
In der Vorlage: <Spalte> oder <Spalte:20> ist ein Feld (mit Breite), [ Speichern ] eine Schaltfläche,
[ Abbrechen ] (oder Cancel, Abort, Quit) bricht ab, <!date!> fügt Befehlsausgabe ein. Kommt eine Spalte mehrfach
vor, ist es ein Feld. Tab/Pfeile wechseln, Enter bestätigt, Esc bricht ab (Status 1, keine Ausgabe).
Zeilen auswählen: pick
pick zeigt eine Tabelle auf dem Terminal, man scrollt, filtert und wählt eine oder mehrere Zeilen. Die Tabelle
kommt von der Standardeingabe, die gewählten Zeilen gehen als Tabelle auf die Standardausgabe, also mitten in der Pipe:
pick < inventory eine Zeile wählen
pick -m < inventory | total Amount Value mehrere (Leertaste markiert, a alle)
column Item Description < inventory | pick -m | jointable - owners
pick -m -k 'down space down enter' < inventory Tasten aus einem String (Skripte, Tests)
Pfeile, Bild auf/ab, Pos1/Ende scrollen; / filtert (Enter behält den Filter, Esc löscht ihn); Enter wählt;
Esc bricht ab (Status 1, keine Ausgabe). Ohne Markierung gilt die Zeile unter dem Cursor.
Steuertabellen 2026 für tax
demo/ enthält vier Tarife für das Steuerjahr 2026 im Format von tax (income, tax, percent), jeweils für
Alleinstehende (Grundtarif). Erzeugt mit ruby tools/gen_tax_tables.rb.
| Datei | Land | Quelle | Genauigkeit |
|---|---|---|---|
taxDE2026 |
Deutschland, Einkommensteuer | § 32a EStG (Grundfreibetrag 12 348 €) | Kurvenstücke durch Geraden genähert, Abweichung unter 0,15 € (die Formel rundet ab, tax auf) |
taxAT2026 |
Österreich, Einkommensteuer | § 33 EStG (0 % bis 13 539 €, dann 20/30/40/48/50/55 %) | exakt, vor Absetzbeträgen |
taxCH2026 |
Schweiz, nur direkte Bundessteuer | Art. 36 DBG, ESTV-Rundschreiben 2-215-D-2025 | Stufen exakt, das Gesetz rechnet in 100-Franken-Schritten |
taxLI2026 |
Liechtenstein, nur Landessteuer | Art. 19 SteG (LR 640.0, Fassung 01.07.2026) | exakt |
taxCH_AI2026 |
Kanton Appenzell I.Rh., einfache Staatssteuer | Art. 38 StG (ESTV-Kantonsblatt) | exakt |
taxCH_AR2026 |
Kanton Appenzell A.Rh., einfache Staatssteuer (Alleinstehendentarif) | Art. 39 StG, Beträge ab 2024 | exakt |
taxCH_SG2026 |
Kanton St. Gallen, einfache Staatssteuer | Art. 50 StG, Beträge ab 2026 | exakt |
taxCH_GR2026 |
Kanton Graubünden, einfache Kantonssteuer | Art. 39 StG, indexierte Beträge ab 2025 | exakt |
tax 60000 < demo/taxDE2026 14233
tax 60000 < demo/taxAT2026 15447
tax 60000 < demo/taxCH2026 671
tax 60000 < demo/taxLI2026 1396 (mal 1 + Gemeindezuschlag, 150 % bis 250 %)
Der Betrag ist die Steuer auf das zu versteuernde Einkommen. Abzüge, Sozialversicherung, Absetzbeträge, Kirchensteuer und Soli sind nicht enthalten. Für die Schweiz kommen Kanton und Gemeinde dazu (sie sind meist höher als der Bund), für Liechtenstein der Gemeindezuschlag: gesamt = Landessteuer × (1 + Zuschlag/100). Ehepaar- und Alleinerziehendentarife fehlen. Das ist keine Steuerberatung.
Kantone (AI, AR, SG, GR): Die Dateien tragen den Präfix taxCH_ (Schweiz) vor dem Kantonskürzel; taxCH2026 ist die Bundessteuer. Die Tabellen geben die einfache Steuer wieder. Sie wird mit dem Steuerfuss multipliziert,
und Gemeinde und Kirche kommen dazu. Der Kanton setzt den Steuerfuss pro Jahr fest:
| Kanton | Steuerfuss Kanton 2026 | Quelle |
|---|---|---|
| AI | 96 % | Kantonsblatt AI (Grossratsbeschluss 1.12.2025) |
| SG | 105 % | Budgetbeschluss des Kantonsrats (Websuche, nicht im Kantonsblatt) |
| GR | 92 % | Grosser Rat, Budget 2026 (Websuche) |
| AR | nicht ermittelt | AR rechnet in Steuereinheiten, die Zahl für 2026 habe ich nicht gefunden |
Beispiel AI bei 60 000: tax 60000 < demo/taxCH_AI2026 = 3770, mal 0,96 = 3619 (Kantonssteuer, ohne Bezirk, Gemeinde und Kirche).
Oberhalb einer Grenze gilt bei allen vier Kantonen ein fester Satz auf das ganze Einkommen (AI ab 200 000 mit 8 %,
AR ab 260 800 mit 2,6 %, SG ab 264 500 mit 8,5 %, GR ab 780 440 mit 11 %); in der Tabelle ist das die letzte Zeile.
Befehle
| Gruppe | Befehle |
|---|---|
| Auswahl | row select column project sorttable record number Number |
| Spalten | compute addcol rename |
| Mengen, Verbund | jointable union intersect difference paste.rdb splittable |
| Auswertung | total mean maximum minimum subtotal width widest precision datatype schema dbdict |
| Schnellzugriff | index search seek append delete replace enter lock unlock vilock update.rdb ve vindex |
| Prüfen | check.rdb domain validate fd searchtree tableorlist |
| Format | justify trim compresss pad rmblank blank headon headoff insertdash display see |
| Text | cap uppercase lowercase substitute trimblank padstring length filesize ascii chr word prompt not path |
| Umwandeln | tabletolist listtotable listtosh tabletofix fixtotable tabletostruct tabletotbl tabletofact tabletorule tabletom4 tabletosed translate uniondict |
| Datum, Namen | julian gregorian computedate todaysdate soundex like hashkey |
| Berichte | report reportwriter screen mask pick label letter |
| Terminal, Hilfe | termput tset clear.rdb cursor menu rdb commands helpme whatis whatwill howmany man |
| Betrieb | testall testsearch timesearch rmcore cpdir.rdb |
| Buchhaltung (/act) | act chartdup foot adjust balance close consolidate getjournal fillform calculate tax cashflow calcpay w2 bom explode sale invoice cstate update.inv |
Schnellzugriff
Wie im Original sucht search über Byte-Offsets. Indizes bauen:
| Option | Methode | Datei |
|---|---|---|
-ms |
sequentiell (Vorgabe) | keine |
-mb |
binär: index sortiert die Tabelle selbst |
keine |
-mr |
Datensatznummer | tabelle.r |
-mi |
invertiert (Schlüssel und Offset, sortiert) | tabelle.i |
-mh |
Hash, doppelt so viele Plätze wie Zeilen | tabelle.h |
index -mi inventory Description
echo scales | search -mi inventory Description
echo cl | search -mi -x inventory Description # Teiltreffer, Groß/klein egal
echo 5 | seek -mi inventory Item # von bis xvon xbis
delete überschreibt einen Satz mit Leerzeichen, ohne die Datei zu verschieben.
replace schreibt an dieselbe Stelle zurück und hängt den Satz hinten an, wenn
er nicht mehr passt (außer mit -a). Beide halten die Indizes aktuell.
rmblank räumt später auf.
Unterschiede zum Original
- Es gibt kein COHERENT-Programmmaterial, daher sind die Ausgaben nur anhand der
Man-Pages und dem gedruckten Handbuch geprüft, nicht gegen das echte /rdb. Die
Beispiele aus den Man-Pages sind als Tests enthalten (
test/rdb_test.rb), die aus dem Lehrbuch indemo/handbuch/(test/handbuch_test.rb). - Die Indexdateien sind dem Original nachgebaut, aber nicht gleich (Handbuch S. 103–108):
die Hashtabelle
.hhat 2n statt 2n+1 Plätze (Hashfunktion FNV-1a), und in.isteht der Schlüssel vor dem Offset statt dahinter. Alte /rdb-Indizes und.h-Dateien früherer Versionen dieses Nachbaus muss man neu bauen. - Summen, Mittelwerte und /act-Beträge sind Dezimalzahlen (
Rational), gerundet wird halb auf. - Dateien, die kein UTF-8 sind, liest
rdbals Latin-1 und schreibt sie als UTF-8. Fehler erscheinen als eine Zeile; mitRDB_DEBUG=1kommt der Ruby-Backtrace. Werte mit Tab oder Zeilenumbruch lehnenappend,enter,replaceundveab. lock/unlocklegen ihre Sperrdateien in/tmp/rdb-locks-<UID>/ab (Modus 0700,flock), nicht als/tmp/L<Tabelle>wie in den Man-Pages. Eine Sperre eines abgestürzten Prozesses wird nicht erkannt;unlockhebt sie auf.vilocksperrt atomar über einen Hardlink;EDITORdarf Argumente haben (nvim -f). Nachkill -9bleibtLOCK<Tabelle>liegen und muss von Hand zurückbenannt werden.report: Spaltenwerte in<!befehl <Spalte>!>werden für die Shell maskiert und kommen als ein Wort an; nicht in Anführungszeichen setzen.screenverlangt Spaltennamen, die gültige Shell-Variablennamen sind (Buchstaben, Ziffern,_).sorttablelässt Zeilen mit gleichem Schlüssel in ihrer Reihenfolge; das Original ordnet sie nach der ganzen Zeile, wiesort(Handbuch S. 94).subtotalschreibt bei einer Summe von null ein leeres Feld; das Handbuch zeigt0und die Kontonummern als101.0(S. 30).- Spaltennamen unterscheiden wie im Original Groß- und Kleinschreibung. Mit
RDB_ICASE=1(z. B. inmise.tomlunter[env]) wird sie ignoriert: ein exakter Treffer gewinnt, mehrere ähnliche sind ein Fehler. Inrow/compute/validatebleiben awk-Wörter und Funktionsaufrufe unberührt (eine SpalteLengthverdeckt also nichtlength()). Die Man-Pages vonrow,compute,column,sorttable,jointable,rename,validateundrdbhaben dazu den Abschnitt „Case of Column Names“; er ist die einzige Ergänzung gegenüber den Original-Seiten. selectist in bash, zsh und ksh ein reserviertes Wort.select 'Cost > 5' < inventoryendet dort mitsyntax error near unexpected token '<', weil die Shell das Schlüsselwort vor demPATHauswertet. Es gibt dasselbe unter dem Namenrow. Außerdem gehen\select ...,command select ...undrdb select ....jointablebraucht keine vorsortierten Tabellen.veist ein zeilenorientierter Editor, kein Bildschirmeditor.soundexnutzt den Standard-Algorithmus. Die Beispielwerte im Handbuch weichen davon ab.- Die /act-Befehle arbeiten auf Tabellen im aktuellen Ordner. Welche das sind
und welche Spalten sie brauchen, steht jeweils in der Man-Page. Das Paket ist
wie im Original nur ein Beispiel für Datenbankprogrammierung. Die Befehle, zu
denen das Handbuch nichts sagt (
post,purchase,ship...), fehlen.
Aufbau
bin/rdb das eine Programm (die Symlinks darauf legt `rdb install` an, nicht im Repo)
lib/rdb.rb Registry der Befehle
lib/rdb/table.rb Tabellen und Listen lesen und schreiben
lib/rdb/fast.rb Schnellzugriff über Byte-Offsets
lib/rdb/cmd/*.rb die Befehle, nach Gruppen
man/ Man-Pages (troff-Quellen aus den MWC-Archiven)
demo/ Beispieltabellen; testall.cmds = je ein Beispiel pro Befehl, testall.old = letzter Lauf
demo/handbuch/ die Beispiele aus dem gedruckten Handbuch, je Kapitel ein Ordner
doc/handbuch.txt Text des gedruckten Handbuchs (OCR)
test/ Tests
Die Man-Pages und die Beispieltabellen stammen aus den MWC-Quellen. Lizenz der Originale: BSD, Copyright 1977-1995 Robert Swartz.