A lossless shell/environment translation layer — write one script, run it natively in bash 3.2 ↔ zsh ↔ POSIX sh (and ksh) with byte-identical behavior. Think i18n, but for shells: service localization for scripts.
License: MIT · Status: v0.1.0 (draft) · Locales verified: bash 3.2.57, bash 5.3, zsh 5.9, dash (POSIX), /bin/sh, ksh93 — all against one shared conformance suite.
In one line: the constructs that silently break a script when it moves
environments — associative arrays, ${v,,}, arrays, echo -e, process
substitution — get one portable shim each, and a conformance runner proves the
shim produces identical output in every shell present.
The trigger is concrete and everyday:
- bash 3.2 (the version Apple still ships at
/bin/bash, frozen at the last GPLv2 release) has no associative arrays and no${v,,}case expansion. - macOS default shell is zsh, whose word-splitting and array indexing differ from bash.
- CI and containers run whatever POSIX
sh(often dash/ash) — no arrays at all, nolocalin the standard, no bashisms. - ksh93 has arrays but rejects
local.
A script authored on a modern bash laptop breaks the moment it lands on any of these. rosetta-shell is the phrasebook that makes the script speak each locale.
| Part | What it is | Command / API |
|---|---|---|
| crossrun | the gate. Runs YOUR script under every installed shell and reports a pass/fail matrix — proves portability instead of guessing it. Exits nonzero on failure, so it drops straight into CI. | rosetta crossrun script.sh |
| lint | scans a script for non-portable constructs, names the shim to use (advisory, never rewrites) | rosetta lint FILE… |
| doctor | localizes your environment: which shell, which native features missing | rosetta doctor |
| rosetta.sh | sourceable shim library — a lossless portable map + case/echo/split shims | . lib/rosetta.sh |
| conformance | runs fixtures under every installed shell and asserts byte-identical stdout — the losslessness proof for the library itself | rosetta selftest |
# See what locale you're in and what's missing:
bin/rosetta doctor
# Prove losslessness across every shell on this host:
bin/rosetta selftest
# Flag non-portable constructs in your own scripts:
bin/rosetta lint path/to/script.shlint guesses from regex; crossrun runs your script under every shell locale
installed on the host and reports the truth:
$ rosetta crossrun deploy.sh
LOCALE VERSION EXIT RESULT
/bin/bash 3.2.57(1)-release 0 ok
bash 5.3.9(1)-release 0 ok
zsh 5.9 0 ok
/bin/dash posix 2 NONZERO EXIT <-- POSIX sh breaks
ksh posix 0 ok
RESULT: FAIL — at least one locale errored. # exit 1
Add --identical to also require byte-identical stdout (catches the insidious
case where a shell runs but does the wrong thing — e.g. bash 3.2 silently
mis-handling declare -A instead of erroring).
As a CI gate: .github/workflows/portability.yml
installs bash / zsh / dash / ksh and gates every push. A PR that introduces a
bashism which breaks POSIX sh fails CI instead of breaking production. No
adoption cost — it runs your existing scripts, no refactoring required.
. /path/to/rosetta-shell/lib/rosetta.sh
# Associative array that works in bash 3.2 AND POSIX sh:
rosetta_map_set env REGION us-east
rosetta_map_set env "feature flag" on
rosetta_map_get env REGION # -> us-east
rosetta_map_keys env # -> insertion-ordered, one per line
rosetta_lower "ARCS" # -> arcs (no bash4 ${v,,} needed)The map is byte-lossless: keys and values may contain spaces, tabs, quotes,
newlines, unicode, and shell metacharacters, and round-trip unchanged (verified
in conformance/fixtures/edge_values.sh).
A fixture is lossless iff its stdout is byte-identical under every shell locale present. The conformance runner hashes each shell's output and fails on any divergence. This is the same "one shared suite, many implementations" discipline germinate uses — here the "implementations" are the shells themselves.
rosetta-shell is a Tier-A candidate in the open ARCS / flashesofbrilliance
family: it declares conformance to the germinate seed contract and ships its own
SEED.md. The lineage is germinate's private rosetta stone
(redaction-by-reference) — this repo extends that same public-method / private-
priors seam from redaction to shell/environment translation. See
CONFORMANCE.md.
- Lossless or it fails. No "close enough" — byte-identical across locales.
- Advisory, never destructive.
lintnames the fix; it never rewrites your script. The construct→shim table is explicit and inspectable. - Written in the intersection. The library itself uses only the portable
subset (see
CONFORMANCE.md§"The one extension"). - No dependencies. Pure shell + coreutils (
printf,od,tr,cksum).
Part of the ARCS / flashesofbrilliance family. The translation method here is open; any environment-specific priors stay private. The animating spark stays private.