<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://brunoroberto.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://brunoroberto.github.io/" rel="alternate" type="text/html" /><updated>2026-04-09T18:41:59+00:00</updated><id>https://brunoroberto.github.io/feed.xml</id><title type="html">Bruno Fernandes</title><subtitle>A blog about technology and stuff related</subtitle><entry><title type="html">Building my own Shell</title><link href="https://brunoroberto.github.io/building-my-own-shell/" rel="alternate" type="text/html" title="Building my own Shell" /><published>2026-04-06T13:33:00+00:00</published><updated>2026-04-06T13:33:00+00:00</updated><id>https://brunoroberto.github.io/building-my-own-shell</id><content type="html" xml:base="https://brunoroberto.github.io/building-my-own-shell/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#building-my-own-shell" id="markdown-toc-building-my-own-shell">Building my own Shell</a>    <ul>
      <li><a href="#project-name" id="markdown-toc-project-name">Project Name</a></li>
      <li><a href="#tech-stack" id="markdown-toc-tech-stack">Tech Stack</a></li>
      <li><a href="#what-are-the-core-parts-of-a-shell" id="markdown-toc-what-are-the-core-parts-of-a-shell">What are the core parts of a shell?</a></li>
      <li><a href="#the-repl" id="markdown-toc-the-repl">The REPL</a></li>
      <li><a href="#lexerparser" id="markdown-toc-lexerparser">Lexer/Parser</a>        <ul>
          <li><a href="#lexer" id="markdown-toc-lexer">Lexer</a></li>
          <li><a href="#parser" id="markdown-toc-parser">Parser</a></li>
        </ul>
      </li>
      <li><a href="#command-resolver--executor" id="markdown-toc-command-resolver--executor">Command Resolver &amp; Executor</a>        <ul>
          <li><a href="#context" id="markdown-toc-context">Context</a></li>
          <li><a href="#resolving" id="markdown-toc-resolving">Resolving</a></li>
          <li><a href="#executing" id="markdown-toc-executing">Executing</a></li>
          <li><a href="#output--redirections" id="markdown-toc-output--redirections">Output &amp; Redirections</a></li>
        </ul>
      </li>
      <li><a href="#whats-next" id="markdown-toc-whats-next">What’s Next</a></li>
    </ul>
  </li>
</ul>

<p align="center"><img src="/assets/images/blog/duckshell/duckshell.gif" alt="Duck Shell" /></p>

<h1 id="building-my-own-shell">Building my own Shell</h1>

<p>Hi there.</p>

<p>Have you ever used something so often that you stopped questioning how it works? For me, that thing was the terminal.</p>

<p>Recently, I got curious: which commands in my shell are actually built-in, and which ones are external programs?</p>

