DEV Community

Cover image for I built a Linux shell that uses Lua instead of bash syntax
hinika
hinika

Posted on

I built a Linux shell that uses Lua instead of bash syntax

I've been working on wisp, a Linux shell that uses Lua 5.4 for scripting and config instead of inventing another shell grammar.

The core idea is simple: any global function you define in ~/.config/wisp/init.lua becomes a shell command. No alias syntax, no config DSL -- just Lua.

function ll(input)
    return sh("ls -la")
end

function g(input, pattern)
    return sh("grep " .. pattern)
end
Enter fullscreen mode Exit fullscreen mode

Then use them like any command: wisp> ll | g "wisp"

Why Lua?

Every shell invents its own scripting language. Bash has its own grammar, fish has its own, zsh has its own. They all have different syntax for aliases, functions, conditionals, and loops.

Lua is already a real programming language with closures, tables, loops, and a standard library. It's the most embedded scripting language on the planet. Why invent another one?

Structured Pipeline Data

The other thing that's different: native pipeline stages (user-defined functions and builtins) pass Lua tables between each other in-process. No fork, no serialization.

function nums(input)
  return { {n = 3}, {n = 1}, {n = 2} }
end

function sorted(input)
  table.sort(input, function(a, b) return a.n < b.n end)
  return input
end
Enter fullscreen mode Exit fullscreen mode

wisp> nums | sorted passes the table directly from nums to sorted without forking or text parsing. Only when a pipeline hits an external command (grep, wc, anything on $PATH) does wisp fork and serialize to bytes.

What's in v2

  • Full job control: fg, bg, kill, disown, wait
  • Globbing, brace expansion
  • ${VAR:-default} expansion
  • 2>&1 redirect
  • Tab completion for commands, filenames, Lua functions
  • Levenshtein command-not-found suggestions
  • Script mode: ./wisp -f script.lua or #!/usr/bin/env wisp
  • Builtins: cd, exit, export, command, jobs, fg, bg, kill, disown, wait, echo, source, pwd, type

Stats

  • Binary: 195K (stripped: 168K)
  • Source: ~2,300 lines C++17
  • Dependencies: Lua 5.4, vendored linenoise
  • Startup: 1.5ms

What it can't do (yet)

No array syntax, no [[ ]] tests, no ${!prefix*} expansions. Not a bash replacement for scripts -- focused on interactive use.

GitHub: https://github.com/Hinikaa/wisp

Top comments (2)

Collapse
 
topstar_ai profile image
Luis Cruz

I particularly appreciate how you've leveraged Lua's strengths to create a more seamless and expressive shell experience, such as using Lua tables to pass data between pipeline stages, which eliminates the need for forking and serialization. The example with the nums and sorted functions demonstrates this beautifully, showcasing how wisp can handle structured data in a more efficient and intuitive way. How do you envision the community contributing to the development of wisp, especially in terms of adding support for features like array syntax or more advanced testing constructs?

Collapse
 
hinika profile image
hinika

Thanks! The nums | sorted pipeline was the moment I knew this wasn't just a toy -- passing actual data structures between stages instead of parsing text at every fork just makes sense for anything beyond ls | grep.

For community contributions, the honest answer is: I'm not sure yet. The codebase is small enough (~2,300 lines) that I can hold the whole thing in my head, which is both a strength and a constraint. I don't want to open it up to PRs that I can't review properly.

That said, the areas where outside help would actually be useful:

  1. Testing on different systems. I've only tested on x86-64 Linux with g++ and Lua 5.4. If someone wants to try it on ARM, musl, or with clang, that's real value.

  2. Edge cases in the parser/executor. The shell has basic job control, redirects, and expansion, but there are definitely quoting/escaping edge cases that only show up when someone actually uses it as their daily shell.

  3. Init.lua libraries. The real power is in what people build in init.lua -- utility functions, pipeline stages, prompt customizations. That's where community contributions could be most useful without touching the C++ core.

As for array syntax or advanced testing constructs -- I'm deliberately keeping v2 simple. Arrays in a shell are a design rabbit hole (indexed vs associative, slicing syntax, iteration). I'd rather get the fundamentals right than chase feature parity with bash.

If someone wants to fork it and add those things, the license allows it. But for the main repo, I'm taking it slow.