[HN Gopher] C89cc.sh - standalone C89/ELF64 compiler in pure por...
___________________________________________________________________
C89cc.sh - standalone C89/ELF64 compiler in pure portable shell
Author : gaigalas
Score : 190 points
Date : 2026-04-01 08:41 UTC (2 days ago)
(HTM) web link (gist.github.com)
(TXT) w3m dump (gist.github.com)
| gaigalas wrote:
| Single standalone file, no external tools used, PATH='' (empty),
| portable (bash, dash, ksh, zsh), produces x86 ELF executables,
| has mini-libc builtin.
|
| Usage:
|
| printf 'int main(){puts("hello");return 0;}' | sh c89cc.sh >
| hello
|
| chmod +x hello
|
| ./hello
| angry_octet wrote:
| I can't think of a reason to use c89cc.sh, but I salute this
| effort nonetheless.
| gaigalas wrote:
| The main show are the techniques for writing portable shell
| scripts, not the compiler.
|
| If you want something one would actually use, try my project
| tuish:
|
| http://github.com/alganet/tuish
| honkcity wrote:
| Is there a linter to ensure scripts are portable across
| shells? I try to write them like that but I'm certainly no
| master so I write them to work with busybox.
| gaigalas wrote:
| A linter, not yet.
|
| You can use what I use: https://github.com/alganet/shell-
| versions
|
| It's a container with lots of shells that you can test.
| Like esvu but for the shell.
|
| Might have a little outdated docs, hit me with an issue
| if you use it and face any problems (I'm also the
| author).
| pvtmert wrote:
| I think shellcheck helps quite a lot, you must set type
| to "sh" (not bash) somewhere in the comments though...
| jonahx wrote:
| gorgeous!
| t-3 wrote:
| Why not POSIX or some common external tools where it makes
| sense? Most of those big switch statements could be easily
| replaced with some standard programs that already exist
| everywhere.
| gaigalas wrote:
| One main reason is performance. Forking for other tools is
| very expensive.
|
| That said, using larger sed or awk programs instead of ad-hoc
| calls for small snippets would perhaps be net-positive for
| performance and readability.
|
| I'm currently working on very strict bootstrap scenarios in
| which sed and awk might not be available, but a shell might
| be (if I'm able to write it). It is possible that in such
| scenarios, the fist send and awk versions will be shell-
| written polyfills anyway.
| MisterTea wrote:
| > One main reason is performance
|
| This assumes the executed program is as fast or slower than
| the caller.
| Brian_K_White wrote:
| Why not just use gcc which already exists everywhere?
|
| When you answer that, same answer. If you can't imagine any
| answer for that, then the answer won't be convincing or make
| sense even if anyone tried to articulate it. Which is fine.
| Everyone doesn't have to find meaning in the same things.
| t-3 wrote:
| Shell without a userland is like FORTH without the ability
| to define new words. It's really contrary to the whole idea
| of what a shell is. Bootstrapping in very constrained
| conditions makes some sense, but where would you have a
| POSIX shell and not a POSIX userland (or close equivalent)
| to work with? When I wrote a similar compiler in shell, I
| purposely offloaded everything I could to external tools
| and used the shell for composition, so I found the approach
| intriguing and wanted to ask. I wasn't trying to criticize
| or dismiss the project, I think it's really cool or else I
| wouldn't have bothered to read the code in the first place.
| anthk wrote:
| Busybox it's what you need. On Forth, Subleq+EForth can
| do a lot more than you think.
| Brian_K_White wrote:
| gcc exists essentially everywhere a shell exists too. If
| you're ok with using grep and bc or whatever, then why
| not gcc?
|
| Or better yet, awk? awk is as old and ubiquitous as sh
| itself, on every machine even ancient ones that don't
| even have a compiler because that was a paid extra. Only
| unlike sh it's actually a full normal programming
| language that can do basically everything the shell can
| do only in a far more readable and sane way instead of
| using wierd expansions and abusing the command line
| parser to achieve functions it doesn't have overt
| functions for. Just write directly in awk the same way
| you would in say python or js. If you have sh, especially
| if you also have the userland you are talking about, then
| you have awk. It's part of that userland in a way that
| gcc is admittedly not.
|
| More in your vein actually, when I do things like this I
| pick yet a different ideal goal than either you or the
| author. I avoid all externals (and even child shells) to
| whatever extent possible, but I do use bash for all its'
| worth. Every possible intentional or hack bashism.
| Require bash, but leverage bash to within an inch of it's
| life and require nothing else.
|
| But this project tageting more portable code that doesn't
| require bash is really cool and valuable. Even though
| it's not a standard I personally shoot for even when I am
| specifically shell-golfing.
|
| There are probably as many different points along the
| spectrum to draw the line as there are individual
| developers, each with some actually reasonbable argument
| to justify that particular place to draw the line.
|
| To me using grep and sed and tr and ls and cat etc etc
| when I don't need them is just unsatisfying, inelegant,
| uninteresting.
|
| If you are in bash or ksh93 or zsh, you don't need all
| kinds of things like basename, dirname, cut, tr, wc, nor
| some of the more powerful stuff either most of the time.
| I have a shell function that uses the built-in read
| combined with a named pipe file created in tmp to make a
| sleep that doesn't need /bin/sleep. Why bother? because
| it's awesome. And usually the only times I need to use
| sleep it's in some rapid short duration polling loop that
| really is better if you don't have to fork & exec &
| teardown on every iteration. It's bad enough to be
| polling like that in the first place. And it just doesn't
| matter how "probably all the externals will be there",
| not using them is even better. And these days a lot of
| once-common "userland" is no longer common or installed
| by default. A script that never tries to run dos2unix
| never cares that it's not installed, or that the bsd
| version behaves differently, or the mac version is stupid
| old, etc.
| dmitrygr wrote:
| Many parts of this are clearly autogenerated, but that in no way
| diminishes the sickening impressiveness of it!
| phire wrote:
| It does make it a little hard to understand how the
| parser/ast_builder works.
|
| But the rest seems easy enough to understand.
| fuhsnn wrote:
| > It does make it a little hard to understand
|
| Or much easier to backdoor...
| gaigalas wrote:
| This is a hand-written parser for the shell itself (portable
| shell parsing portable shell):
|
| https://gist.github.com/alganet/23df53c567b8a0bf959ecbc7b689.
| ..
|
| It's an incomplete idea from around a year ago. The approach
| taken here (aliases as macro-like evals, AST generation using
| shell variables) became the backbone for the BNF parser
| generator.
|
| This one is much simpler to understand. Simpler grammars tend
| to produce parser code that looks more like this one.
| userbinator wrote:
| It would be far more interesting to look at what this was
| "compiled" from; it looks like the output of a state-machine
| generator.
| gaigalas wrote:
| The source for BNF generator is here:
|
| https://gist.github.com/alganet/4dfd501a3377a60f7825901114d6.
| ..
|
| Roughly 70% of c89cc was generated from it (parser, emitter).
|
| It can generate parsers for C, ES6 and XML for example
| (subsets but not missing a lot).
|
| It's still a mess though and I have lots of work to do to a
| proper release.
| gaigalas wrote:
| Yes! The main parser and emitter come from a BNF parser
| generator, also written in portable shell (to be released
| though).
| kelsey98765431 wrote:
| Would be a lot better if it came with tests. Please do this
| justice and dont let it rot as a gist, make a real repo and add
| some docs and at least smoke tests or some kind. Thanks
| fuhsnn wrote:
| Don't understand why you were downvoted. An untested C compiler
| is simply worthless.
| nananana9 wrote:
| The 2026 brain simply cannot comprehend recreational
| programming.
| fuhsnn wrote:
| Well, I happen to have been recreationally maintaining a
| hobbyist C compiler for three years, adding tests is part
| of the fun.
| gaigalas wrote:
| You want to know what kinds of programs it can run, right?
|
| Look at this one:
|
| https://gist.github.com/alganet/1513d7b6abef5c1a53a324d897c3.
| ..
|
| shell.c is a shell interpreter written for c89cc.sh. It can
| do the full self-hosting ouroboros:
|
| - c89cc.sh can compile shell.c
|
| - compiled shell.c via c89cc.sh can run c89cc.sh
|
| It's not a full blown battle tested shell interpreter yet,
| but I'm working on it.
|
| This file is part of the bootstrapping setup I'm working on
| for very early (pre tinyc) bootstrap from source in x64
| machines and it is by far the most complicated program
| c89cc.sh can compile.
| fuhsnn wrote:
| Thanks, that actually look like a very solid baseline to
| start things with. Are you aware of onramp[1]? They use a
| custom VM to base compiler and shell on, it's extra steps,
| but could be more flexible long term.
|
| [1] https://github.com/ludocode/onramp
| ludocode wrote:
| I'm the author of Onramp. Thanks for linking it!
|
| One of the VMs I wrote for Onramp is in POSIX shell [1].
| This was intended to make C bootstrappable on any POSIX
| system of any architecture with nothing besides the
| shell. Unfortunately it's about 100,000x too slow to be
| useful. It's also at least as complicated as a machine
| code VM. I've since mostly abandoned the POSIX shell
| idea.
|
| Onramp does have a very simple C89 VM though, and its
| purpose is for bootstrapping modern C on systems that
| have only a basic C compiler [2]. So this c89cc.sh could
| in theory work. I tried it and unfortunately it doesn't
| quite compile yet (and doesn't give a comprehensible
| error message either.) Even if it worked, c89cc.sh only
| compiles to x86_64 ELF, and it's way more complicated
| than the x86_64 ELF machine code Onramp VM [3].
|
| This has been a bit of a recurring theme with Onramp:
| anything I've tried to get away from the initial machine
| code stages ends up being more complicated than
| handwritten machine code. Still, it's nice to have a lot
| of different ways to bootstrap. I love seeing projects
| like this and I'm glad to see more people taking
| bootstrapping seriously.
|
| [1]: https://github.com/ludocode/onramp/blob/develop/plat
| form/vm/...
|
| [2]: https://github.com/ludocode/onramp/blob/develop/plat
| form/vm/...
|
| [3]: https://github.com/ludocode/onramp/blob/develop/plat
| form/vm/...
| gaigalas wrote:
| This gist is a concatenation of several shell script modules
| which form a comprehensive parser library for the portable
| shell.
|
| The main parser and emitter are BFN-generated (that's why they
| look so mechanical). The BNF parser generator is also written
| in portable shell (I posted another gist with a preview of it
| in another thread).
|
| All modules have comprehensive tests, but it is still lacking
| documentation and not ready for prime time!
| akavel wrote:
| In the classic FLOSS tradition, it would be cool if you might
| still consider publishing such a "not-ready" repository -
| some people may (or may not!) be still interested, and also
| (sorry!) there's the bus factor... But on the other hand, in
| the classic FLOSS tradition, it's also 100% your decision and
| you have the full right to do any way you like!
| jey wrote:
| It targets x86-64/ELF? I thought it would target `sh` to be
| portable?
| gaigalas wrote:
| That sounds like pnut:
|
| https://github.com/udem-dlteam/pnut
| saagarjha wrote:
| SuperH is not that portable.
| _ache_ wrote:
| I'm tempted to execute it, but it may as well be shellcode I
| couldn't tell.
| self_awareness wrote:
| "Claude please generate me a C compiler in bash"
|
| I mean, today it's possible to generate it in Tcl, Elisp, Windows
| BAT, Powershell.
|
| The effort is just 1 prompt.
|
| The WHY question is much more important today -- "because I can"
| no longer makes sense, because we all can do much, much more with
| minimum effort today than before LLMs.
| gaigalas wrote:
| Here's a prototype parser from 10 months ago, when this was not
| possible yet:
|
| https://gist.github.com/alganet/23df53c567b8a0bf959ecbc7b689...
|
| Here is me 10 years ago experimenting on parsing stuff with
| sed:
|
| https://gist.github.com/alganet/542f46865420529c9bd2
|
| ---
|
| Yes, c89cc.sh was definitely AI-assisted. However, I do carry
| extensive knowledge of the portable shell that was essential
| for the AI to complete it.
|
| You'll find tricks inside c89cc.sh that don't exist anywhere,
| except in other code from me (like the ksh93 fix for local
| dynamic scoping or the alias/macro read -n1 polyfill).
|
| The WHY is pretty obvious: I want to show that the portable
| shell is not a toy.
| self_awareness wrote:
| I didn't mean to imply that you're not capable doing it
| without LLM. I believe you.
|
| The point I'm trying to make is that _the rest of us_ that
| don 't know bash that well is capable of doing it as well.
|
| This is the new reality we all need to adapt to.
| gaigalas wrote:
| Just bash? Sure. Anyone can do just bash.
| oybng wrote:
| If you can't discern between bash and shell, or even read
| the title, then it's certainly not for you
| fkoep wrote:
| >The WHY is pretty obvious: I want to show that the portable
| shell is not a toy.
|
| What does that mean? You sat down with the goal of showing
| that a decades old scripting environment can be used for
| large projects in production, with all its obscure hacks? I'd
| say it's more a novelty project made for the fun of it - and
| that's fine, it's a cool project.
|
| It would be pretty interesting to read a blog post about the
| making-of: How to write a compiler in portable shell, what
| parts could be automated and where LLM-coding fell short,
| what rare tricks were applied, etc...
| gaigalas wrote:
| Most people think shell is just bash, and portability is
| impossible.
|
| It is also common sense that shell scripts are just glue
| code, and it's impossible to do anything else with it.
|
| I think they're wrong. Never said one should use this to
| write large production programs though.
|
| The hacks I'm using are no different than JavaScript
| polyfills (set once, makes a feature more uniform). It's
| actually a clean design, the bulk of the program is POSIX
| shell.
| anthk wrote:
| LLM's can barely generate valid Subleq code.
| uecker wrote:
| I am tempted to click the "report abuse" link ;-)
| dmead wrote:
| This is vibe coded right?
| gaigalas wrote:
| It is AI assisted, but way less than one might think.
|
| All the portability tricks are authored by me (I have previous
| repos from before the AI era that feature them).
|
| The parsing structure is also authored by me (also have pre-AI
| proof).
|
| What AI did here was the boring, mechanical work.
|
| Just prompt it, all AI as of 2026 is clueless about wide
| portability shell scripts.
| wengo314 wrote:
| if one could boostrap tcc with it, then it might be a viable
| tool.
| redoh wrote:
| Writing a C compiler in pure shell is one of those projects that
| sounds absurd until you think about bootstrapping. If you want to
| compile C on a system where you literally have nothing but a
| POSIX shell, this is exactly what you need. The fact that the
| parser itself is BNF-generated from shell modules makes it even
| more interesting as a study in how far you can push shell
| scripting before it breaks. Would love to see this evolve into a
| proper repo with tests so it can actually serve as a minimal
| bootstrapping tool.
| lpribis wrote:
| I don't think any realistic system will bootstrap C from shell.
| What is the shell implemented in if not C?
| gaigalas wrote:
| This shell.c can be compiled from c89cc.sh itself:
|
| https://gist.github.com/alganet/1513d7b6abef5c1a53a324d897c3.
| ..
|
| Ouroboros self-hosting. They can self-host one another.
|
| The idea is to make shell.c compile from an even simpler C
| compiler, such as M2-Planet:
|
| https://github.com/oriansj/M2-Planet
|
| Let me remind you that current stage0 bootstraps tinyc from
| mes, which is an interpreted lisp. It's not that different
| from the shell architecturally.
|
| The current stage0 also features kaem as one of the first
| dependencies. kaem is, in fact, a simpler version of the
| bourne shell.
|
| It's always a tower. You'll never get a one single clean
| dependency pass in bootstrapping.
| zhongwei2049 wrote:
| The shell.c ouroboros is really cool. Being able to
| bootstrap trust through an entirely different language
| family (shell - C - shell) adds genuine value to the
| trusting-trust problem beyond just technical novelty.
| cestith wrote:
| Even if your shell was compiled in C, it doesn't mean it
| wasn't cross-compiled from another platform.
| xonix wrote:
| But how would POSIX shell be available? Wouldn't it be compiled
| from C?
| laurenth wrote:
| A shell is almost always used to setup the bootstrap
| environment, so the dependency on a shell is more or less
| always there.
|
| Otherwise, something special with POSIX shell is its large
| number of independent implementations, making it the ideal
| starting point for diverse double-compilation
| (https://arxiv.org/abs/1004.5534). The idea is to bootstrap a
| toolchain from multiple compilers (shells in this case), and
| the result compared to verify that no shell introduced a
| trusting trust attack.
| lioeters wrote:
| > bootstrapping
|
| This project reminds me of:
|
| The Design of a Self-Compiling C Transpiler Targeting POSIX
| Shell - https://dl.acm.org/doi/10.1145/3687997.3695639
|
| It's not just a toy or a fun hobby project, there's potential
| for practical use as a step in bootstrapping an entire software
| stack from human-verifiable artifacts.
| wahern wrote:
| It's an ongoing project: https://github.com/udem-dlteam/pnut
|
| See also pnut.sh, but the server seems to be down at the
| moment.
| cestith wrote:
| I love this as a novelty, and it could be useful for
| bootstrapping a system that's had a shell cross-compiled to it.
|
| Thinking about this in the context of a job I used to do,
| security on shared hosting environments, it gives me a bit of a
| shiver. There are reasons compilers aren't available to normal
| users on those.
___________________________________________________________________
(page generated 2026-04-03 23:02 UTC)