<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Colas.Nahaboo.net - bash</title>
    <subtitle>Colas Nahaboo personal site, with discussions about programming code, web and computing topics, surfing and SUPing, and various musings.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://colas.nahaboo.net/tags/bash/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://colas.nahaboo.net"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-08-10T00:00:00+00:00</updated>
    <id>https://colas.nahaboo.net/tags/bash/atom.xml</id>
    <entry xml:lang="en">
        <title>follow-symlinks: tracing the whole link chain</title>
        <published>2026-08-10T00:00:00+00:00</published>
        <updated>2026-08-10T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/follow-symlinks-tracing-the-link-chain/"/>
        <id>https://colas.nahaboo.net/code/follow-symlinks-tracing-the-link-chain/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/follow-symlinks-tracing-the-link-chain/">&lt;p&gt;I just added &lt;strong&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib/blob/master/bin/follow-symlinks&quot;&gt;follow-symlinks&lt;/a&gt;&lt;/strong&gt; to my &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib&lt;/a&gt; collection. It is a small bash script that traces and expands all the symlinks of a file, recursively, and shows you the mount points and the dangling links found along the way.&lt;/p&gt;
&lt;p&gt;I made it because there are no existing linux utilities that show both the mount points and the symbolic link traversed to get to the file.&lt;/p&gt;
&lt;h2 id=&quot;what-it-shows&quot;&gt;What it shows&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-shows&quot; aria-label=&quot;Anchor link for: what-it-shows&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Give it the path of a file or directory and it walks the chain, printing each hop:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;## /tmp/symtest/chain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Mount: /tmp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;#39;/tmp/symtest/chain&amp;#39; -&amp;gt; &amp;#39;link-to-real&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  =&amp;gt; /tmp/symtest/link-to-real&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output is made for human consumption:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-&amp;gt;&lt;/code&gt; the file is a symbolic link to&lt;/li&gt;
&lt;li&gt;&lt;code&gt;=&amp;gt;&lt;/code&gt; the final target&lt;/li&gt;
&lt;li&gt;&lt;code&gt;=&lt;/code&gt; the path after normalization (resolving &lt;code&gt;/..&lt;/code&gt;, &lt;code&gt;/./&lt;/code&gt;, &lt;code&gt;//&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Mount:&lt;/code&gt; a filesystem boundary — printed when the mount point changes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*** File not found:&lt;/code&gt; a dangling link&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also resolves the relative links and the &lt;code&gt;..&lt;/code&gt; segments for you, so what you read is what the kernel will actually open, not the spelling on disk.&lt;/p&gt;
&lt;h2 id=&quot;the-dangling-links&quot;&gt;The dangling links&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-dangling-links&quot; aria-label=&quot;Anchor link for: the-dangling-links&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dangling (or broken) links get a useful diagnostic to know the missing link.&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;## /tmp/symtest/dangling&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Mount: /tmp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;#39;/tmp/symtest/dangling&amp;#39; -&amp;gt; &amp;#39;missing-file&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  *** File not found: /tmp/symtest/missing-file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      Because in missing directory: /tmp/symtest/missing-file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;the-mount-points&quot;&gt;The mount points&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-mount-points&quot; aria-label=&quot;Anchor link for: the-mount-points&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Links that cross a mount point are easy to miss — the file is the same name, but it lives on another filesystem, with its own space and speed characteristics:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;## /tmp/symtest/link-to-etc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Mount: /tmp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;#39;/tmp/symtest/link-to-etc&amp;#39; -&amp;gt; &amp;#39;/etc&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Mount: /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  =&amp;gt; /etc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;why-i-like-it&quot;&gt;Why I like it&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-i-like-it&quot; aria-label=&quot;Anchor link for: why-i-like-it&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One shot, whole chain&lt;/strong&gt;: it prints everything in one pass instead of me chaining &lt;code&gt;ls -l&lt;/code&gt; and &lt;code&gt;readlink&lt;/code&gt; by hand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A reason, not a symptom&lt;/strong&gt;: for broken links it tells me the missing directory, not just &quot;file not found&quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pure bash, no dependencies&lt;/strong&gt; — it only uses &lt;code&gt;readlink&lt;/code&gt;, &lt;code&gt;stat&lt;/code&gt; and &lt;code&gt;realpath&lt;/code&gt;, all of which are everywhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See it and the rest of my bash scripts at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>passgen: a password generator with a memory</title>
        <published>2026-08-05T00:00:00+00:00</published>
        <updated>2026-08-05T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/passgen-a-password-generator-with-a-memory/"/>
        <id>https://colas.nahaboo.net/code/passgen-a-password-generator-with-a-memory/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/passgen-a-password-generator-with-a-memory/">&lt;p&gt;I just added &lt;strong&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib/blob/master/bin/passgen&quot;&gt;passgen&lt;/a&gt;&lt;/strong&gt;, a password generator, to my &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib&lt;/a&gt; collection. It is a tool that lives in your terminal, generates a password, copies it to your clipboard, and keeps a private log of what it generated and for what.&lt;/p&gt;
&lt;p&gt;Why a log, you may ask. Because the biggest password problem is not generating strong ones, it is remembering which password goes where. My log solves it in the laziest possible way: I search it when I need to know which password I used for a given site.&lt;/p&gt;
&lt;h2 id=&quot;what-it-does&quot;&gt;What it does&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-does&quot; aria-label=&quot;Anchor link for: what-it-does&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;passgen&lt;/code&gt; generates a password of length 24 by default, made of lowercase letters, uppercase, digits, and a few special characters. It is careful about the details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It starts with a lowercase letter, so the password is a valid identifier anywhere.&lt;/li&gt;
&lt;li&gt;It deliberately avoids the zero character, to prevent confusion with the letter O.&lt;/li&gt;
&lt;li&gt;The special characters are limited to &lt;code&gt;-%=+_^/~?.&lt;/code&gt; — the ones that survive a &lt;strong&gt;double-click&lt;/strong&gt; to select the whole password, which is the way I copy things from a terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then it copies the password to your clipboard, on Wayland or X11 alike. No need to select it yourself.&lt;/p&gt;
&lt;h2 id=&quot;the-log&quot;&gt;The log&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-log&quot; aria-label=&quot;Anchor link for: the-log&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every generated password is appended to &lt;code&gt;~/.local/state/passgen/log&lt;/code&gt;, with a date and an optional note. I use it this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;passgen -l&lt;/code&gt; lists the last passwords generated, so I can find the one for a given site.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;passgen 32 facebook&lt;/code&gt; generates a 32-character password and logs it as the &quot;facebook&quot; one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The log is chmod&#39;ed to be readable only by me, and I keep it in sync and backed up like the rest of my home directory. It replaced my password manager for everything but shared secrets.&lt;/p&gt;
&lt;h2 id=&quot;checking-the-strength&quot;&gt;Checking the strength&lt;a class=&quot;zola-anchor&quot; href=&quot;#checking-the-strength&quot; aria-label=&quot;Anchor link for: checking-the-strength&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;passgen -s 24&lt;/code&gt; prints the strength of a password of that length in number of combinations, which for 24 characters is about 10^42 — comfortably future-proof even against quantum computers. The default length of 24 is chosen for exactly that reason.&lt;/p&gt;
&lt;h2 id=&quot;why-i-like-it&quot;&gt;Why I like it&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-i-like-it&quot; aria-label=&quot;Anchor link for: why-i-like-it&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One tool, one habit&lt;/strong&gt;: no account, no sync, no database, nothing to learn. Generate, paste, done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The password is in my control&lt;/strong&gt;, stored in a plain file I can grep, rsync, and back up with the rest of my life.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Useful in a script&lt;/strong&gt;: pipe it, adapt the length, add a note. It composes like any other command-line tool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See it and the rest of my bash functions at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib&lt;/a&gt;.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>x-workspace-switcher: a menu to switch and rename X11 workspaces</title>
        <published>2026-08-04T00:00:00+00:00</published>
        <updated>2026-08-04T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/x-workspace-switcher/"/>
        <id>https://colas.nahaboo.net/code/x-workspace-switcher/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/x-workspace-switcher/">&lt;img src=&#39;x-workspace-switcher.png&#39; align=right width=300&gt;
&lt;p&gt;I switch workspaces dozens of times a day — browser here, terminal there, editor somewhere else — and I could not find a workspace switcher that was convenient. Either they were tiny unreadable icons in a panel, or huge fancy views. I just wanted a readable list of names, plus an easy way to rename the workspace via the programs that run in them.&lt;/p&gt;
&lt;p&gt;So I added &lt;strong&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;x-workspace-switcher&lt;/a&gt;&lt;/strong&gt; to my &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib&lt;/a&gt;, a single bash script in &lt;code&gt;bin/&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;switching&quot;&gt;Switching&lt;a class=&quot;zola-anchor&quot; href=&quot;#switching&quot; aria-label=&quot;Anchor link for: switching&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Run it with no argument and a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/davatorium/rofi&quot;&gt;rofi&lt;/a&gt; menu pops up, listing each workspace with the classes of the windows currently on it:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1  ws_1 | firefox, discord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2  ws_2 | kitty, vim&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I can see at a glance where everything is, instead of remembering. The current workspace is pre-selected. Move with the arrows or incremental search, hit Enter — or just hit the function key of the workspace you want: F6 goes straight to workspace 6.&lt;/p&gt;
&lt;p&gt;Note: I use &lt;code&gt;F6&lt;/code&gt; to go to workspace 6 instead of just &lt;code&gt;6&lt;/code&gt; to not mess with typing 6 during an incremental search. Also I use &lt;code&gt;Super-F6&lt;/code&gt; as a keyboard shortcut to go to Workspace 6, and using functions keys allow for 12 workspaces (or 14 on my &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://nzxt.com/en-intl/collections/gaming-keyboards&quot;&gt;NZXT keyboard&lt;/a&gt; :-)&lt;/p&gt;
&lt;h2 id=&quot;renaming&quot;&gt;Renaming&lt;a class=&quot;zola-anchor&quot; href=&quot;#renaming&quot; aria-label=&quot;Anchor link for: renaming&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;x-workspace-switcher new-name&lt;/code&gt; renames the current workspace.&lt;/p&gt;
&lt;p&gt;That turned out to be the interesting part: there is no standard way to rename a workspace on X11. For instance, I have a script that I run in a git repository that sets up my development environment  for it (emacs, kittyies, opencode, ...) and now also name the workspace with the repository name.&lt;/p&gt;
&lt;h2 id=&quot;why-renaming-needs-per-wm-code&quot;&gt;Why renaming needs per-WM code&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-renaming-needs-per-wm-code&quot; aria-label=&quot;Anchor link for: why-renaming-needs-per-wm-code&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each window manager does it its own way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Xfce: &lt;code&gt;xfconf-query&lt;/code&gt; on &lt;code&gt;/general/workspace_names&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;i3: &lt;code&gt;i3-msg &quot;rename workspace to ...&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;bspwm: &lt;code&gt;bspc desktop -n&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GNOME: &lt;code&gt;gsettings&lt;/code&gt; on &lt;code&gt;workspace-names&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;KDE: &lt;code&gt;kwriteconfig&lt;/code&gt; plus a &lt;code&gt;qdbus&lt;/code&gt; reconfigure&lt;/li&gt;
&lt;li&gt;anything else: a python-xlib fallback that rewrites the &lt;code&gt;_NET_DESKTOP_NAMES&lt;/code&gt; property directly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Detection is via &lt;code&gt;wmctrl -m&lt;/code&gt;, helped by &lt;code&gt;XDG_CURRENT_DESKTOP&lt;/code&gt; when the WM name is ambiguous.&lt;/p&gt;
&lt;h2 id=&quot;the-wayland-question&quot;&gt;The Wayland question&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-wayland-question&quot; aria-label=&quot;Anchor link for: the-wayland-question&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As always with Wayland: alas, no. The core protocol has no workspace support, and each compositor invents its own incompatible API. My switcher is X11 only — save your sanity, use X11. :-)&lt;/p&gt;
&lt;h2 id=&quot;installation-usage-and-more-info&quot;&gt;Installation, Usage, and more info&lt;a class=&quot;zola-anchor&quot; href=&quot;#installation-usage-and-more-info&quot; aria-label=&quot;Anchor link for: installation-usage-and-more-info&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib repository&lt;/a&gt;, the script is &lt;code&gt;bin/x-workspace-switcher&lt;/code&gt;. It needs &lt;code&gt;rofi&lt;/code&gt;; renaming on some WMs needs extra packages (&lt;code&gt;i3-wm bspwm plasma-workspace python3-xlib&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Just copy the script in your PATH (&lt;code&gt;/usr/local/bin&lt;/code&gt;, &lt;code&gt;~/.local/bin/&lt;/code&gt;, ...), and bind it to a keyboard shortcut for convenience. For instance I bound it to Super-W (aka Windows-W) since I bound all my window and workspace management function to Super shortcuts.&lt;/p&gt;
&lt;h2 id=&quot;license&quot;&gt;License&lt;a class=&quot;zola-anchor&quot; href=&quot;#license&quot; aria-label=&quot;Anchor link for: license&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;© Colas Nahaboo, 2026. MIT License. Do what you want with it.
&lt;img src=&quot;https://img.shields.io/badge/Co--Designed%20with-AI-blueviolet?style=flat-square&amp;amp;logo=openai&quot; alt=&quot;Co-Designed with AI&quot; /&gt;.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>mailpixtracker: a lightweight bash CGI to track emails</title>
        <published>2026-08-01T00:00:00+00:00</published>
        <updated>2026-08-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/mailpixtracker-a-bash-cgi-to-track-emails/"/>
        <id>https://colas.nahaboo.net/code/mailpixtracker-a-bash-cgi-to-track-emails/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/mailpixtracker-a-bash-cgi-to-track-emails/">&lt;p&gt;&lt;img src=&quot;mpt3-576.png&quot; width=300 align=right&gt;When I send an email, did the recipient read it? So I wrote &lt;strong&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/mailpixtracker&quot;&gt;mailpixtracker&lt;/a&gt;&lt;/strong&gt;, a small self-hosted CGI script that tracks when your emails are opened, via the classic invisible 1x1 pixel trick.&lt;/p&gt;
&lt;p&gt;Think of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.getmailtracker.com/&quot;&gt;MailTracker&lt;/a&gt; or &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://mailtrack.email/&quot;&gt;MailTrack&lt;/a&gt;, but without all the marketing machinery: no account on a third-party server, no spyware concerns, no &quot;business intelligence&quot; dashboards. Just three bash scripts on your own web server.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-it-works&quot; aria-label=&quot;Anchor link for: how-it-works&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You create a new tracker with an optional name on your mailpixtracker page. It gives you a tiny HTML snippet to paste in your email such as:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot; &gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #1E66F5;&quot;&gt;img&lt;/span&gt;&lt;span style=&quot;color: #DF8E1D;&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #40A02B;&quot;&gt;&amp;#39;https://my.mailpixtracker.org/_/12-I5rHJCFap&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #DF8E1D;&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #40A02B;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #DF8E1D;&quot;&gt; height&lt;/span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #40A02B;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When someone opens your email and their mailer loads images, your server logs the request: date, IP, hostname, user-agent. No data ever leaves your server.&lt;/p&gt;
&lt;h2 id=&quot;features&quot;&gt;Features&lt;a class=&quot;zola-anchor&quot; href=&quot;#features&quot; aria-label=&quot;Anchor link for: features&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No accounts&lt;/strong&gt;: The URL &lt;em&gt;is&lt;/em&gt; the authentication, via &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.w3.org/TR/capability-urls/&quot;&gt;Capability URLs&lt;/a&gt;. The admin URL is your unguessable &quot;password&quot; — share it with nobody and you are safe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plain files&lt;/strong&gt;: Logs are tab-separated text, one line per open. No database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Colored logs&lt;/strong&gt;: Each distinct IP gets its own &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://programmingdesignsystems.com/color/perceptually-uniform-color-spaces/&quot;&gt;perceptually-spaced color&lt;/a&gt;, so you can spot repeat readers at a glance. Your own opens (while composing the email) show in dim grey, so they do not pollute the picture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New entries highlighted&lt;/strong&gt;: Entries added since your last visit get a yellow marker.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notes field&lt;/strong&gt;: Each tracker has an editable name and notes — handy to remember what you sent, to whom, and when.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guest accounts&lt;/strong&gt;: Create isolated spaces for different purposes (work, family, surfing...) with admin (you) accounts, or guest accounts that you can just give to others with a link. Non-admin guests only see their own trackers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geolocalization&lt;/strong&gt; (optional): With the MaxMind databases installed, logs show the organization and country of each reader.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;caveats&quot;&gt;Caveats&lt;a class=&quot;zola-anchor&quot; href=&quot;#caveats&quot; aria-label=&quot;Anchor link for: caveats&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Email tracking is not foolproof. Most mailers now prompt before loading images, so people can read your email without ever triggering the tracker. Mailpixtracker will not tell you everything — but when it does fire, it is accurate, and the logs are all yours.&lt;/p&gt;
&lt;p&gt;Also note that it is a tool for personal curiosity, not for marketing analytics: it has no click tracking, no A/B testing, no &quot;campaigns&quot;. If you want that, there are plenty of SaaS products; this one is the lazy, self-hosted, private alternative.&lt;/p&gt;
&lt;h2 id=&quot;install&quot;&gt;Install&lt;a class=&quot;zola-anchor&quot; href=&quot;#install&quot; aria-label=&quot;Anchor link for: install&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For instructions, see the code is at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/mailpixtracker&quot;&gt;github.com/ColasNahaboo/mailpixtracker&lt;/a&gt;, it is multi-licensed (MIT, Apache 2.0, MPL 2.0, LGPL v3, GPL v3) — pick whichever suits your project.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>filewebx: a simple, private, web file exchange</title>
        <published>2026-07-30T00:00:00+00:00</published>
        <updated>2026-07-30T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/filewebx-simple-private-web-file-exchange/"/>
        <id>https://colas.nahaboo.net/code/filewebx-simple-private-web-file-exchange/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/filewebx-simple-private-web-file-exchange/">&lt;p&gt;&lt;img src=&quot;filewebx-logo-240x200.png&quot; align=right&gt;I often need to send files to people — photos, documents, archives — and I got tired of the usual suspects: WeTransfer limits you, SwissTransfer is great but you depend on a third party, and all of them impose size caps, retention limits, and account management overhead.&lt;/p&gt;
&lt;p&gt;So I wrote &lt;strong&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/filewebx&quot;&gt;filewebx&lt;/a&gt;&lt;/strong&gt;. It is a self-hosted CGI Bash system for private file exchange, using &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.w3.org/TR/capability-urls/&quot;&gt;Capability URLs&lt;/a&gt;: you upload a file, get back a non-guessable link, share it. No logins, no passwords, no accounts.&lt;/p&gt;
&lt;p&gt;It is your own private WeTransfer / MASV / Smash / SwissTransfer, on your own server.&lt;/p&gt;
&lt;h2 id=&quot;what-it-does&quot;&gt;What it does&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-does&quot; aria-label=&quot;Anchor link for: what-it-does&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No accounts needed&lt;/strong&gt;: The URL &lt;em&gt;is&lt;/em&gt; the authentication. Your secret admin URL (e.g: &lt;code&gt;https://your.site/yJDdYNEXmB&lt;/code&gt;) is your dashboard. Share it with nobody, and it is secure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guest accounts&lt;/strong&gt;: Create isolated spaces for other people by just giving them a link. They can upload and get their own shareable links without seeing yours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-expiry&lt;/strong&gt;: Files expire after a configurable delay (100 days by default). A daily cron job cleans them up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nice interface&lt;/strong&gt; with for instance an upload progress bar, easy copying of the url, nice colored logs that work without JavaScript frameworks; Just plain HTML + CSS + JS served by a Bash CGI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slug URLs&lt;/strong&gt;: Download links are readable: &lt;code&gt;https://your.site/_/OLw6bZrh65lj/my-photo.jpg&lt;/code&gt; instead of raw url-encoded gibberish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geolocalized logs&lt;/strong&gt;: See where downloads come from, with IP colors assigned via &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://medium.com/@winwardo/simple-non-repeating-colour-generation-6efc995832b8&quot;&gt;golden-angle hue sampling&lt;/a&gt; to spot patterns at a glance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quotas&lt;/strong&gt;: Reserve free disk space. Per-guest quotas available.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-it-works&quot; aria-label=&quot;Anchor link for: how-it-works&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Three bash scripts, one web server, a single directory for data. Uploads go to &lt;code&gt;data/&lt;/code&gt;, metadata is stored as bash associative arrays via &lt;code&gt;declare -p&lt;/code&gt; (from my &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib&lt;/a&gt;). The download script (&lt;code&gt;_&lt;/code&gt;) logs access and serves the file. The admin script handles the UI, guest management, and cleaning.&lt;/p&gt;
&lt;p&gt;The whole thing is plain files — no database, no Docker, no snap, flatpak, no dependencies beyond &lt;code&gt;iconv&lt;/code&gt; and a CGI-capable web server (tested with Apache).&lt;/p&gt;
&lt;h2 id=&quot;why-bash&quot;&gt;Why bash?&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-bash&quot; aria-label=&quot;Anchor link for: why-bash&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Because for a CGI system that runs once per request, startup time does not matter, and bash makes deployment trivial: copy three files, rename one to your admin password, done. No compilation, no npm install, no virtualenv. No dependencies on library that can break at any time in the future. It works now, it will still work in twenty years and more. Also, the bash associative array metadata format (&lt;code&gt;declare -p&lt;/code&gt;) is surprisingly practical as simple database — it is both human-readable and machine-parseable without a custom serializer.&lt;/p&gt;
&lt;h2 id=&quot;guest-accounts&quot;&gt;Guest accounts&lt;a class=&quot;zola-anchor&quot; href=&quot;#guest-accounts&quot; aria-label=&quot;Anchor link for: guest-accounts&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can create guest accounts from the admin tab. A guest only sees its own files. Mark a guest as &quot;admin&quot; to get a link back to the main admin account — useful for creating separate namespaces for different audiences (work, family, surfing buddies).&lt;/p&gt;
&lt;p&gt;Non-admin guests cannot access the main account, see other guests, or create new guests. Perfect for giving to people who just need to send you a file.&lt;/p&gt;
&lt;h2 id=&quot;deploy&quot;&gt;Deploy&lt;a class=&quot;zola-anchor&quot; href=&quot;#deploy&quot; aria-label=&quot;Anchor link for: deploy&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create a directory on your server with &lt;code&gt;data/&lt;/code&gt; and &lt;code&gt;cgi/&lt;/code&gt; subdirectories.&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;filewebx&lt;/code&gt; as your admin password name into &lt;code&gt;cgi/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;filewebdl&lt;/code&gt; as &lt;code&gt;_&lt;/code&gt; into &lt;code&gt;cgi/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copy &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/cgibashopts&quot;&gt;cgibashopts&lt;/a&gt; into &lt;code&gt;cgi/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set up a virtual host, add a cron job for daily cleanup.&lt;/li&gt;
&lt;li&gt;Done.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/filewebx&quot;&gt;README&lt;/a&gt; for full install instructions and Apache config samples. Configuration is done via a single bash-syntax file: set &lt;code&gt;validity&lt;/code&gt;, &lt;code&gt;passlen&lt;/code&gt;, &lt;code&gt;freequota&lt;/code&gt;, &lt;code&gt;guestquotas&lt;/code&gt;, and a few more.&lt;/p&gt;
&lt;h2 id=&quot;what-i-like-about-it&quot;&gt;What I like about it&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-like-about-it&quot; aria-label=&quot;Anchor link for: what-i-like-about-it&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It is the simplest thing that works: data is plain files, so I can &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;rsync&lt;/code&gt; everything without special tools.&lt;/li&gt;
&lt;li&gt;Capability URLs mean I never have to manage a user database. The random tokens are generated from &lt;code&gt;/dev/urandom&lt;/code&gt; — 12 characters give about 10^14 combinations.&lt;/li&gt;
&lt;li&gt;The log coloring with perceptually-spaced HSL hues makes browsing download history genuinely pleasant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code is at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/filewebx&quot;&gt;github.com/ColasNahaboo/filewebx&lt;/a&gt;, MIT license. Contributions, issues, and ideas welcome.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Foswiki update script</title>
        <published>2026-03-26T00:00:00+00:00</published>
        <updated>2026-03-26T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/foswiki-update-script/"/>
        <id>https://colas.nahaboo.net/code/foswiki-update-script/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/foswiki-update-script/">&lt;p&gt;Although I have &lt;a href=&quot;/blog/a-new-version-of-the-site-for-2026-in-zola&quot;&gt;migrated my public site to zola&lt;/a&gt; which was on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://foswiki.org&quot;&gt;Foswiki&lt;/a&gt;, I have decided for now to keep my private family wiki on Foswiki for now.&lt;/p&gt;
&lt;p&gt;So I needed to upgrade it to 2.1.11, it was still in 2.1.6, and since the upgrade is not exactly intuitive, I made a bash script to automatise the upgrade.&lt;/p&gt;
&lt;p&gt;You can find it &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/foswiki-upgrade&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See also its publication on the Foswiki site, with a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://foswiki.org/Support/BestPracticeTip35#comment1.1774606339&quot;&gt;reminder of the standard upgrade method&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-the-script-works&quot;&gt;How the Script Works&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-the-script-works&quot; aria-label=&quot;Anchor link for: how-the-script-works&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This script supposes a modern version of Foswiki (v2+): it blindly overwrites the files that should not have been modified, so when customizing your wiki be sure to never directly modify distributed files.&lt;/p&gt;
&lt;p&gt;The script however will not overwrite blindly any topic file that has been edited via Foswiki itself, by checking the author metadata. It will perform a 3-way merge, as automated as possible.&lt;/p&gt;
&lt;p&gt;It also works only on a local copy of the site. You will have to download yourself a copy of your site, upgrade, and then re-upload it in place. This keeps the script simpler and safer.&lt;/p&gt;
&lt;p&gt;It works on linux, but should work in linux-like layers on top of other OSes, such as cygwin, WSL, Homebrew, ...&lt;/p&gt;
&lt;h2 id=&quot;why-staying-on-foswiki&quot;&gt;Why staying on Foswiki?&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-staying-on-foswiki&quot; aria-label=&quot;Anchor link for: why-staying-on-foswiki&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My private wiki is used by me, but also my wife, who is not really fluent in wiki or markdown  editing, so an excellent WYSIWYG is a must. And personnaly, I hate databases and strive for stability and simple architecture. Foswiki has all these qualities... and I would have a lot of content to convert.&lt;/p&gt;
&lt;p&gt;But I will probably switch to an open source markdown-based wiki with a very good WYSIWYG editor and flat file storage, and easy to maintain. For now I see 3 potential candidates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://silverbullet.md/&quot;&gt;SilverBullet&lt;/a&gt;, still evolving but very promising&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://otterwiki.com/&quot;&gt;OtterWiki&lt;/a&gt;, minimal but seems powerful enough&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://js.wiki/&quot;&gt;Wiki.js&lt;/a&gt; the current leader, but I will wait for the v3 as the v2 to v3 migration do not seem automatic, and the development &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.reddit.com/r/selfhosted/comments/1mebp67/is_wikijs_3_dead/&quot;&gt;seems to have slowed greatly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Suntimes, a small shell utility</title>
        <published>2022-03-03T00:00:00+00:00</published>
        <updated>2022-03-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/suntimes-a-small-shell-utility/"/>
        <id>https://colas.nahaboo.net/code/suntimes-a-small-shell-utility/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/suntimes-a-small-shell-utility/">&lt;p&gt;I just made a small command-line utility to display the sunrise and sunset times at a location. It can be quite useful in shell scripts.&lt;/p&gt;
&lt;p&gt;It is a minimal Go wrapper around the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/nathan-osman/go-sunrise&quot;&gt;nathan-osman/go-sunrise library&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can find it at my repository: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/suntimes&quot;&gt;GitHub - ColasNahaboo/suntimes&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My bash library on Github</title>
        <published>2022-02-05T00:00:00+00:00</published>
        <updated>2022-02-05T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/my-bash-library-on-github/"/>
        <id>https://colas.nahaboo.net/code/my-bash-library-on-github/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/my-bash-library-on-github/">&lt;p&gt;I have started to publish on Github, at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/colas-bash-lib&quot;&gt;colas-bash-lib&lt;/a&gt;, my collection of various &lt;strong&gt;bash functions&lt;/strong&gt; I reuse often in my bash scripts.&lt;/p&gt;
&lt;p&gt;Fell free to copy and use in any of your projects or compilation of bash tools.&lt;/p&gt;
&lt;p&gt;I have tried to make them the &lt;strong&gt;fastest&lt;/strong&gt; possible, by avoiding forking sub-shells or external commands, and
benchmarking extensively to compare the possible way of coding them. Of course, I will gladly accept suggestions or code to make them faster.
But it means that error checking is often terse and minimal, and readability of the code was not a priority.&lt;/p&gt;
&lt;p&gt;I have a kind of «anti-npm» approach, in that I &lt;strong&gt;copy&lt;/strong&gt; these functions into my scripts rather that using hem as a true external library that I would load at runtime. It thus avoid installation issues, and the ependency problems that may arise from automated upgrades.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Bash lessons learned with AoC 2021</title>
        <published>2022-01-11T00:00:00+00:00</published>
        <updated>2022-01-11T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/bash-lessons-learned-with-aoc-2021/"/>
        <id>https://colas.nahaboo.net/code/bash-lessons-learned-with-aoc-2021/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/bash-lessons-learned-with-aoc-2021/">&lt;p&gt;I completed all the exercises of the Advent of Code 2021 in bash! (see &lt;a href=&quot;/code/advent-of-code-2021&quot;&gt;my previous post&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;You can find my solutions with comments in my GitHub repository at https://github.com/ColasNahaboo/advent-of-code-my-solutions/tree/main/bash/2021&lt;/p&gt;
&lt;p&gt;I must say I &quot;cheated&quot; a bit. My  solution for the Day 24 was too slow in bash, and as I was a bit out of  steam, I did not try to find a smart algorithm. I just noticed that I  solved it by building a bash arithmetic expression (of 1 million  characters...) that I then evaluated in bash, and since its syntax was  exactly the one for C... I just made the bash script compile the bash  expression in C and execute it, using the C compiler as a bash  arithmetic just-in-time compiler :-)&lt;/p&gt;
&lt;p&gt;I just discovered AoC this year, and I am impressed. Challenging, fun,  and a great way to progress. I am going to do the previous years, too,  but in &quot;real&quot; languages this time. At least ones with data structures... I will start with Go.&lt;/p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;What I learned:&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-learned&quot; aria-label=&quot;Anchor link for: what-i-learned&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Coding in bash is not so bad, even if it can border on insanity at times :-).&lt;/li&gt;
&lt;li&gt;Modern bash features are often overlooked but very useful.&lt;/li&gt;
&lt;li&gt;Passing shellcheck should be a mandatory goal for each bash programmer. I resented it at first since I thought it was adding unecessary syntaxic sugar to my code until I realized that it was a symptom that my coding style was the problem.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;[[...]]&lt;/code&gt; for strings and &lt;code&gt;((...))&lt;/code&gt; for integers other any of the legacy constructs like &lt;code&gt;[...]&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, etc... The code is then much cleaner and safer (and a tad faster), as you do not have to quote as much. E.g: &lt;code&gt;[[ -z foo ]]&lt;/code&gt; instead of &lt;code&gt;[ -z &quot;$foo&quot; ]&lt;/code&gt;, or even &lt;code&gt;((i=j))&lt;/code&gt; instead of &lt;code&gt;i=&quot;$j&quot;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;but&lt;/strong&gt;, this makes traditional debugging with &lt;code&gt;set -x&lt;/code&gt; less useful as the values of variables are not displayed anymore. E.g if j is 2, the tracing of &lt;code&gt;i=&quot;$j&quot;&lt;/code&gt; shows &lt;code&gt;i=2&lt;/code&gt; whereas the tracing of &lt;code&gt;((i=j))&lt;/code&gt; only shows &lt;code&gt;((i=j))&lt;/code&gt;. This could be where a bash debugger would be useful, but I know only one, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://bashdb.sourceforge.net/bashdb.html&quot;&gt;bashdb&lt;/a&gt;, and it does not seem updated anymore, and I could not find a version working with bash 5.1. The &lt;code&gt;trap DEBUG&lt;/code&gt; trick can be useful, though.&lt;/li&gt;
&lt;li&gt;So, I tend to write now &lt;code&gt;i=$((j+k))&lt;/code&gt; while developing code, and maybe later for production switch to the a bit more efficient &lt;code&gt;((i = j+k))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I avoided arrays in bash because I found out they were abysmally slow when first introduced, to the point that managing data in files with grep, sed, ... was actually faster than using arrays. But not anymore! Bash arrays should now be used as much as possible.&lt;/li&gt;
&lt;li&gt;A lot of bash functions can &quot;map&quot; on arrays. For instance &lt;code&gt;${tab[@]//x/y}&lt;/code&gt; will string-replace x by y in all the elements of the array tab, and is super fast.&lt;/li&gt;
&lt;li&gt;Use arrays rather than the classic way to represent lists in bash by space-separated (or tab-separated) substrings in a  string.&lt;/li&gt;
&lt;li&gt;Bash can have typed variables: integer ones via &lt;code&gt;declare -i&lt;/code&gt; or &lt;code&gt;local -i&lt;/code&gt;, and using them makes your code safer.&lt;/li&gt;
&lt;li&gt;Bash functions can be passed variables by name, useful for efficiency to avoid copying big arrays or strings, and to provide multiple return values by modifying passed variables. But it cannot recurse as it is not a passing by reference, but by name.&lt;/li&gt;
&lt;li&gt;Working with arrays makes using &lt;code&gt;$(...)&lt;/code&gt; impractical, as commands are executed in a subshell and cannot access arrays anymore to update them in the parent shell. So I tend to pass the return value(s) into global variables of the same name of a function. E.g. instead of &lt;code&gt;x=$(foo)&lt;/code&gt;, I write &lt;code&gt;foo; x=&quot;$foo&quot;&lt;/code&gt;. Or pass variables by name to set them if I want to return multiple results.&lt;/li&gt;
&lt;li&gt;To parse a space-separated string, the fastest is using &lt;code&gt;set&lt;/code&gt; to map the elements in the positional parameters &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, ... then the &lt;code&gt;${string#* }&lt;/code&gt; and &lt;code&gt;${string% *}&lt;/code&gt; operators are the fastest, closely followed by a read, the full &lt;code&gt;[[ $string =~ ([-[:digit:]]+)[[:space:]]... ]]&lt;/code&gt; being 3 times slower. And if possible, using indexes is even faster: &lt;code&gt;${string:i:j}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use the faster &lt;code&gt;$(&amp;lt; filename)&lt;/code&gt; instead of &lt;code&gt;$(cat filename)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To copy an associative array A1 to A2 in bash 4.4+, do:
&lt;code&gt;A1_def=$(declare -p A1) &amp;amp;&amp;amp; declare -A A2=&quot;${A1_def#*=}&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To access more than 9 parameters in a function: use braces: &lt;code&gt;$10&lt;/code&gt; wont work, but &lt;code&gt;${10}&lt;/code&gt; does.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Advent of Code 2021</title>
        <published>2021-12-07T00:00:00+00:00</published>
        <updated>2021-12-07T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://colas.nahaboo.net/code/advent-of-code-2021/"/>
        <id>https://colas.nahaboo.net/code/advent-of-code-2021/</id>
        
        <content type="html" xml:base="https://colas.nahaboo.net/code/advent-of-code-2021/">&lt;p&gt;I just discovered the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://adventofcode.com/&quot;&gt;Advent of code&lt;/a&gt; coding challenge. It exists since 2015, and seems really interesting in that it is language-agnostic.&lt;/p&gt;
&lt;p&gt;Each day a problem is described, with input files consisting of numeric values for the problem. You must submit the answer which is a number by whatever means you want.&lt;/p&gt;
&lt;p&gt;I started to do the current 2021 challenge, in &lt;strong&gt;bash&lt;/strong&gt; to force myself to code in a more modern style that pass &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.shellcheck.net/&quot;&gt;shellcheck&lt;/a&gt;. And later, i will probably do the other years in &lt;strong&gt;Go&lt;/strong&gt; to learn it.&lt;/p&gt;
&lt;p&gt;I publish my solutions as I code them on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ColasNahaboo/advent-of-code-my-solutions/tree/main/bash/2021&quot;&gt;my github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;More info:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://adventofcode.com/&quot;&gt;advent of code&lt;/a&gt; (aka AOC) site&lt;/li&gt;
&lt;li&gt;The sub-reddit  &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://www.reddit.com/r/adventofcode/&quot;&gt;/r/adventofcode&lt;/a&gt; to discuss AOC&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
</feed>
