<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: hinika</title>
    <description>The latest articles on DEV Community by hinika (@hinika).</description>
    <link>https://dev.to/hinika</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4036581%2F61069be2-6df6-4b51-a1e6-3ca72f5d1fe1.png</url>
      <title>DEV Community: hinika</title>
      <link>https://dev.to/hinika</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hinika"/>
    <language>en</language>
    <item>
      <title>I built a Linux shell that uses Lua instead of bash syntax</title>
      <dc:creator>hinika</dc:creator>
      <pubDate>Sun, 19 Jul 2026 13:37:07 +0000</pubDate>
      <link>https://dev.to/hinika/i-built-a-linux-shell-that-uses-lua-instead-of-bash-syntax-3cam</link>
      <guid>https://dev.to/hinika/i-built-a-linux-shell-that-uses-lua-instead-of-bash-syntax-3cam</guid>
      <description>&lt;p&gt;I've been working on wisp, a Linux shell that uses Lua 5.4 for scripting and config instead of inventing another shell grammar.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ls -la"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;g&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"grep "&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use them like any command: &lt;code&gt;wisp&amp;gt; ll | g "wisp"&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Lua?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured Pipeline Data
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;table.sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  What's in v2
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Stats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Binary: 195K (stripped: 168K)&lt;/li&gt;
&lt;li&gt;Source: ~2,300 lines C++17&lt;/li&gt;
&lt;li&gt;Dependencies: Lua 5.4, vendored linenoise&lt;/li&gt;
&lt;li&gt;Startup: 1.5ms&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What it can't do (yet)
&lt;/h3&gt;

&lt;p&gt;No array syntax, no &lt;code&gt;[[ ]]&lt;/code&gt; tests, no &lt;code&gt;${!prefix*}&lt;/code&gt; expansions. Not a bash replacement for scripts -- focused on interactive use.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Hinikaa/wisp" rel="noopener noreferrer"&gt;https://github.com/Hinikaa/wisp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