<p>So I asked the shell itself:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% <span class="nb">compgen</span> <span class="nt">-b</span>
<span class="nb">unset
</span>rehash
<span class="nb">popd
ulimit
local
jobs
</span>disable
<span class="o">[</span>
compfiles
<span class="nb">printf
</span>autoload
noglob
pushln
zle
<span class="nb">readonly
exit
false
times
</span>sched
setopt
getln
<span class="nb">builtin
let
bg
</span>which
unhash
<span class="nb">pwd
</span>zparseopts
<span class="nb">logout
disown
type
source
eval
</span>comptags
compdescribe
compctl
r
zmodload
zregexparse
<span class="nb">history
</span><span class="k">return
</span><span class="nb">exec
</span>compadd
emulate
chdir
ttyctl
<span class="nb">test
</span>comparguments
<span class="nb">pushd
</span>functions
float
zstyle
print
<span class="nb">declare
</span>comptry
<span class="nb">alias
shift</span>
-
<span class="nb">.</span>
bindkey
<span class="nb">typeset
true
hash
</span>compset
<span class="nb">cd
</span>compvalues
<span class="nb">getopts
</span>compgroups
<span class="nb">export
enable
</span>limit
echotc
<span class="nb">echo
wait
dirs
</span>unsetopt
<span class="nb">read</span>
:
integer
bye
echoti
compquote
unfunction
<span class="nb">fc
</span>vared
<span class="nb">unalias
kill
</span>compcall
where
<span class="nb">fg
</span>zformat
<span class="nb">suspend
</span>unlimit
<span class="nb">break
set
</span><span class="k">continue
</span><span class="nb">command
</span>zcompile
whence
<span class="nb">umask
trap
</span>private
log

</code></pre></div></div>

<p>That gave me the list of built-in commands. Yeah… that’s a lot.</p>

<p>Looking at it, something clicked for me: the shell isn’t just a simple command runner. It’s its own little world, with
its own language, its own built-ins, and its own way of executing things.</p>

<p>And that got me thinking.</p>

<p>As I wrote on my homepage, <strong>“Making sense of things by building them.”</strong> That’s usually how I learn best. So instead of
just reading about it, I decided to try building my own shell.</p>

<p>Nothing fancy. I’m not trying to build something production ready.</p>

<p>I just want to understand a few things a bit better:</p>

<ul>
  <li>How commands are interpreted</li>
  <li>How built-ins differ from external programs</li>
  <li>How redirections actually work</li>
</ul>

<p>So… I’m building a tiny shell.</p>

<h2 id="project-name">Project Name</h2>

<p>Duck Shell. That’s the name I chose for this project. I keep a Batman rubber duck on my desk
for <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">Rubber Duck Debugging</a>. It was right there when I started this
project, so the name stuck.</p>

<p align="center"><img src="/assets/images/blog/duckshell/duck.jpeg" alt="Duck Shell" style="width: 50%;" /></p>

<h2 id="tech-stack">Tech Stack</h2>

<p>I built this in Java. It’s not the closest-to-the-metal choice, and a lot of people would prefer C for something like a
shell. Java sits behind the JVM, so you lose some of that direct system call feeling.</p>

<p>But this is a learning project, not a production tool, and Java is the language I know best. So Java it is.</p>

<h2 id="what-are-the-core-parts-of-a-shell">What are the core parts of a shell?</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Shell
 ├── REPL
 ├── Lexer
 ├── Parser
 ├── CommandResolver
 ├── Executor
 │    ├── BuiltinExecutor
 │    └── ExternalExecutor
 ├── EnvironmentManager
 ├── RedirectionHandler
 └── PipeHandler
</code></pre></div></div>

<p>A shell is more complex than it first looks. It’s a loop, yes, but it also has to parse what you typed, decide what it
means, and handle things like state, output, and redirection.</p>

<p>I kept Duck Shell pretty small and only implemented a few of these components. Nothing fancy. Just enough to understand
what’s happening.</p>

<blockquote>
  <p>💡 If you want to skip ahead and see the code, the GitHub repo is
here: <a href="https://github.com/brunoroberto/DuckShell/">DuckShell</a></p>
</blockquote>

<h2 id="the-repl">The REPL</h2>

<p>If you’re not familiar with what a REPL is, here’s a definition
from <a href="https://www.notion.so/8e0ab89ff7bd464ba71ac4740357718f?pvs=21">Wikipedia</a>.</p>

<p>At a high level, a shell is an interactive loop: it reads input, evaluates it, and prints a result.</p>

<p>But once I started implementing mine, I realised that the “evaluate” step hides most of the complexity.</p>

<p>In Duck Shell, the loop currently looks like this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DuckShell</span> <span class="kd">implements</span> <span class="nc">Shell</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">PROMPT_SYMBOL</span> <span class="o">=</span> <span class="s">"%s | quack &gt; "</span><span class="o">;</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Context</span> <span class="n">context</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ShellParser</span> <span class="n">shellParser</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CommandResolver</span> <span class="n">commandResolver</span><span class="o">;</span>

    <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">running</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">DuckShell</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nc">ShellParser</span> <span class="n">shellParser</span><span class="o">,</span> <span class="nc">CommandResolver</span> <span class="n">commandResolver</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">context</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">shellParser</span> <span class="o">=</span> <span class="n">shellParser</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">commandResolver</span> <span class="o">=</span> <span class="n">commandResolver</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">running</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="k">while</span> <span class="o">(</span><span class="n">running</span><span class="o">)</span> <span class="o">{</span>
            <span class="kt">var</span> <span class="n">rawInput</span> <span class="o">=</span> <span class="n">prompt</span><span class="o">();</span>
            <span class="kt">var</span> <span class="n">commandNode</span> <span class="o">=</span> <span class="n">shellParser</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">rawInput</span><span class="o">);</span>
            <span class="kt">var</span> <span class="n">command</span> <span class="o">=</span> <span class="n">commandResolver</span><span class="o">.</span><span class="na">resolve</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">context</span><span class="o">,</span> <span class="n">commandNode</span><span class="o">);</span>
            <span class="kt">var</span> <span class="n">executor</span> <span class="o">=</span> <span class="nc">CommandExecutorFactory</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">command</span><span class="o">);</span>
            <span class="kt">var</span> <span class="n">result</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">context</span><span class="o">,</span> <span class="n">command</span><span class="o">);</span>
            <span class="kt">var</span> <span class="n">resultOutput</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ResultOutput</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">context</span><span class="o">,</span> <span class="k">new</span> <span class="nc">ConsoleOutput</span><span class="o">())</span>
                    <span class="o">.</span><span class="na">withRedirections</span><span class="o">(</span><span class="n">commandNode</span><span class="o">.</span><span class="na">redirections</span><span class="o">());</span>
            <span class="n">resultOutput</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">prompt</span><span class="o">()</span> <span class="o">{</span>
        <span class="kt">var</span> <span class="n">prompt</span> <span class="o">=</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="no">PROMPT_SYMBOL</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">context</span><span class="o">.</span><span class="na">getCurrentWorkingDirectoryAsString</span><span class="o">());</span>
        <span class="k">return</span> <span class="no">IO</span><span class="o">.</span><span class="na">readln</span><span class="o">(</span><span class="n">prompt</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Here’s what happens, step by step:</p>

<ol>
  <li>The shell prints a prompt and reads raw text from the user</li>
  <li>The parser turns that text into a structured command node</li>
  <li>The resolver decides what the command refers to</li>
  <li>A matching executor is selected</li>
  <li>The command is executed</li>
  <li>The result is written, applying any output redirections</li>
</ol>

<p>Even though this is still a REPL, it’s already more than a simple “read, run, print” loop. The shell has to understand
what you typed, decide how to execute it, and only then decide where the output should go.</p>

<p>I also introduced a <code class="language-plaintext highlighter-rouge">Context</code> object to keep shell state in one place, like the current working directory. That becomes
important quickly, because a shell is stateful. Running <code class="language-plaintext highlighter-rouge">cd</code> should affect the next command, and the prompt depends on
that state.</p>

<h2 id="lexerparser">Lexer/Parser</h2>

<p>I went with a classic two-phase design here: Lexer then Parser.</p>

<h3 id="lexer">Lexer</h3>

<p>The lexer takes raw input and produces a list of Token records. Each token has a type, value, and position. It scans
character-by-character, skips whitespace, and classifies tokens based on the first character:</p>

<ul>
  <li>Words: accumulated until whitespace or an operator, with backslash escape support (so <code class="language-plaintext highlighter-rouge">hello\ world</code> becomes a single
token).</li>
  <li>Quoted strings: double quotes allow escapes (<code class="language-plaintext highlighter-rouge">\"</code>), single quotes are fully literal. Unterminated quotes throw
immediately.</li>
  <li>Operators: redirection operators (<code class="language-plaintext highlighter-rouge">&gt;</code>, <code class="language-plaintext highlighter-rouge">&gt;&gt;</code>, <code class="language-plaintext highlighter-rouge">2&gt;</code>, <code class="language-plaintext highlighter-rouge">2&gt;&gt;</code>) are recognised with one-character lookahead. I also tokenise
<code class="language-plaintext highlighter-rouge">|</code>, <code class="language-plaintext highlighter-rouge">&amp;&amp;</code>, <code class="language-plaintext highlighter-rouge">||</code>, and <code class="language-plaintext highlighter-rouge">;</code> already, even though the parser doesn’t consume them yet.</li>
</ul>

<p>These are all the token types the lexer can produce:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">enum</span> <span class="nc">TokenType</span> <span class="o">{</span>
	<span class="no">WORD</span><span class="o">,</span>               <span class="c1">// Unquoted text</span>
	<span class="no">STRING</span><span class="o">,</span>             <span class="c1">// Quoted text (single or double)</span>
	<span class="no">PIPE</span><span class="o">,</span>               <span class="c1">// |</span>
	<span class="no">REDIRECT_OUT</span><span class="o">,</span>       <span class="c1">// &gt; or 1&gt;</span>
	<span class="no">REDIRECT_APPEND</span><span class="o">,</span>    <span class="c1">// &gt;&gt;</span>
	<span class="no">REDIRECT_IN</span><span class="o">,</span>        <span class="c1">// &lt;</span>
	<span class="no">STDERR_OUT</span><span class="o">,</span>         <span class="c1">// 2&gt;</span>
	<span class="no">STDERR_OUT_APPEND</span><span class="o">,</span>  <span class="c1">// 2&gt;&gt;</span>
	<span class="no">AND</span><span class="o">,</span>                <span class="c1">// &amp;&amp;</span>
	<span class="no">OR</span><span class="o">,</span>                 <span class="c1">// ||</span>
	<span class="no">SEMICOLON</span><span class="o">,</span>          <span class="c1">// ;</span>
	<span class="no">EOF</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="parser">Parser</h3>

<p>The DuckParser consumes the token stream and builds a <code class="language-plaintext highlighter-rouge">CommandNode</code>. I used Java records to keep the data structures
minimal:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="n">record</span> <span class="nf">Token</span><span class="o">(</span><span class="nc">TokenType</span> <span class="n">type</span><span class="o">,</span> <span class="nc">String</span> <span class="n">value</span><span class="o">,</span> <span class="kt">int</span> <span class="n">position</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">CommandNode</span><span class="o">(</span><span class="nc">String</span> <span class="n">command</span><span class="o">,</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">arguments</span><span class="o">,</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">RedirectionNode</span><span class="o">&gt;</span> <span class="n">redirections</span><span class="o">)</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">RedirectionNode</span><span class="o">(</span><span class="nc">RedirectionType</span> <span class="n">type</span><span class="o">,</span> <span class="nc">String</span> <span class="n">target</span><span class="o">)</span> <span class="o">{}</span>
</code></pre></div></div>

<p>The logic is pretty straightforward: grab the first word as the command, then loop. Words and strings become arguments.
Redirection operators consume the next token as a target. Anything unexpected is an error.</p>

<p>So for example, parsing <code class="language-plaintext highlighter-rouge">echo hello world &gt; output.txt</code> produces:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CommandNode</span><span class="o">(</span>
    <span class="n">command</span>     <span class="o">=</span> <span class="s">"echo"</span><span class="o">,</span>
    <span class="n">arguments</span>   <span class="o">=</span> <span class="o">[</span><span class="s">"hello"</span><span class="o">,</span> <span class="s">"world"</span><span class="o">],</span>
    <span class="n">redirections</span> <span class="o">=</span> <span class="o">[</span><span class="nc">RedirectionNode</span><span class="o">(</span><span class="no">STDOUT_OVERWRITE</span><span class="o">,</span> <span class="s">"output.txt"</span><span class="o">)]</span>
  <span class="o">)</span>
</code></pre></div></div>

<p>I kept this intentionally flat: one <code class="language-plaintext highlighter-rouge">CommandNode</code> per input, no nested AST. Pipes and command chaining are on the
roadmap. That’s why the lexer already recognises those tokens. I just haven’t wired them up in the parser yet.</p>

<h2 id="command-resolver--executor">Command Resolver &amp; Executor</h2>

<p>Once the parser gives me a <code class="language-plaintext highlighter-rouge">CommandNode</code>, I do three things: resolve the command, execute it, and handle the output. A
<code class="language-plaintext highlighter-rouge">Context</code> object ties everything together throughout this pipeline.</p>

<h3 id="context">Context</h3>

<p>The <code class="language-plaintext highlighter-rouge">Context</code> is the shared state passed through the whole flow, from resolution to execution to output. It holds three
things: the current working directory, an <code class="language-plaintext highlighter-rouge">OSPath</code> for finding executables, and a default output handler.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Context</span> <span class="o">{</span>
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ShellOutput</span> <span class="n">defaultOutput</span><span class="o">;</span>
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">OSPath</span> <span class="n">osPath</span><span class="o">;</span>
	<span class="kd">private</span> <span class="nc">Path</span> <span class="n">currentWorkingDirectory</span><span class="o">;</span>
	<span class="c1">// ...</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Most commands just read from it, but some mutate it. <code class="language-plaintext highlighter-rouge">cd</code>, for example, updates the current working directory. This way,
the rest of the shell always has an up-to-date view of where we are.</p>

<h3 id="resolving">Resolving</h3>

<p>The <code class="language-plaintext highlighter-rouge">CommandResolver</code> decides what to run. Built-in commands are matched first via a <code class="language-plaintext highlighter-rouge">CommandNames</code> enum. Things like
<code class="language-plaintext highlighter-rouge">echo</code>, <code class="language-plaintext highlighter-rouge">pwd</code>, <code class="language-plaintext highlighter-rouge">cd</code>, <code class="language-plaintext highlighter-rouge">type</code>, <code class="language-plaintext highlighter-rouge">exit</code>, <code class="language-plaintext highlighter-rouge">clear</code>, and <code class="language-plaintext highlighter-rouge">quack</code> (every shell needs a signature command). If nothing matches,
it tries to find the command as an external executable.</p>

<p>That’s where <code class="language-plaintext highlighter-rouge">OSPath</code> comes in. On startup, it reads the system’s <code class="language-plaintext highlighter-rouge">PATH</code> environment variable and splits it into a list
of directories. When asked to find a command, it walks through those directories in order and checks each one for a
matching file that is both a regular file and executable:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">Path</span> <span class="nf">findExecutableCommandPath</span><span class="o">(</span><span class="nc">String</span> <span class="n">commandName</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">var</span> <span class="n">currentPath</span> <span class="o">:</span> <span class="k">this</span><span class="o">.</span><span class="na">paths</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="kt">var</span> <span class="n">commandPath</span> <span class="o">=</span> <span class="n">currentPath</span><span class="o">.</span><span class="na">resolve</span><span class="o">(</span><span class="n">commandName</span><span class="o">);</span>
            <span class="k">if</span> <span class="o">(</span><span class="nc">Files</span><span class="o">.</span><span class="na">isRegularFile</span><span class="o">(</span><span class="n">commandPath</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="nc">Files</span><span class="o">.</span><span class="na">isExecutable</span><span class="o">(</span><span class="n">commandPath</span><span class="o">))</span> <span class="o">{</span>
                <span class="k">return</span> <span class="n">currentPath</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvalidPathException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// skip invalid paths and keep looking</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>First match wins, just like a real shell. If nothing is found, it falls back to <code class="language-plaintext highlighter-rouge">InvalidCmd</code>, which reports the error.</p>

<h3 id="executing">Executing</h3>

<p>Built-in commands are straightforward. Each implements a <code class="language-plaintext highlighter-rouge">ShellCommand</code> interface and returns a <code class="language-plaintext highlighter-rouge">Result</code>.</p>

<p>External commands are more interesting: I spin up a <code class="language-plaintext highlighter-rouge">ProcessBuilder</code>, set the working directory from the context, then
use a small thread pool to read stdout and stderr concurrently so neither stream blocks the other.</p>

<p>All commands return one of three result types:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="n">record</span> <span class="nf">SuccessCmdResult</span><span class="o">(</span><span class="nc">String</span> <span class="n">stdOut</span><span class="o">,</span> <span class="nc">String</span> <span class="n">stdErr</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Result</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">EmptyCmdResult</span><span class="o">()</span>                                <span class="kd">implements</span> <span class="nc">Result</span> <span class="o">{}</span>  <span class="c1">// cd, exit, etc.</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">ErrorCmdResult</span><span class="o">(</span><span class="nc">String</span> <span class="n">stdErr</span><span class="o">)</span>                   <span class="kd">implements</span> <span class="nc">Result</span> <span class="o">{}</span>
</code></pre></div></div>

<p>I like how clean this turned out: <code class="language-plaintext highlighter-rouge">SuccessCmdResult</code> for anything that produces output, <code class="language-plaintext highlighter-rouge">EmptyCmdResult</code> for
side-effect-only commands like <code class="language-plaintext highlighter-rouge">cd</code>, and <code class="language-plaintext highlighter-rouge">ErrorCmdResult</code> when something goes wrong.</p>

<h3 id="output--redirections">Output &amp; Redirections</h3>

<p>After execution, I need to decide where the result actually goes. That’s the job of <code class="language-plaintext highlighter-rouge">ResultOutput</code>. It checks whether
the <code class="language-plaintext highlighter-rouge">CommandNode</code> carried any redirections and routes accordingly:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">write</span><span class="o">(</span><span class="nc">Result</span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">hasRedirections</span><span class="o">())</span> <span class="o">{</span>
        <span class="kt">var</span> <span class="n">stdOutRedirect</span> <span class="o">=</span> <span class="n">getAnyRedirection</span><span class="o">(</span><span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">RedirectionType</span><span class="o">.</span><span class="na">STDOUT_OVERWRITE</span><span class="o">,</span> <span class="nc">RedirectionType</span><span class="o">.</span><span class="na">STDOUT_APPEND</span><span class="o">));</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">stdOutRedirect</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">new</span> <span class="nf">FileOutput</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">stdOutRedirect</span><span class="o">).</span><span class="na">write</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span> <span class="c1">// stdout redirected, nothing goes to the console</span>
        <span class="o">}</span>
        <span class="kt">var</span> <span class="n">errorRedirect</span> <span class="o">=</span> <span class="n">getAnyRedirection</span><span class="o">(</span><span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">RedirectionType</span><span class="o">.</span><span class="na">STDERR</span><span class="o">,</span> <span class="nc">RedirectionType</span><span class="o">.</span><span class="na">STDERR_APPEND</span><span class="o">));</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">errorRedirect</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">new</span> <span class="nf">FileOutput</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">errorRedirect</span><span class="o">).</span><span class="na">write</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
            <span class="c1">// falls through, stderr goes to file, but stdout still prints</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="k">this</span><span class="o">.</span><span class="na">consoleOutput</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>There’s a subtle difference I’m happy with here: stdout redirections fully replace console output (if you do
<code class="language-plaintext highlighter-rouge">echo hello &gt; file.txt</code>, nothing prints), but stderr redirections just capture the error stream while still letting
stdout through to the terminal.</p>

<p>When writing to a file, <code class="language-plaintext highlighter-rouge">FileOutput</code> resolves the target path relative to the current working directory from the
context, picks the right content (stdout or stderr based on the redirection type), and writes it out. It either
truncates or appends depending on whether it’s <code class="language-plaintext highlighter-rouge">&gt;</code> or <code class="language-plaintext highlighter-rouge">&gt;&gt;</code>:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">createOutputFile</span><span class="o">(</span><span class="nc">Path</span> <span class="n">target</span><span class="o">,</span> <span class="nc">String</span> <span class="n">content</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">append</span><span class="o">)</span> <span class="o">{</span>
	<span class="kt">var</span> <span class="n">options</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">StandardOpenOption</span><span class="o">.</span><span class="na">CREATE</span><span class="o">,</span>
		<span class="n">append</span> <span class="o">?</span> <span class="nc">StandardOpenOption</span><span class="o">.</span><span class="na">APPEND</span> <span class="o">:</span> <span class="nc">StandardOpenOption</span><span class="o">.</span><span class="na">TRUNCATE_EXISTING</span>
	<span class="o">);</span>
	<span class="nc">Files</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">content</span><span class="o">.</span><span class="na">getBytes</span><span class="o">(</span><span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">),</span>
	<span class="n">options</span><span class="o">.</span><span class="na">toArray</span><span class="o">(</span><span class="k">new</span> <span class="nc">StandardOpenOption</span><span class="o">[</span><span class="mi">0</span><span class="o">]));</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The key design choice here is that redirections are applied after the command runs. The commands themselves don’t know
or care whether their output ends up in a file or the terminal. That separation keeps things clean.</p>

<h2 id="whats-next">What’s Next</h2>

<p>There’s still a lot I want to add. The lexer already recognises pipes, <code class="language-plaintext highlighter-rouge">&amp;&amp;</code>, <code class="language-plaintext highlighter-rouge">||</code>, and <code class="language-plaintext highlighter-rouge">;</code>, so the next step is wiring
those up in the parser. That means moving from a flat <code class="language-plaintext highlighter-rouge">CommandNode</code> to a real AST that can represent pipelines and
command chains.</p>

<p>I’m also looking forward to tackling environment variables, glob expansion, and maybe even basic scripting support down
the line.</p>

<p>If you want to follow along or check out the code, the repo is <a href="https://github.com/brunoroberto/DuckShell/">here</a>. And if you’ve ever been curious about how your
terminal actually works under the hood, I’d really recommend trying to build one yourself. Even a minimal shell teaches
you more than you’d expect.</p>]]></content><author><name>brunoroberto</name></author><category term="blog" /><category term="building_my_own" /><summary type="html"><![CDATA[Have you ever wondered how a shell works? I've built one]]></summary></entry></feed>