<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Misc on Redowan&#39;s Reflections</title>
    <link>https://rednafi.com/misc/</link>
    <description>Recent content in Misc on Redowan&#39;s Reflections</description>
    <image>
      <title>Redowan&#39;s Reflections</title>
      <url>https://blob.rednafi.com/home/cover-39e2ac8de020.png</url>
      <link>https://rednafi.com/</link>
    </image>
    <generator>Hugo -- 0.165.0</generator>
    <language>en</language>
    <dc:creator>Redowan Delowar</dc:creator>
    <lastBuildDate>Sat, 11 Jul 2026 23:37:22 +0200</lastBuildDate>
    <atom:link href="https://rednafi.com/misc/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Migrating from GNU stow to chezmoi</title>
      <link>https://rednafi.com/misc/chezmoi/</link>
      <pubDate>Fri, 12 Jun 2026 00:00:00 +0200</pubDate>
      <guid>https://rednafi.com/misc/chezmoi/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Why I swapped GNU stow&amp;#39;s symlink farm for chezmoi: one command to bootstrap a Mac with Homebrew packages and macOS settings, a small daily sync loop, and agent skills shared between Claude Code and Codex.</description>
      <content:encoded>&lt;p&gt;I&amp;rsquo;ve been managing my dotfiles with &lt;a href=&#34;https://www.gnu.org/software/stow/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;GNU stow (opens in new tab)&#34;&gt;GNU stow&lt;/a&gt; for a few years. I even wrote &lt;a href=&#34;https://rednafi.com/misc/dotfile-stewardship-for-the-indolent/&#34;&gt;a piece with a
corny title&lt;/a&gt; about that setup back in 2023. Stow served me well, but managing symlinks
across multiple devices slowly became a pain in the butt.&lt;/p&gt;
&lt;p&gt;So I started looking around for a better tool and even considered writing my own. Then a
colleague pointed me to &lt;a href=&#34;https://www.chezmoi.io/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;chezmoi (opens in new tab)&#34;&gt;chezmoi&lt;/a&gt;, and so far I&amp;rsquo;m liking it a lot. It does everything I
need, and I&amp;rsquo;ve started tracking my agent skill files with it too.&lt;/p&gt;
&lt;h2 id=&#34;the-machines&#34;&gt;The machines &lt;a class=&#34;anchor&#34; href=&#34;#the-machines&#34; aria-label=&#34;Permalink to The machines&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I run three Macs: a MacBook Pro for work, a MacBook Air for personal use, and a Mac Mini
that acts as a small personal server. The Mini mostly gets SSHed into from the other two.
It&amp;rsquo;s still a Mac with my shell on it, so the same dotfiles apply.&lt;/p&gt;
&lt;p&gt;I also keep a few Linux VMs around, but I rarely need my dotfiles on servers. Ansible
provisions those. This workflow is strictly for the desktop machines.&lt;/p&gt;
&lt;h2 id=&#34;when-i-outgrew-stow&#34;&gt;When I outgrew stow &lt;a class=&#34;anchor&#34; href=&#34;#when-i-outgrew-stow&#34; aria-label=&#34;Permalink to When I outgrew stow&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Stow&amp;rsquo;s model is symlinking. The config files live in a git repo, grouped into directories
that stow calls packages, and stowing a package links its files into the home directory. For
a single machine it still holds up. The commands are idempotent and there&amp;rsquo;s almost nothing
to learn.&lt;/p&gt;
&lt;p&gt;The trouble is that symlinks cut both ways. Every edit on every machine writes straight
through the link into that machine&amp;rsquo;s clone of the repo. Months later I&amp;rsquo;d find dirty working
trees on the Air with changes I had no memory of making. Half of them conflicted with
whatever the Pro had already pushed. Keeping three clones converged turned into a chore.&lt;/p&gt;
&lt;p&gt;Fresh machines were the other half of the problem. Stow won&amp;rsquo;t link over a real file. By the
time Homebrew and a couple of tools have run on a new Mac, files like &lt;code&gt;~/.zprofile&lt;/code&gt; and
&lt;code&gt;~/.gitconfig&lt;/code&gt; already exist. Bootstrapping meant cloning the repo, deleting the conflicting
files by hand, and restowing every package while trying to remember what I&amp;rsquo;d named them. And
stow only does files. Homebrew packages and macOS settings lived in separate scripts that I
had to remember to run in the right order.&lt;/p&gt;
&lt;h2 id=&#34;how-chezmoi-works&#34;&gt;How chezmoi works &lt;a class=&#34;anchor&#34; href=&#34;#how-chezmoi-works&#34; aria-label=&#34;Permalink to How chezmoi works&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chezmoi keeps a source directory at &lt;code&gt;~/.local/share/chezmoi&lt;/code&gt;, which is a regular git repo.
&lt;code&gt;chezmoi add ~/.zshrc&lt;/code&gt; copies the live file into it and names the copy &lt;code&gt;dot_zshrc&lt;/code&gt;. Adding
&lt;code&gt;~/.config/gh/config.yml&lt;/code&gt; creates &lt;code&gt;dot_config/gh/config.yml&lt;/code&gt;, parent directories included. I
never create those names by hand since &lt;code&gt;chezmoi add&lt;/code&gt; derives them from the real paths. The
tree ends up mirroring the home directory, with every leading dot spelled out as a &lt;code&gt;dot_&lt;/code&gt;
prefix.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dot_&lt;/code&gt; is one of several &lt;a href=&#34;https://www.chezmoi.io/reference/source-state-attributes/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;attributes (opens in new tab)&#34;&gt;attributes&lt;/a&gt; that chezmoi encodes into file names. A &lt;code&gt;private_&lt;/code&gt;
prefix strips group and world permissions from the file. A &lt;code&gt;.tmpl&lt;/code&gt; suffix turns the file
into a Go template that can read per-machine data. I use templates sparingly, and every one
of them shows up later in this post.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chezmoi apply&lt;/code&gt; goes the other way. It writes every tracked file back to the home path its
name spells out, so &lt;code&gt;dot_zshrc&lt;/code&gt; lands at &lt;code&gt;~/.zshrc&lt;/code&gt;. The copies are real files, not
symlinks. The source directory is the single source of truth. When a file in the home
directory stops matching its source copy, &lt;code&gt;chezmoi diff&lt;/code&gt; shows the difference and the next
apply puts it back.&lt;/p&gt;
&lt;p&gt;Losing the automatic write-through of symlinks turned out to be the thing I like most.
Nothing changes in the repo unless I deliberately put the change there.&lt;/p&gt;
&lt;h2 id=&#34;what-i-track&#34;&gt;What I track &lt;a class=&#34;anchor&#34; href=&#34;#what-i-track&#34; aria-label=&#34;Permalink to What I track&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All of it sits in that source directory. &lt;code&gt;chezmoi cd&lt;/code&gt; drops me into a subshell there, and
here&amp;rsquo;s the entire tree:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;~/.local/share/chezmoi
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .chezmoi.toml.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .chezmoiignore
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .chezmoiscripts
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── macos
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       ├── run_onchange_after_disable-macos-animations.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       ├── run_onchange_after_init-macos-machine.sh.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       └── run_onchange_before_install-homebrew-bundle.sh.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── Brewfile
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── README.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_agents
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── skills
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       ├── go-modernize
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       ├── go-styleguide
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       └── meatspeak
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_claude
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── settings.json
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── symlink_skills.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_codex
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── private_config.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_config
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── gh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │   ├── config.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │   └── private_hosts.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── ghostty
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│       └── config
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_gitconfig
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_gitconfig-pers
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_gitconfig-werk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_shellcheckrc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── dot_zsh_aliases
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── dot_zshrc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The list is short because I dislike customizing tools and stick to defaults where I can. The
dotfiles proper are the zsh, git, shellcheck, &lt;a href=&#34;https://ghostty.org/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;ghostty (opens in new tab)&#34;&gt;ghostty&lt;/a&gt;, and GitHub CLI configs. I track
Claude Code&amp;rsquo;s &lt;code&gt;settings.json&lt;/code&gt; and Codex&amp;rsquo;s &lt;code&gt;config.toml&lt;/code&gt; too, so the agents behave the same
on every machine. The &lt;code&gt;private_&lt;/code&gt; prefix on gh&amp;rsquo;s &lt;code&gt;hosts.yml&lt;/code&gt; and the Codex config keeps those
two at &lt;code&gt;0600&lt;/code&gt;. I&amp;rsquo;ll talk about the skills under &lt;code&gt;dot_agents&lt;/code&gt; at the end.&lt;/p&gt;
&lt;p&gt;The three gitconfigs split my identities. All my projects live under two directories,
&lt;code&gt;~/canvas/werk/&lt;/code&gt; for work and &lt;code&gt;~/canvas/pers/&lt;/code&gt; for everything personal, and both exist on
every machine. The main gitconfig routes identity by where a repo lives:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[includeIf &amp;#34;gitdir:~/canvas/pers/&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    path = ~/.gitconfig-pers
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[includeIf &amp;#34;gitdir:~/canvas/werk/&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    path = ~/.gitconfig-werk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Repos under &lt;code&gt;~/canvas/pers/&lt;/code&gt; get my personal email and repos under &lt;code&gt;~/canvas/werk/&lt;/code&gt; get the
work one. That&amp;rsquo;s a plain git feature, not chezmoi templating, but chezmoi guarantees all
three files exist on every machine.&lt;/p&gt;
&lt;p&gt;That &lt;code&gt;.chezmoi.toml.tmpl&lt;/code&gt; at the top is chezmoi&amp;rsquo;s own config template. It asks for the
machine&amp;rsquo;s name once and remembers the answer in &lt;code&gt;~/.config/chezmoi/chezmoi.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{{- $machineName := promptStringOnce . &amp;#34;machineName&amp;#34; &amp;#34;machineName&amp;#34; .chezmoi.hostname -}}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[data]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    machineName = {{ $machineName | quote }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The machine setup script reads that value to set the hostname. It&amp;rsquo;s the only per-machine
data in the whole repo. Everything else is identical everywhere. I keep it this way partly
for simplicity and partly because I&amp;rsquo;m not a big fan of Go&amp;rsquo;s template syntax, so the less I
have to muck around with it, the better.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.chezmoiignore&lt;/code&gt; lists &lt;code&gt;README.md&lt;/code&gt;, the &lt;code&gt;Brewfile&lt;/code&gt;, and &lt;code&gt;Brewfile.lock.json&lt;/code&gt;, so all three
stay in the source directory without ever being written to the home directory. A plain
&lt;code&gt;.gitignore&lt;/code&gt; keeps the lock file out of version control. I&amp;rsquo;ll cover the &lt;code&gt;Brewfile&lt;/code&gt; and the
scripts under &lt;code&gt;.chezmoiscripts&lt;/code&gt; in the next section.&lt;/p&gt;
&lt;h2 id=&#34;bootstrapping-a-new-mac&#34;&gt;Bootstrapping a new Mac &lt;a class=&#34;anchor&#34; href=&#34;#bootstrapping-a-new-mac&#34; aria-label=&#34;Permalink to Bootstrapping a new Mac&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Homebrew goes on first, and then the whole setup is two commands:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew install chezmoi
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi init --apply &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --promptString &lt;span class=&#34;nv&#34;&gt;machineName&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;mini &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    https://github.com/rednafi/dotfiles.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;chezmoi init&lt;/code&gt; clones the repo into &lt;code&gt;~/.local/share/chezmoi&lt;/code&gt;, and &lt;code&gt;--apply&lt;/code&gt; writes every
tracked file into place right away. The &lt;code&gt;--promptString&lt;/code&gt; flag pre-answers the config
template&amp;rsquo;s question. Without it, chezmoi asks interactively. Scripts run as part of the same
apply.&lt;/p&gt;
&lt;p&gt;Anything under &lt;code&gt;.chezmoiscripts/&lt;/code&gt; gets &lt;a href=&#34;https://www.chezmoi.io/user-guide/use-scripts-to-perform-actions/#understand-how-scripts-work&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;executed during apply (opens in new tab)&#34;&gt;executed during apply&lt;/a&gt;, and the file names control
the timing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;before&lt;/code&gt; script runs before chezmoi writes any files.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;after&lt;/code&gt; script runs once they&amp;rsquo;re all in place.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;run_onchange_&lt;/code&gt; prefix makes a script fire on the first apply and after that only when
its contents change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On a fresh machine that works out to: install the Homebrew packages, lay down the dotfiles,
then configure macOS itself. The &lt;code&gt;onchange&lt;/code&gt; part enables a trick that comes &lt;a href=&#34;https://www.chezmoi.io/user-guide/use-scripts-to-perform-actions/#run-a-script-when-the-contents-of-another-file-changes&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;straight from
the chezmoi docs (opens in new tab)&#34;&gt;straight from
the chezmoi docs&lt;/a&gt;. Here&amp;rsquo;s the Homebrew script, trimmed:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Brewfile checksum: {{ include &amp;#34;Brewfile&amp;#34; | sha256sum }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ... elided&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;brewfile&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;={{&lt;/span&gt; joinPath .chezmoi.sourceDir &lt;span class=&#34;s2&#34;&gt;&amp;#34;Brewfile&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; quote &lt;span class=&#34;o&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$brew_bin&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; bundle check --no-upgrade --file &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$brewfile&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &amp;gt;/dev/null 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$brew_bin&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; bundle install --no-upgrade --file &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$brewfile&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The elided lines locate the Homebrew binary and store its path in &lt;code&gt;$brew_bin&lt;/code&gt;. The template
inlines a hash of the &lt;code&gt;Brewfile&lt;/code&gt; into a comment. Adding a package to the &lt;code&gt;Brewfile&lt;/code&gt; changes
the hash, which changes the rendered script, which makes chezmoi run it again on the next
apply. So &lt;a href=&#34;https://docs.brew.sh/Brew-Bundle-and-Brewfile&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;brew bundle (opens in new tab)&#34;&gt;brew bundle&lt;/a&gt; fires exactly when the package list changes and stays quiet
otherwise. The &lt;code&gt;--no-upgrade&lt;/code&gt; flag keeps it from touching packages that are already
installed. Upgrades stay manual since I want to see what&amp;rsquo;s about to change first.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Brewfile&lt;/code&gt; is about sixty lines long. An excerpt:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;rb&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;brew&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;chezmoi&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;brew&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;fzf&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;brew&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;gh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;brew&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;micro&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;brew&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ripgrep&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;brew&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;uv&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;cask&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;claude-code&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;cask&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;codex&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;cask&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ghostty&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;cask&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;raycast&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Two more scripts run after the files land. The first sets the hostname from &lt;code&gt;machineName&lt;/code&gt;
and writes every macOS default I&amp;rsquo;d otherwise set by clicking through System Settings on each
new machine. The second turns off most of the UI animations. Both are long lists of plain
&lt;code&gt;defaults write&lt;/code&gt; calls, and the details are in the repo.&lt;/p&gt;
&lt;p&gt;Every script starts with a Darwin check and exits early anywhere else, so nothing fires if I
ever apply this on a Linux box. I used to keep all of this in a setup script that I&amp;rsquo;d forget
to run. Now it&amp;rsquo;s part of apply and I can&amp;rsquo;t forget.&lt;/p&gt;
&lt;h2 id=&#34;day-to-day&#34;&gt;Day to day &lt;a class=&#34;anchor&#34; href=&#34;#day-to-day&#34; aria-label=&#34;Permalink to Day to day&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The whole routine is about five commands.&lt;/p&gt;
&lt;p&gt;Edits usually start at the source. &lt;code&gt;chezmoi edit&lt;/code&gt; opens the source copy behind a home file,
and &lt;code&gt;--apply&lt;/code&gt; writes it through when I close the editor:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi edit --apply ~/.zshrc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Sometimes the edit happens in the other direction. An installer appends to &lt;code&gt;~/.zshrc&lt;/code&gt;, or I
tweak the live file directly out of habit. Now the home directory is ahead of the source,
and &lt;code&gt;chezmoi diff&lt;/code&gt; will show that an apply would undo my change. When the change should
stick, I import the live file back into the source:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi add ~/.zshrc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;When several home files have moved ahead of their sources like this, &lt;code&gt;chezmoi re-add&lt;/code&gt;
re-imports them all in one go.&lt;/p&gt;
&lt;p&gt;Once the source state looks right, sharing it is plain git from inside the source repo:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git add -A
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git commit -m &lt;span class=&#34;s2&#34;&gt;&amp;#34;Update dotfiles&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;exit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;On the other machines, catching up is one command:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi update --verbose&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That pulls the repo and applies it in one shot. When I want to inspect what&amp;rsquo;s coming, I
split it up and read the diff first:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi git pull -- --autostash --rebase
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi diff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chezmoi apply --verbose&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Packages can fall out of sync with the &lt;code&gt;Brewfile&lt;/code&gt; too. &lt;code&gt;brew bundle check&lt;/code&gt; reports anything
the &lt;code&gt;Brewfile&lt;/code&gt; expects but the machine lacks, &lt;code&gt;brew outdated --greedy&lt;/code&gt; shows what&amp;rsquo;s stale,
and &lt;code&gt;brew bundle cleanup&lt;/code&gt; lists what&amp;rsquo;s installed but untracked:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew bundle check --no-upgrade --file &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;chezmoi source-path&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/Brewfile&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew outdated --greedy
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brew bundle cleanup --file &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;chezmoi source-path&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/Brewfile&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;tracking-agent-skills&#34;&gt;Tracking agent skills &lt;a class=&#34;anchor&#34; href=&#34;#tracking-agent-skills&#34; aria-label=&#34;Permalink to Tracking agent skills&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The newest additions to the repo are &lt;a href=&#34;https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;skills for LLM agents (opens in new tab)&#34;&gt;skills for LLM agents&lt;/a&gt;. A skill is a folder with a
&lt;code&gt;SKILL.md&lt;/code&gt; and whatever reference files it needs. The &lt;code&gt;SKILL.md&lt;/code&gt; carries name and
description frontmatter followed by instructions. The layout comes straight from the &lt;a href=&#34;https://agentskills.io/home&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Agent
Skills (opens in new tab)&#34;&gt;Agent
Skills&lt;/a&gt; spec, an open standard that started at Anthropic and has been adopted by a growing
list of agent products.&lt;/p&gt;
&lt;p&gt;Because the format is standard, one copy should work everywhere. I use both &lt;a href=&#34;https://code.claude.com/docs/en/skills&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Claude Code (opens in new tab)&#34;&gt;Claude Code&lt;/a&gt;
and &lt;a href=&#34;https://developers.openai.com/codex/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Codex (opens in new tab)&#34;&gt;Codex&lt;/a&gt;, and the skills live in &lt;code&gt;~/.agents/skills&lt;/code&gt;, which Codex picks up by default. In
chezmoi terms that&amp;rsquo;s a regular directory at &lt;code&gt;dot_agents/skills/&lt;/code&gt;, tracked like any other
config.&lt;/p&gt;
&lt;p&gt;Claude Code hasn&amp;rsquo;t caught up with that convention yet. It looks for personal skills in
&lt;code&gt;~/.claude/skills&lt;/code&gt; and knows nothing about &lt;code&gt;~/.agents&lt;/code&gt;. The fix is a one-line file in the
source repo at &lt;code&gt;dot_claude/symlink_skills.tmpl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{{ .chezmoi.homeDir }}/.agents/skills&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Three name parts work together here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dot_claude/&lt;/code&gt; directory and the file name map the target to &lt;code&gt;~/.claude/skills&lt;/code&gt;, the
same way &lt;code&gt;dot_zshrc&lt;/code&gt; maps to &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;symlink_&lt;/code&gt; prefix tells chezmoi to create that target as a symlink instead of a
regular file, pointing wherever the file&amp;rsquo;s content says.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.tmpl&lt;/code&gt; suffix makes chezmoi render the content first, so &lt;code&gt;{{ .chezmoi.homeDir }}&lt;/code&gt;
expands to the right home directory on whichever machine is applying.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After an apply:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls -ld ~/.claude/skills&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;lrwxr-xr-x 1 rednafi staff 29 Jun 11 17:37 /Users/rednafi/.claude/skills -&amp;gt; /Users/rednafi/.agents/skills&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a mild irony in leaving stow to escape symlinks and then having chezmoi manage the
one symlink I still need. But that&amp;rsquo;s on Anthropic being a baby and not following the
convention other agents already follow. Now both agents read the same skill files, git holds
a single copy, and a new machine picks all of it up from the same &lt;code&gt;chezmoi init&lt;/code&gt; as
everything else.&lt;/p&gt;
&lt;p&gt;Everything here lives in my &lt;a href=&#34;https://github.com/rednafi/dotfiles&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;dotfiles repo (opens in new tab)&#34;&gt;dotfiles repo&lt;/a&gt;. Steal whatever looks useful.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>Shell</category>
      <category>CLI</category>
      <category>Unix</category>
    </item>
    <item>
      <title>Putting this blog on ATProto with standard.site</title>
      <link>https://rednafi.com/misc/standard-site/</link>
      <pubDate>Sun, 07 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/standard-site/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Mirroring a static Hugo blog onto ATProto with standard.site and Sequoia, plus the GitHub Actions wiring that republishes the records on every push without any manual steps.</description>
      <content:encoded>&lt;p&gt;I put this blog on &lt;a href=&#34;https://standard.site&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;standard.site (opens in new tab)&#34;&gt;standard.site&lt;/a&gt;. Every post now also lives as a record on &lt;a href=&#34;https://atproto.com&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;ATProto (opens in new tab)&#34;&gt;ATProto&lt;/a&gt; (the
protocol behind Bluesky), and new ones publish themselves whenever I &lt;a href=&#34;https://github.com/rednafi/com.rednafi&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;push to main (opens in new tab)&#34;&gt;push to main&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-it-is&#34;&gt;What it is &lt;a class=&#34;anchor&#34; href=&#34;#what-it-is&#34; aria-label=&#34;Permalink to What it is&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;standard.site is a set of shared &lt;a href=&#34;https://atproto.com/specs/lexicon&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;ATProto lexicons (opens in new tab)&#34;&gt;ATProto lexicons&lt;/a&gt;. The two that matter here are
&lt;code&gt;site.standard.publication&lt;/code&gt; and &lt;code&gt;site.standard.document&lt;/code&gt;. The publication record describes
the blog: name, URL, icon. Each post becomes a document record that lives in my own &lt;a href=&#34;https://atproto.com/guides/data-repos&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;data
repository (opens in new tab)&#34;&gt;data
repository&lt;/a&gt; on a &lt;a href=&#34;https://atproto.com/guides/glossary#pds-personal-data-server&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;PDS (opens in new tab)&#34;&gt;PDS&lt;/a&gt; and points back at the publication. To prove the records are actually
mine, there&amp;rsquo;s a &lt;a href=&#34;https://rednafi.com/.well-known/site.standard.publication&#34;&gt;/.well-known/site.standard.publication&lt;/a&gt; file on my domain and a &lt;a href=&#34;https://github.com/rednafi/com.rednafi/blob/main/layouts/partials/head.html&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;link-rel
tag (opens in new tab)&#34;&gt;link-rel
tag&lt;/a&gt; in every post&amp;rsquo;s HTML pointing at the matching record. The two ends point at each other,
with no central registry in between.&lt;/p&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;div class=&#34;mermaid&#34;&gt;
sequenceDiagram
    participant R as Reader
    participant S as rednafi.com
    participant P as PDS

    R-&gt;&gt;S: GET /zephyr/carry-the-pager/
    S--&gt;&gt;R: HTML + a site.standard.document link tag
    R-&gt;&gt;P: resolve that document record
    P--&gt;&gt;R: site = publication URI, path = /zephyr/carry-the-pager/
    R-&gt;&gt;S: GET /.well-known/site.standard.publication
    S--&gt;&gt;R: the same publication URI
    Note over R: URIs match, so it&#39;s provably rednafi.com&#39;s
&lt;/div&gt;

&lt;!-- prettier-ignore-end --&gt;
&lt;h2 id=&#34;why-bother&#34;&gt;Why bother &lt;a class=&#34;anchor&#34; href=&#34;#why-bother&#34; aria-label=&#34;Permalink to Why bother&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mostly the previews. Share one of my posts on Bluesky and the link turns into a card with
the title, description, and image instead of a bare URL. That works because the post is a
real record the network can read. Bluesky &lt;a href=&#34;https://atproto.com/blog/standard-site-bluesky-timeline&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;shows richer previews (opens in new tab)&#34;&gt;shows richer previews&lt;/a&gt; for standard.site links.&lt;/p&gt;
&lt;p&gt;It goes past Bluesky, though. The records sit in my own PDS, so any reader can pick them up
on its own. &lt;a href=&#34;https://docs.surf&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;docs.surf (opens in new tab)&#34;&gt;docs.surf&lt;/a&gt; already lists my posts, and a search on &lt;a href=&#34;https://pckt.blog/read?search=rednafi&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;pckt (opens in new tab)&#34;&gt;pckt&lt;/a&gt; turns them up too:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blob.rednafi.com/misc/standard-site/image-03-d67bfdc6d73e.png&#34; alt=&#34;Searching pckt for rednafi lists the posts, the pager among them&#34; width=&#34;1744&#34; height=&#34;1520&#34; referrerpolicy=&#34;no-referrer&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s cheap &lt;a href=&#34;https://indieweb.org/POSSE&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;POSSE (opens in new tab)&#34;&gt;POSSE&lt;/a&gt; on top of that: rednafi.com stays the canonical copy while a copy
syndicates out into the &lt;a href=&#34;https://atproto.com/blog/indexing-standard-site&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;ATmosphere (opens in new tab)&#34;&gt;ATmosphere&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;setting-it-up-with-sequoia&#34;&gt;Setting it up with Sequoia &lt;a class=&#34;anchor&#34; href=&#34;#setting-it-up-with-sequoia&#34; aria-label=&#34;Permalink to Setting it up with Sequoia&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I didn&amp;rsquo;t hand-roll any of the ATProto plumbing. &lt;a href=&#34;https://sequoia.pub&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Sequoia (opens in new tab)&#34;&gt;Sequoia&lt;/a&gt; is a CLI by Steve Simkins that
does the whole thing for static sites. It doesn&amp;rsquo;t much care what built yours (Hugo, Astro,
Eleventy) as long as it&amp;rsquo;s Markdown. If you want to put your own blog on standard.site, it
goes roughly like this.&lt;/p&gt;
&lt;p&gt;First, you need an ATProto identity, and a Bluesky account is the easy way to get one. The
records live in your own PDS. Ownership is checked against a domain, so set your site&amp;rsquo;s
domain as your handle (mine is &lt;code&gt;rednafi.com&lt;/code&gt;) and mint an app password for the CLI.&lt;/p&gt;
&lt;p&gt;Then run &lt;code&gt;sequoia init&lt;/code&gt; in the repo. It authenticates against your PDS, creates a
&lt;code&gt;site.standard.publication&lt;/code&gt; record describing the blog (name, URL, icon), and scaffolds a
&lt;code&gt;sequoia.json&lt;/code&gt;. That config is small: it points at your content directory and maps the
frontmatter fields it reads, like the publish date and the slug.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;json&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;siteUrl&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://rednafi.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;contentDir&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;publicationUri&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;frontmatter&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;&amp;#34;publishDate&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;date&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;&amp;#34;slugField&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;slug&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That &lt;code&gt;publicationUri&lt;/code&gt; is the &lt;code&gt;at://&lt;/code&gt; address of the publication record &lt;code&gt;init&lt;/code&gt; just made. The
same URI also lands in &lt;code&gt;static/.well-known/site.standard.publication&lt;/code&gt;, so the domain and the
record name each other and the ownership check holds.&lt;/p&gt;
&lt;p&gt;Each post&amp;rsquo;s HTML also needs a &lt;code&gt;&amp;lt;link rel=&amp;quot;site.standard.document&amp;quot;&amp;gt;&lt;/code&gt; pointing at that post&amp;rsquo;s
record. &lt;code&gt;sequoia inject&lt;/code&gt; can patch the tags into your built HTML; I emit them from my Hugo
&lt;code&gt;head&lt;/code&gt; partial instead.&lt;/p&gt;
&lt;p&gt;With that wired up, &lt;code&gt;sequoia publish&lt;/code&gt; walks the content, creates a &lt;code&gt;site.standard.document&lt;/code&gt;
record per post, and writes the resulting &lt;code&gt;atUri&lt;/code&gt; back into each post&amp;rsquo;s frontmatter. State
lives in &lt;code&gt;.sequoia-state.json&lt;/code&gt;, so reruns only touch what actually changed.&lt;/p&gt;
&lt;h2 id=&#34;making-it-hands-free&#34;&gt;Making it hands-free &lt;a class=&#34;anchor&#34; href=&#34;#making-it-hands-free&#34; aria-label=&#34;Permalink to Making it hands-free&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I didn&amp;rsquo;t want to run &lt;code&gt;sequoia publish&lt;/code&gt; by hand, so it happens in &lt;a href=&#34;https://github.com/rednafi/com.rednafi/blob/main/.github/workflows/ci.yml&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;CI (opens in new tab)&#34;&gt;CI&lt;/a&gt;. The job does a few
things before the Hugo build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;checks that every post has the same frontmatter keys in the same order&lt;/li&gt;
&lt;li&gt;sets &lt;code&gt;slug&lt;/code&gt; and &lt;code&gt;atprotoPath&lt;/code&gt; from the filename&lt;/li&gt;
&lt;li&gt;stops the build if a post has extra fields, missing fields, or a wrong path&lt;/li&gt;
&lt;li&gt;copies the posts to a temporary Sequoia directory and adds the shared cover there&lt;/li&gt;
&lt;li&gt;asks Sequoia to publish only posts whose content changed&lt;/li&gt;
&lt;li&gt;writes Sequoia&amp;rsquo;s returned &lt;code&gt;atUri&lt;/code&gt; values and &lt;code&gt;.sequoia-state.json&lt;/code&gt; back&lt;/li&gt;
&lt;li&gt;commits only those generated metadata changes with &lt;code&gt;[skip ci]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;builds and deploys the Hugo site&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My routine didn&amp;rsquo;t change: write Markdown and push to &lt;code&gt;main&lt;/code&gt;. CI fills in the &lt;code&gt;atUri&lt;/code&gt;,
commits it back, and lets the deploy continue. This post turned into a
&lt;code&gt;site.standard.document&lt;/code&gt; the moment the deploy ran.&lt;/p&gt;
&lt;h2 id=&#34;seeing-it-work&#34;&gt;Seeing it work &lt;a class=&#34;anchor&#34; href=&#34;#seeing-it-work&#34; aria-label=&#34;Permalink to Seeing it work&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The previews are what I actually wanted. The card is just the record rendered, so I can put
a live, clickable one right here instead of a screenshot:&lt;/p&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;style&gt;
.bskycard{position:relative;max-width:30rem;margin:var(--space-6,1.5rem) auto;border:1px solid var(--border,#ebebeb);border-radius:var(--radius,6px);overflow:hidden;background:var(--bg-2,#fafafa);box-shadow:none;transition:border-color var(--motion,.2s ease)}
.bskycard:hover{border-color:var(--border-strong,#c9c9c9);box-shadow:none}
.bskycard .bskycard__media{display:block;margin:0;background:var(--surface,var(--code-bg,#f2f2f2))}
.bskycard .bskycard__media img{display:block;width:100%;height:auto;max-width:100%;margin:0;border:0;border-radius:0;aspect-ratio:1200/630;object-fit:cover}
.bskycard .bskycard__body{padding:var(--space-8,2rem)}
.bskycard a.bskycard__title,.bskycard a.bskycard__title:visited{display:block;margin:0;font-weight:var(--fw-semibold,600);font-size:var(--fs-lg,1.1rem);line-height:var(--lh-snug,1.3);color:var(--text,#171717);background:none;text-decoration:none}
.bskycard:hover a.bskycard__title{color:var(--link,#0062d1);text-decoration:none}
.bskycard a.bskycard__title::after{content:&#34;&#34;;position:absolute;inset:0;z-index:1}
.bskycard .bskycard__desc{margin:var(--space-2,.5rem) 0 0;color:var(--muted,#4d4d4d);font-size:var(--fs-md,.9rem);line-height:var(--lh-ui,1.4);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.bskycard .bskycard__meta{margin:var(--space-3,.75rem) 0 0;color:var(--faint,#6e6e6e);font-size:var(--fs-sm,.85rem)}
.bskycard .bskycard__meta .dot{margin:0 .45em}
.bskycard .bskycard__foot{display:flex;align-items:center;gap:var(--space-3,.75rem);margin-top:var(--space-4,1rem);padding-top:var(--space-3,.75rem);border-top:1px solid var(--border,#ebebeb)}
.bskycard img.bskycard__avatar{display:block;flex:none;width:2.1rem;height:2.1rem;max-width:none;margin:0;border:0;border-radius:50%;object-fit:cover}
.bskycard .bskycard__pub{display:flex;flex-direction:column;min-width:0;line-height:1.3}
.bskycard .bskycard__pubname{font-weight:var(--fw-semibold,600);font-size:var(--fs-sm,.85rem);color:var(--text,#171717);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bskycard .bskycard__handle{display:flex;align-items:center;gap:.34em;color:var(--faint,#6e6e6e);font-size:var(--fs-sm,.85rem);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bskycard .bskycard__handle svg{flex:none;opacity:var(--op-dim,.72)}
.bskycard a.bskycard__cta,.bskycard a.bskycard__cta:visited{position:relative;z-index:2;margin-left:auto;flex:none;display:inline-flex;align-items:center;gap:.4em;padding:var(--space-2,.5rem) var(--space-3,.75rem);border-radius:var(--radius,6px);background:var(--text,#171717);color:var(--bg,#fafafa);font-size:var(--fs-sm,.85rem);font-weight:var(--fw-semibold,600);text-decoration:none}
.bskycard a.bskycard__cta:hover{opacity:var(--op-hover,.85)}
&lt;/style&gt;&lt;div class=&#34;bskycard&#34;&gt;
  &lt;div class=&#34;bskycard__media&#34;&gt;&lt;img src=&#34;https://blob.rednafi.com/home/cover-39e2ac8de020.png&#34; alt=&#34;&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&lt;/div&gt;
  &lt;div class=&#34;bskycard__body&#34;&gt;
    &lt;a class=&#34;bskycard__title&#34; href=&#34;https://rednafi.com/zephyr/carry-the-pager/&#34;&gt;If you won&amp;#39;t carry the pager, maybe don&amp;#39;t push to mainline&lt;/a&gt;&lt;p class=&#34;bskycard__desc&#34;&gt;Drive-by AI changes break the shared model a team builds around its code, and the ICs end up cleaning up the mess. Why pushing to mainline should come with the pager.&lt;/p&gt;
    &lt;p class=&#34;bskycard__meta&#34;&gt;May 30, 2026&lt;span class=&#34;dot&#34;&gt;·&lt;/span&gt;8m read&lt;/p&gt;
    &lt;div class=&#34;bskycard__foot&#34;&gt;
      &lt;img class=&#34;bskycard__avatar&#34; src=&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAACohjseAAAKRElEQVR42tVb&amp;#43;VNUVxZ&amp;#43;&amp;#43;cHJ/ANjuWFcSNNNN4syqHFBBHoTulkUFVCjElFQwSVBFhWMLdAbyCZD1JhJNO41qSh0o6iDipgay1GiY1V0TKyJYxA1Ws5Ek0o4ud/Drpbl9aNJQ&amp;#43;BWnXqv77v33O87595zz30PuL4q46eah0rCLcE&amp;#43;EUXzpCpztq/G0uSrsb6A4B51eCYJM04arzIP5QZ68daWvS4JN82Qqa0GP11pw4Q5lQ&amp;#43;CE3a1TUveTzNTD1NoGpNVhyC45&amp;#43;vwjLUhtPWLKj0nZ32hY0xo/h8HDDFZmOUNX7Ulx19f1hycuJsROErq947T7Jxaitxk6ySo61KPtujD&amp;#43;h4j6PCPLm&amp;#43;Wqay50tDCMb8bsXERRaPZdLNOiNv5MGTFIVJnnnCCzq1loGvcEvRx9NcwXSErDhN0yzSWYozVb8SCglKGyJSmjIDYyvszU4&amp;#43;QNrum3QsOUp6Ql2ShG2NgLJnSkhEUVD2kT8lJQgukisiSU28t3UeajbzHAKhPBWNgLIwpj9xRL1EVSPuGnMoYExBT8V1Y&amp;#43;t/c8JhnPYqxgUHGsHiUnFRlWhM0r/onBAIRr/W5N4EBWIDJU&amp;#43;SyghP2kDarplfBQ5t9glQbj7OrZ0gCA3QFJ&amp;#43;wmmdK48beRizCt4cll944c&amp;#43;iUV1FP1iRsUm2cnrRt9NVknSMOMAx2uSEojzKt7RY5lGdGYCr0lB4HnrEeu0vfPfqTMD5rwu8fkNu39gvbVf0W6zbUuSQIjsLrnufACCVvMLZjvQuSg3CnCYKNY/4zKC6RmoF&amp;#43;ph3cE&amp;#43;&amp;#43;DZwbO36Ktvn1BSYT0IC05XYARWabhV0iNycnn&amp;#43;H&amp;#43;SzrWcc0bIrMYCuobn5Nkp4v13m5NkAQohoF8&amp;#43;B7ALDKUGS0KPfbKOFjJyY15EZASswA7v41FSZ07HndEuOyfytNnqnyE7LC22UaqqjlcY6Sim0k&amp;#43;GjJopnpHsSTJaYztDZq/fgWeYFwfUr4LmOeCIyP2fXWprKMAO764Q51DgqIKb8ATZW7D2dlSVts9MKRi6nqoFqLv6bbn7zkK7faaUL1/5DT//3gqo/u0pz82pFSRYduEKtT57THttNeArtexXAolnfT8/copU7zpGSTVVgBwfhxFllKUZqBO91VjaHAQe5gr820cOnP1Dn8uLHn6nw4yZKYW1iEBiEwcHqCCK4d0FOfPtBZG64do/WVzWS&amp;#43;mVaJ1OZirs/v4UYvAJjKx93NyDALtlup/Tierr73VMSKs23H1CauY4WGewiwEWmn3Dw6WJ4rGdeV27778DYisfwYndrLxengu7WHoJKcoGdSg7&amp;#43;g1yVn39po/c/bKRlrG2kh7MYEJm7tQ5Xl5kOOEiVppwO5HDAVOhLb2gyu197&amp;#43;k21jKCNPqr5ksRK6aHLjKANRvEoubU7G&amp;#43;n6149oXVWjIElgx7HNT7fjOg7gzkQ6omh6cOKeNqE8U8fAgiDAuyq/MA8a9l5s96CHCaaVnaNLN1toFbuKeRFcwMm5satN23GadpVIYw2uLamne63PSKCwqPqIVplP0mLnGvQoyegtNlxFE3JwkanNBge/1xRRO86LZS1z82x8FDXv&amp;#43;4LfEjqXB4//T/m7L/BRNHaLyygqnoMKkBA3mjO7UUSVnAc3zntGwZ8mxFW24r2I2Ka68OU&amp;#43;mLfrPJ2&amp;#43;fJfu3HtCt7/9nuxNdyh7ZwOtMNpZZtN77yFKFnx6hUqOXvtNMwBcwAncOEmYYRLLyh3rT1SQmi1nJJG9YDqmMUFWgzpkOdr26QRxe1/TsWB2/sv79M/brRSXb0ddr8&amp;#43;N4CQJLwzmfJSmeLy&amp;#43;6ylBWBZA5uXbWGZjo0Tm1fh8RE3nvpS95xK9V93kNkm0n284SYkF9bjvyXFKkCA4&amp;#43;SgL4znsGSGph0UI9uw0AYvD8te/eUxXbrUiKLjrBYAWTQLULJVcVHQaCTsM0S1BcMLLZc5XbbkUmgaCtR6LditKGijZehb3PTkzgpRbBpiTX0fNdx5hOmM2dTEiuICTTGO5yOFVOt42ixB0TL8eknR6QUxn4YErOF24RRL9LOwgvfWTy92OAy7gJNdaf&amp;#43;hAUCyxXWY5C4t7zNPLi//OEvfnONwKG0/ciKIEm8SmKKx7uOE23W15RouNp6HcYyQRjOZtOymyVkVFaIo2cliIM0WCDCy14S8XaVfNv1i0dGziHhHRN24grsw87vY2EbISQcaU1eNtAiRhca0bL2ujNtuZ1OHq&amp;#43;GaBK&amp;#43;ogrl4goy17ZqOkojO0bf8V0uedbO&amp;#43;3pa6zTuFtIqwwXmyjd4ByKoOIWA8A1Jmf0Yx39tDEOUaSq3LJJ/RdkoSsJ&amp;#43;msTPKbnUfB80soNPVj0mz83EHW0ZfXo1x3hKYmf0irDMeo&amp;#43;cbXFJJUTIGxRfTnuWaalFBK05ZWs2n4Cak2HEN7J&amp;#43;lcW4eNvkuqBhL8gKyxNus4KdcfpfD0gxS25gBFrD1MGgbc0aaDUV7WhWccokC9gcYEpdBwaSINkyygYT4JEPYbV8gCXkb4LqSxk1L59rNW7&amp;#43;cJB0ZvJ&amp;#43;9p6eQVsJSGy5J4Hd5ByTT0zfnQBUFf6OL7ewUmk/fUdN5oUxZV8OPPzq5xpmocR68hMdVk1sD9vOUmJ5WRrzKHxk1OYwMto5GKt5ksplH&amp;#43;S3jgPjM30MQ4IyzIk9NtOcl7YkJsEdoABMABgKiAAABD/wj5IvTl60a80n&amp;#43;YVFgX2jl0wHijGFbprBzyiyo5xyfbKHJtieGtt/fCCrCco/ErAyU5lTHBMwBBHabd5MRSZvUMAWL9K/DsuCnvkiKy1MA5Cvv&amp;#43;Nl0SuqUNAEHCTYUwCIzRz2SEPeoza2tboL5q2ivf12tf99WYb3gFLncQHJQC7F6BKeSrsXR8ZYGi0JXleM/IYVMscdASBHZwABeuc1FEV3rJ1EWPR/ktGbQEgR0cwKX77xKRpcXe07MGpReBGdjlUWVWTqgERFWN9FWbWkb5Jw&amp;#43;qtQiswOyrNraAA&amp;#43;eqKHTlaySz8geVF4EVmIFd/PNZPD6flZweO3kdOg4KcsAKzMDOiRfsixUSucbcgpALBQOZHDACKzBz7hR/fblepiz8aSSi6gBcj8AEbMAIrFxvil9U2WppuAG5KBQOLHLIOSMM5KcvW831voBkeSYUwVrDBwBJYMB&amp;#43;x5PTlW3kPFFgJUwFgTXZ72sOWMQ85z5JXXmMXGu574iu/TllMZYjWgIDsHB9UEBSqogqPYU9Bxtrf5wgQAxjsTFxBDoFDFxflqCU6iEsamWw08d9pEZYmw6Petpj0M3GwOngv4roinT&amp;#43;zyn7q8h1paP99RUWmdrYigweawPAekm2Q18c26CT6X7op6&amp;#43;wCiTP/UW0erRCV5Ej1xZfwzQaPzWTRk9ciTDuAO1SHCEffdAXOuRaa7O/rjx3QlzFGwPqj9IDYquns3zQoJhtbZCqCh4wsG1vhmyi8dOyaOyUdTQmeA0E93wde8YI5RHaog/6&amp;#43;kfvnAFd3EAv/rFVQwPjPgj20&amp;#43;&amp;#43;MZ4EhiwWnRnnkjucQ3KMOzwJiqiaxtn32bwW/AjpdBh8xDr&amp;#43;yAAAAAElFTkSuQmCC&#34; alt=&#34;&#34; width=&#34;34&#34; height=&#34;34&#34; loading=&#34;lazy&#34;&gt;
      &lt;span class=&#34;bskycard__pub&#34;&gt;
        &lt;span class=&#34;bskycard__pubname&#34;&gt;Redowan&amp;#39;s Reflections&lt;/span&gt;
        &lt;span class=&#34;bskycard__handle&#34;&gt;&lt;svg width=&#34;12&#34; height=&#34;12&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; aria-hidden=&#34;true&#34;&gt;&lt;circle cx=&#34;12&#34; cy=&#34;12&#34; r=&#34;9&#34;/&gt;&lt;path d=&#34;M3 12h18&#34;/&gt;&lt;path d=&#34;M12 3c2.5 2.4 3.9 5.6 4 9-.1 3.4-1.5 6.6-4 9-2.5-2.4-3.9-5.6-4-9 .1-3.4 1.5-6.6 4-9z&#34;/&gt;&lt;/svg&gt;rednafi.com · by @rednafi.com&lt;/span&gt;
      &lt;/span&gt;
      &lt;a class=&#34;bskycard__cta&#34; href=&#34;https://bsky.app/profile/rednafi.com&#34;&gt;&lt;span&gt;View publication&lt;/span&gt;&lt;svg width=&#34;12&#34; height=&#34;12&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; aria-hidden=&#34;true&#34;&gt;&lt;line x1=&#34;7&#34; y1=&#34;17&#34; x2=&#34;17&#34; y2=&#34;7&#34;/&gt;&lt;polyline points=&#34;8 7 17 7 17 16&#34;/&gt;&lt;/svg&gt;&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;!-- prettier-ignore-end --&gt;
&lt;p&gt;That same post also exists as a record on &lt;a href=&#34;https://pdsls.dev/at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.document/3mnl6iapia32u&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;pdsls (opens in new tab)&#34;&gt;pdsls&lt;/a&gt;. It has the title, description, path,
tags, and the full body.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blob.rednafi.com/misc/standard-site/image-02-v2-b87aea40ea0e.png&#34; alt=&#34;The same post as a site.standard.document record in pdsls&#34; width=&#34;1624&#34; height=&#34;1440&#34; referrerpolicy=&#34;no-referrer&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;If you want to copy the setup, it&amp;rsquo;s all in the repo: &lt;a href=&#34;https://github.com/rednafi/com.rednafi/blob/main/sequoia.json&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;config (opens in new tab)&#34;&gt;config&lt;/a&gt;, &lt;a href=&#34;https://github.com/rednafi/com.rednafi/blob/main/scripts/frontmatter/main.go&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;script (opens in new tab)&#34;&gt;script&lt;/a&gt;, and the &lt;a href=&#34;https://github.com/rednafi/com.rednafi/blob/main/.github/workflows/ci.yml&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;ci
workflow (opens in new tab)&#34;&gt;ci
workflow&lt;/a&gt;.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>Essay</category>
      <category>DevOps</category>
      <category>Web</category>
    </item>
    <item>
      <title>Hierarchical rate limiting with Redis sorted sets</title>
      <link>https://rednafi.com/misc/hierarchical-rate-limiting/</link>
      <pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/hierarchical-rate-limiting/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Build multi-level rate limiting with Redis sorted sets and Lua. Enforce global and category-specific limits with ZREMRANGEBYSCORE and ZCARD commands.</description>
      <content:encoded>&lt;p&gt;Recently at work, we ran into this problem:&lt;/p&gt;
&lt;p&gt;We needed to send Slack notifications for specific events but had to enforce rate limits to
avoid overwhelming the channel. Here&amp;rsquo;s how the limits worked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global limit&lt;/strong&gt;: Max 100 requests every 30 minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Category limit&lt;/strong&gt;: Each event type (e.g., errors, warnings) capped at 10 requests per 30
minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, imagine this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There are 20 event types.&lt;/li&gt;
&lt;li&gt;Each type hits its 10-notification limit in 30 minutes.&lt;/li&gt;
&lt;li&gt;That&amp;rsquo;s 200 requests total, but the global limit only allows 100. So, 100 requests must be
dropped - even if some event types still have room under their individual caps.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This created a &lt;strong&gt;hierarchy of limits&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Category limits keep any event type from exceeding 10 requests.&lt;/li&gt;
&lt;li&gt;The global limit ensures the combined total stays under 100.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Every 30 minutes, the system resets. Here are two issues that could arise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If some event types are busier, the global limit could block quieter ones.&lt;/li&gt;
&lt;li&gt;Even with room under the global limit, some event types might still hit their category
caps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our case, the event types are limited, and the category limits are both uniform and
significantly smaller than the global limit, so this isn&amp;rsquo;t a concern.&lt;/p&gt;
&lt;h2 id=&#34;redis-sorted-sets&#34;&gt;Redis sorted sets &lt;a class=&#34;anchor&#34; href=&#34;#redis-sorted-sets&#34; aria-label=&#34;Permalink to Redis sorted sets&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The notification sender service runs on multiple instances, each processing events and
sending notifications independently. Without a shared system to enforce rate limits, these
instances would maintain separate counters for global and category-specific limits. This
would create inconsistencies because no instance would have a complete view of the overall
activity, leading to conflicts and potential exceedance of limits.&lt;/p&gt;
&lt;p&gt;Redis provides a centralized state that all instances can access, ensuring they share the
same counters for rate limits. This removes inconsistencies and makes rate limiting
reliable, even when the notification sender scales to multiple instances.&lt;/p&gt;
&lt;p&gt;Sorted sets in Redis track notifications within a rolling time window by using timestamps as
scores, which keeps entries ordered by time. The implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintains a global sorted set to enforce the overall limit (e.g., 100 notifications per 30
minutes).&lt;/li&gt;
&lt;li&gt;Uses category-specific sorted sets to enforce category limits for each event type (e.g.,
10 notifications per 30 minutes for errors, warnings, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The limits are enforced with two Redis commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ZREMRANGEBYSCORE&lt;/code&gt; removes entries with timestamps outside the rolling time window,
keeping only recent notifications.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ZCARD&lt;/code&gt; counts the remaining entries in a set to check whether the global or
category-specific limits have been reached.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;lua-script&#34;&gt;Lua script &lt;a class=&#34;anchor&#34; href=&#34;#lua-script&#34; aria-label=&#34;Permalink to Lua script&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Instead of embedding the rate-limiting logic directly into the notification sender, we chose
to implement it as a Lua script in Redis. While we could write the logic in the code and run
it in a Redis pipeline, we opted not to, for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A dedicated script keeps the rate-limiting logic separate and independently auditable.&lt;/li&gt;
&lt;li&gt;It saves a few TCP calls, as the entire logic runs within Redis itself.&lt;/li&gt;
&lt;li&gt;And most importantly, I wanted to write some Lua.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The script is as follows:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;lua&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- rate_limiter.lua&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;check_rate_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;-- Get the current timestamp in seconds (including microseconds)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time_raw&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redis.call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;TIME&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;-- {secs, usecs}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time_raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time_raw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1e6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;-- Step 1: Remove expired entries&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;redis.call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ZREMRANGEBYSCORE&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;redis.call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ZREMRANGEBYSCORE&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;-- Step 2: Check the global limit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_count&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redis.call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ZCARD&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_count&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;-- Reject the request if the global limit is reached&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;-- Step 3: Check the category-specific limit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_count&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redis.call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ZCARD&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_count&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;-- Reject the request if the category limit is reached&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;-- Step 4: Add the current notification to the sorted sets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;redis.call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ZADD&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;redis.call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ZADD&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;current_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;-- Allow the request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- Parameters passed to the script:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- KEYS[1]: The Redis key for the global sorted set&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- KEYS[2]: The Redis key for the category-specific sorted set&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- ARGV[1]: Global limit (e.g., 100)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- ARGV[2]: Category limit (e.g., 10)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- ARGV[3]: Time window in seconds (e.g., 1800 for 30 minutes)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;KEYS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;KEYS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tonumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ARGV&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tonumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ARGV&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tonumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ARGV&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- Execute the rate-limiting function and return the result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;check_rate_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The script performs the following operations in order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Remove expired entries&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It uses &lt;code&gt;ZREMRANGEBYSCORE&lt;/code&gt; to remove notifications older than the time window
(&lt;code&gt;current_time - window&lt;/code&gt;). This ensures that only active notifications are considered
for the limits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This eliminates the need for additional bookkeeping to remove expired keys.
&lt;code&gt;ZREMRANGEBYSCORE&lt;/code&gt; is fast enough to handle the removal of a small number of keys
during each invocation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check the global limit&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ZCARD&lt;/code&gt; counts the number of active notifications in the global sorted set.&lt;/li&gt;
&lt;li&gt;If this count equals or exceeds the global limit (e.g., 100), the request is rejected
(&lt;code&gt;return 0&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check the category-specific limit&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ZCARD&lt;/code&gt; is used again to count the active notifications for the specific category.&lt;/li&gt;
&lt;li&gt;If this count equals or exceeds the category limit (e.g., 10), the request is rejected
(&lt;code&gt;return 0&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add the notification&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If both limits are within bounds, the script uses &lt;code&gt;ZADD&lt;/code&gt; to insert the current
notification into both the global and category-specific sorted sets, using a timestamp
as the score for accurate tracking.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;using-the-script&#34;&gt;Using the script &lt;a class=&#34;anchor&#34; href=&#34;#using-the-script&#34; aria-label=&#34;Permalink to Using the script&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can load the Lua script from disk, register it with Redis, and call it before invoking
the notification service. If the script returns 0, drop the notification request. If it
returns 1, send the notification. Here&amp;rsquo;s how to do it in Python:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;py&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;redis&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;redis.commands.core&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;load_lua_script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;redis_client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Redis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;script_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;script_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;lua_script&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redis_client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;register_script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lua_script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;send_notification&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Check the rate limiter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# Allowed: send the notification&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Notification sent: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# Add actual notification-sending logic here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# Blocked: drop the notification&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Notification dropped (rate limit exceeded): &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;vm&#34;&gt;__name__&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Connect to Redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;redis_client&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Redis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;port&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;6379&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Load and register the Lua script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;script_path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rate_limiter.lua&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;load_lua_script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;redis_client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;script_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Define rate limiting parameters&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rate_limit:global&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rate_limit:category:errors&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# Max 100 requests globally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# Max 10 requests per category&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1800&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# 30-minute window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Send a single notification&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;send_notification&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;This is a single notification message&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Registering the Lua script loads it from disk once and reuses it, which is faster than
repeatedly loading and evaluating it for each invocation.&lt;/p&gt;
&lt;p&gt;To test this, you&amp;rsquo;ll need a running Redis instance. You can run one with Docker:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run --name redis-server -d -p 6379:6379 redis&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, running the script will print:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Notification sent: This is a single notification message&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Since this sends a notification only once, the rate limiting isn&amp;rsquo;t apparent yet, but it&amp;rsquo;s
working under the hood and will kick in if any limit is exceeded. To see it in action, you
can attempt to send multiple notifications in a tight loop.&lt;/p&gt;
&lt;h2 id=&#34;testing-the-rate-limiter&#34;&gt;Testing the rate limiter &lt;a class=&#34;anchor&#34; href=&#34;#testing-the-rate-limiter&#34; aria-label=&#34;Permalink to Testing the rate limiter&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can call the &lt;code&gt;send_notification&lt;/code&gt; function multiple times to test the rate limiter. Below
is an example that simulates several notification requests in a short loop, giving you a
sense of how many will be allowed versus blocked:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;py&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;redis&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Connect to Redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;redis_client&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Redis&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;port&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;6379&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Load the Lua script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;rate_limiter.lua&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;lua_script&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Register the Lua script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redis_client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;register_script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lua_script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Example keys and arguments&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rate_limit:global&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rate_limit:category:errors&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;60&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# 1 minute in seconds for this test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Run the script in a loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;global_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;global_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;category_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;message&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Some notification message&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;# Allowed: send the notification&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;. Notification sent: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;# Add actual notification-sending logic here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;# Blocked: drop the notification&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;. Notification dropped (rate limited): &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;vm&#34;&gt;__name__&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This code demonstrates how to test the rate limiter by simulating multiple notification
requests. The Lua script is loaded, registered with Redis, and executed in a loop to
evaluate whether each request is allowed or blocked based on the defined rate limits.&lt;/p&gt;
&lt;p&gt;Running this will produce output similar to:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;0. Notification sent: Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;1. Notification sent: Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2. Notification sent: Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3. Notification dropped (rate limit exceeded): Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;4. Notification dropped (rate limit exceeded): Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;5. Notification dropped (rate limit exceeded): Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;6. Notification dropped (rate limit exceeded): Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;7. Notification dropped (rate limit exceeded): Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8. Notification dropped (rate limit exceeded): Some notification message
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;9. Notification dropped (rate limit exceeded): Some notification message&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, for demonstration, we set the global rate limit to 10 and the category limit to 3 with
a 60-second rolling window. After three successful category notifications (and a total of
three global notifications), the rate limiter rejects additional requests in the same
window, illustrating how both the global and category limits work together.&lt;/p&gt;
</content:encoded>
      <category>Database</category>
      <category>Python</category>
      <category>System</category>
      <category>Redis</category>
    </item>
    <item>
      <title>Dynamic shell variables</title>
      <link>https://rednafi.com/misc/dynamic-shell-variables/</link>
      <pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/dynamic-shell-variables/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Learn variable indirection in Bash with ${!var} syntax. Build context-aware configs, function dispatch, and dynamic variable name resolution.</description>
      <content:encoded>&lt;p&gt;I came across a weird shell syntax today - dynamic shell variables. It lets you dynamically
construct and access variable names in Bash scripts, which I haven&amp;rsquo;t encountered in any of
the mainstream languages I juggle for work.&lt;/p&gt;
&lt;p&gt;In an actual programming language, you&amp;rsquo;d usually use a hashmap to achieve the same effect,
but directly templating variable names is a quirky shell feature that sometimes comes in
handy.&lt;/p&gt;
&lt;h2 id=&#34;a-primer&#34;&gt;A primer &lt;a class=&#34;anchor&#34; href=&#34;#a-primer&#34; aria-label=&#34;Permalink to A primer&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dynamic shell variables allow shell scripts to define and access variables based on runtime
conditions. Variable indirection (&lt;code&gt;${!var}&lt;/code&gt; syntax) lets you reference the value of a
variable through another variable. This can be useful for managing environment-specific
configurations and function dispatch mechanisms.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# script.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;config_path&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/etc/config&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;config_path&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;The value of \$config_path is: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!var&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;The value of $config_path is: /etc/config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, &lt;code&gt;${!var}&lt;/code&gt; resolves to the value of the variable &lt;code&gt;config_path&lt;/code&gt; because &lt;code&gt;var&lt;/code&gt; contains
its name. This allows you to dynamically decide which variable to reference at runtime.&lt;/p&gt;
&lt;h2 id=&#34;context-aware-environment-management&#34;&gt;Context-aware environment management &lt;a class=&#34;anchor&#34; href=&#34;#context-aware-environment-management&#34; aria-label=&#34;Permalink to Context-aware environment management&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A more practical use of dynamic shell variables is managing environment-specific
configurations. This is particularly handy in scenarios where you have multiple environments
like &lt;code&gt;staging&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt;, each with its own unique configuration settings.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# script.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Define environment-specific configurations dynamically&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;staging_URL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;https://staging.example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;staging_PORT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;8081&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;prod_URL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;https://example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;prod_PORT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Set the current environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Validate input&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$env&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; !&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;staging&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$env&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; !&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;prod&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Invalid environment. Please specify &amp;#39;staging&amp;#39; or &amp;#39;prod&amp;#39;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Dynamically access the environment-specific variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;URL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;_URL&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PORT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;_PORT&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;URL: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!URL&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Port: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!PORT&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Run the script with an environment as the argument:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./script.sh staging&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Output for &lt;code&gt;env=&amp;quot;staging&amp;quot;&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;URL: https://staging.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Port: 8081&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;By passing the environment as an argument, you can switch between environments without
duplicating configuration logic.&lt;/p&gt;
&lt;p&gt;One gotcha to be aware of is that appending text directly to the &lt;code&gt;${!VAR}&lt;/code&gt; syntax (e.g.,
&lt;code&gt;${!env}_URL&lt;/code&gt;) doesn&amp;rsquo;t produce the intended results. Instead of resolving &lt;code&gt;staging_URL&lt;/code&gt;,
this line will print only &lt;code&gt;_URL&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!env&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;_URL&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Output:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;_URL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This happens because &lt;code&gt;${!VAR}&lt;/code&gt; only resolves the value of &lt;code&gt;VAR&lt;/code&gt; and doesn&amp;rsquo;t support direct
concatenation. To avoid this, construct the full variable name (&lt;code&gt;URL=&amp;quot;${env}_URL&amp;quot;&lt;/code&gt;) before
using &lt;code&gt;${!VAR}&lt;/code&gt; for indirect expansion. This ensures the correct variable is accessed.&lt;/p&gt;
&lt;h2 id=&#34;function-dispatch&#34;&gt;Function dispatch &lt;a class=&#34;anchor&#34; href=&#34;#function-dispatch&#34; aria-label=&#34;Permalink to Function dispatch&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another neat use case for dynamic variables is function dispatch - calling the appropriate
function based on runtime conditions. This technique can be used to simplify scripts that
need to handle multiple services or operations.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# script.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Define functions for operations on different services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;web_start&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Starting web service...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;web_stop&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Stopping web service...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;db_status&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Checking database status...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Dynamically bind operation to function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;web_start_function&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;web_start&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;web_stop_function&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;web_stop&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;db_status_function&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;db_status&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Input variables for service and operation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;operation&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Build dynamic function name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;operation&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;_function&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Dispatch function dynamically&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;type&lt;/span&gt; -t &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!func&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;function&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!func&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# Call the dynamically resolved function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Unknown operation: &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$service&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$operation&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Run the script with service and operation as arguments:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./script.sh web start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This returns:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Starting web service...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Similarly, running &lt;code&gt;./script.sh db status&lt;/code&gt; prints:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Checking database status...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;temporary-file-handling&#34;&gt;Temporary file handling &lt;a class=&#34;anchor&#34; href=&#34;#temporary-file-handling&#34; aria-label=&#34;Permalink to Temporary file handling&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dynamic variables can also help manage temporary files or logs in scripts that process
multiple datasets. By dynamically generating variable names, you can track temporary file
paths for each dataset without conflicts.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# script.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Process multiple datasets with temporary files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; dataset in data1 data2 data3&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Dynamically declare a temporary file variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;temp_file_var&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;dataset&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;_temp_file&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$temp_file_var&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/tmp/&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;dataset&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;_processing.tmp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Simulate processing and logging&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Processing &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$dataset&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;...&amp;#34;&lt;/span&gt; &amp;gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!temp_file_var&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    cat &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!temp_file_var&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Clean up (or add a trap to make this more robust)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    rm -f &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!temp_file_var&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Running this prints the following:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Processing data1...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Processing data2...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Processing data3...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, each dataset gets a unique temporary file, managed dynamically by the script. It
eliminates the need for manually creating and tracking file names.&lt;/p&gt;
&lt;p&gt;This works, but like everything else in shell scripts, it can quickly turn into a hairball
if we&amp;rsquo;re not careful. While the syntax is nifty, I find it a bit hard to read at times!&lt;/p&gt;
</content:encoded>
      <category>Shell</category>
      <category>Unix</category>
      <category>TIL</category>
      <category>CLI</category>
    </item>
    <item>
      <title>Link blog in a static site</title>
      <link>https://rednafi.com/misc/link-blog/</link>
      <pubDate>Mon, 06 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/link-blog/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Build a link blog without a backend using Hugo and markdown. Track reading, articles, and activities with yearly detail pages and RSS filtering.</description>
      <content:encoded>&lt;p&gt;One of my 2025 resolutions is doing things that don&amp;rsquo;t scale and doing them faster without
overthinking. The idea is to focus on doing more while worrying less about scalability and
sustainability in the things I do outside of work. With that in mind, I&amp;rsquo;ve been thinking for
a while about tracking some of my out-of-band activities on this blog. The goal is to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List the things I do, books and articles I read, and talks I grok.&lt;/li&gt;
&lt;li&gt;Add some commentary or quote something I liked from the content, verbatim, for posterity.&lt;/li&gt;
&lt;li&gt;Not spam people who just want to read the regular articles.&lt;/li&gt;
&lt;li&gt;Not turn into a content junkie, churning out slop I wouldn&amp;rsquo;t want to read myself.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn&amp;rsquo;t about getting more eyeballs on what I publish. It&amp;rsquo;s about tracking what I do so I
can look back at the end of the year and enjoy a nice little lull of accomplishment. Plus,
having a place to post stuff regularly nudges me to read more, explore more, and do more of
the things I actually want to do.&lt;/p&gt;
&lt;p&gt;Social media is usually where people do this, but digging up old posts and reviewing them
later is a pain. Plus, platforms like Twitter tank your posts&amp;rsquo; visibility if they have links
to other sites. A simple link blog solves all that for me.&lt;/p&gt;
&lt;p&gt;Having a dynamic backend would make setting up a link blog trivial. But I&amp;rsquo;m too lazy to
maintain one. Right now, this site is just a &lt;a href=&#34;https://rednafi.com/misc/behind-the-blog/&#34;&gt;static Hugo build with some custom CSS flair&lt;/a&gt;.
Adding a link blog without a dedicated backend is the tricky part.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t mind maintaining a dynamic site, Simon Willison has a &lt;a href=&#34;https://simonwillison.net/2024/Dec/22/link-blog/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;great piece on running a
link blog (opens in new tab)&#34;&gt;great piece on running a
link blog&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;heres-what-has-worked-for-me&#34;&gt;Here&amp;rsquo;s what has worked for me &lt;a class=&#34;anchor&#34; href=&#34;#heres-what-has-worked-for-me&#34; aria-label=&#34;Permalink to Here&amp;rsquo;s what has worked for me&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I went with a dead simple approach that has worked for me for the past 5 months:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Detail view:&lt;/strong&gt; Have a single markdown page dedicated to each year and add the links
there. See &lt;a href=&#34;https://rednafi.com/feed/2025&#34;&gt;2025 feed detail view&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List view:&lt;/strong&gt; Cluster the detail pages in a list view named &lt;a href=&#34;https://rednafi.com/feed&#34;&gt;feed&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;detail-view&#34;&gt;Detail view &lt;a class=&#34;anchor&#34; href=&#34;#detail-view&#34; aria-label=&#34;Permalink to Detail view&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Each year gets its own detail page where I add links in reverse chronological order. The
content has the following structure in markdown:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;md&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-md&#34; data-lang=&#34;md&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;---&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;2024&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;layout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;---&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;### December 26
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;#### [Reflecting on life – Armin Ronacher][32]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[32]: https://lucumr.pocoo.org/2024/12/26/reflecting-on-life/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;### December 24
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;#### [How I write HTTP services in Go after 13 years – Mat Ryer][31]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[31]: https://grafana.com/blog/2024/02/09/how-i-write-http-services/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Hugo renders the detail view like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blob.rednafi.com/misc/link-blog/image-01-104ac78cde04.png&#34; alt=&#34;Link blog detail view showing curated links with dates and commentary&#34; width=&#34;1722&#34; height=&#34;1590&#34; referrerpolicy=&#34;no-referrer&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;list-view&#34;&gt;List view &lt;a class=&#34;anchor&#34; href=&#34;#list-view&#34; aria-label=&#34;Permalink to List view&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hugo automatically creates a list view from the yearly pages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blob.rednafi.com/misc/link-blog/image-02-af1a5c2c448c.png&#34; alt=&#34;Link blog list view showing yearly feed pages&#34; width=&#34;1636&#34; height=&#34;884&#34; referrerpolicy=&#34;no-referrer&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;To avoid spamming people, the entries in the link blog are filtered out of the main RSS
feed. Also, the client-side search functionality allows me to look for a particular link
without having to maintain any extra infra.&lt;/p&gt;
&lt;p&gt;This solution is simple and has been working well for me. I started it in August last year,
and the &lt;a href=&#34;https://rednafi.com/feed/2024&#34;&gt;2024 feed page&lt;/a&gt; already has enough entries to make me feel good when I reviewed
them at the end of the year.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>Essay</category>
      <category>Hugo</category>
      <category>Web</category>
    </item>
    <item>
      <title>Running only a single instance of a process</title>
      <link>https://rednafi.com/misc/run-single-instance/</link>
      <pubDate>Tue, 31 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/run-single-instance/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Prevent multiple script instances with file locking. Use flock in Bash, fcntl in Python, and syscall.Flock in Go for single-instance processes.</description>
      <content:encoded>&lt;p&gt;I&amp;rsquo;ve been having a ton of fun fiddling with &lt;a href=&#34;https://tailscale.com/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Tailscale (opens in new tab)&#34;&gt;Tailscale&lt;/a&gt; over the past few days. While
setting it up on a server, I came across this &lt;a href=&#34;https://github.com/AT3K/Tailscale-Firewall-Setup/blob/main/update_tailscale_ufw_rules.sh&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;ufw firewall script (opens in new tab)&#34;&gt;ufw firewall script&lt;/a&gt; that configures the
firewall on Linux to ensure direct communication across different nodes in my tailnet. It
has the following block of code that I found interesting (added comments for clarity):&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Define PID file path using script&amp;#39;s name for uniqueness&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PIDFILE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/tmp/&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;basename &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;BASH_SOURCE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[0]%.*&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.pid&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Open file descriptor 200 for the PID file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 200&amp;gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;PIDFILE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Try to acquire a non-blocking lock; exit if the script is already running&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;flock -n &lt;span class=&#34;m&#34;&gt;200&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;BASH_SOURCE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[0]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; already running. Aborting...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; 1&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Store the current process ID (PID) in the lock file for reference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PID&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$$&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;PID&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; 1&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Do work (in the original script, real work happens here)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sleep &lt;span class=&#34;m&#34;&gt;999&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, &lt;code&gt;flock&lt;/code&gt; is a Linux command that ensures only one instance of the script runs at a time
by locking a specified file (e.g., &lt;code&gt;PIDFILE&lt;/code&gt;) through a file descriptor (e.g., &lt;code&gt;200&lt;/code&gt;). If
another process already holds the lock, the script either waits or exits immediately. Above,
it bails with an error message and exit code 1.&lt;/p&gt;
&lt;p&gt;If you try running two instances of this script, the second one will exit with this message:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt;script-name&amp;gt; script is already running. Aborting...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;On most Linux distros, &lt;code&gt;flock&lt;/code&gt; comes along with the coreutils. If not, it&amp;rsquo;s easy to install
with your preferred package manager.&lt;/p&gt;
&lt;h2 id=&#34;a-more-portable-version&#34;&gt;A more portable version &lt;a class=&#34;anchor&#34; href=&#34;#a-more-portable-version&#34; aria-label=&#34;Permalink to A more portable version&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On macOS, the file locking mechanism is different, and &lt;code&gt;flock&lt;/code&gt; doesn&amp;rsquo;t work there. To make
your script portable, you can use &lt;code&gt;mkdir&lt;/code&gt; in the following manner to achieve a similar
result:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;LOCKDIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/tmp/&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;basename &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;BASH_SOURCE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[0]%.*&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.lock&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Try to create the lock directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;LOCKDIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Another instance is running. Aborting...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Set up cleanup for the lock directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;trap&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rmdir \&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;LOCKDIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;\&amp;#34;&amp;#34;&lt;/span&gt; EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Main script logic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Acquired lock, doing important stuff...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ... your script logic ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sleep &lt;span class=&#34;m&#34;&gt;999&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This works because &lt;code&gt;mkdir&lt;/code&gt; is atomic. It creates the lock directory (&lt;code&gt;LOCKDIR&lt;/code&gt;) in &lt;code&gt;/tmp&lt;/code&gt; or
fails if the directory already exists. This acts as a marker for the running instance. If
successful, the script sets up a &lt;code&gt;trap&lt;/code&gt; to remove the directory on exit and continues to the
main logic. If &lt;code&gt;mkdir&lt;/code&gt; fails, it means another instance of the process is running, and the
script exits with a message.&lt;/p&gt;
&lt;p&gt;This is almost as effective as the &lt;code&gt;flock&lt;/code&gt; version. Since I rarely write scripts for
non-Linux environments, either option is fine!&lt;/p&gt;
&lt;h2 id=&#34;with-python&#34;&gt;With Python &lt;a class=&#34;anchor&#34; href=&#34;#with-python&#34; aria-label=&#34;Permalink to With Python&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Oftentimes, I opt for Python when I need to write larger scripts. The same can be achieved
in Python like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;py&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;fcntl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;sys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Use the script name to generate a unique lock file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;LOCKFILE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/tmp/&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;basename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vm&#34;&gt;__file__&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.lock&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;999&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;vm&#34;&gt;__name__&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# Open a file and acquire an exclusive lock&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;LOCKFILE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;w&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lockfile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;fcntl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lockfile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fcntl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;LOCK_EX&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fcntl&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;LOCK_NB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Acquired lock, running script...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;# Main script logic here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;except&lt;/span&gt; &lt;span class=&#34;ne&#34;&gt;BlockingIOError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Another instance is running. Exiting.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;sys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;exit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The script uses &lt;code&gt;fcntl.flock&lt;/code&gt; to prevent multiple instances from running. It creates a lock
file (&lt;code&gt;LOCKFILE&lt;/code&gt;) in the &lt;code&gt;/tmp&lt;/code&gt; directory, named after the scripts filename. When the script
starts, it opens the file in write mode and tries to lock it with &lt;code&gt;fcntl.flock&lt;/code&gt; using an
exclusive lock (&lt;code&gt;LOCK_EX&lt;/code&gt;). The &lt;code&gt;LOCK_NB&lt;/code&gt; flag makes the operation non-blocking. If another
process holds the lock, the script exits with a message.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;This approach works on both Linux and macOS, as both support &lt;code&gt;fcntl&lt;/code&gt; for file-based locks.
The lock is automatically released when the file is closed, either at the end of the
script or the &lt;code&gt;with&lt;/code&gt; block.&lt;/p&gt;

&lt;/blockquote&gt;&lt;h2 id=&#34;with-go&#34;&gt;With Go &lt;a class=&#34;anchor&#34; href=&#34;#with-go&#34; aria-label=&#34;Permalink to With Go&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I was curious about doing it in Go. It&amp;rsquo;s quite similar to Python:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;go&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;path/filepath&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;syscall&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Use the script name (basename) to generate a unique lock file&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lockfile&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Sprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;/tmp/%s.lock&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filepath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;999&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Second&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Open the lock file&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;OpenFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lockfile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;O_CREATE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;O_RDWR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mo&#34;&gt;0644&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Failed to open lock file:&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Exit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Try to acquire an exclusive lock&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;syscall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Flock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Fd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;syscall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;LOCK_EX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;syscall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;LOCK_NB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Another instance is running. Exiting.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Exit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Release the lock on exit&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;syscall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Flock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Fd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;syscall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;LOCK_UN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Acquired lock, running script...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Main script logic&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Like the Python example, this uses &lt;code&gt;syscall.Flock&lt;/code&gt; to prevent multiple script instances. It
creates a lock file based on the script&amp;rsquo;s name using &lt;code&gt;filepath.Base(os.Args[0])&lt;/code&gt; and stores
it in &lt;code&gt;/tmp&lt;/code&gt;. The script tries to acquire an exclusive, non-blocking lock
(&lt;code&gt;LOCK_EX | LOCK_NB&lt;/code&gt;). If unavailable, it exits with a message. The lock is automatically
released when the file is closed in the &lt;code&gt;defer&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;Underneath, Go makes sure that &lt;code&gt;syscall.Flock&lt;/code&gt; works on both macOS and Linux.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>Shell</category>
      <category>Unix</category>
      <category>Python</category>
      <category>Go</category>
    </item>
    <item>
      <title>SSH saga</title>
      <link>https://rednafi.com/misc/ssh-saga/</link>
      <pubDate>Tue, 17 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/ssh-saga/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Complete SSH setup guide: key pairs, authorized_keys, sshd_config, ssh_config, known_hosts, agent forwarding, and hardening for secure remote access.</description>
      <content:encoded>&lt;p&gt;Setting up SSH access to a new VM usually follows the same routine: generate a key pair,
copy it to the VM, tweak some configs, confirm the host&amp;rsquo;s identity, and maybe set up an
agent to avoid typing passphrases all day. Tools like cloud-init and Ansible handle most of
the setup for me now, so I rarely think about it. But I realized I don&amp;rsquo;t fully understand
how all the parts work together.&lt;/p&gt;
&lt;p&gt;This post attempts to give an overview of what happens when you type &lt;code&gt;ssh user@host&lt;/code&gt;. It
covers key pairs, &lt;code&gt;authorized_keys&lt;/code&gt;, &lt;code&gt;sshd_config&lt;/code&gt;, &lt;code&gt;~/.ssh/config&lt;/code&gt;, &lt;code&gt;known_hosts&lt;/code&gt;, and how
they all fit together.&lt;/p&gt;
&lt;h2 id=&#34;a-new-vm-in-the-void&#34;&gt;A new VM in the void &lt;a class=&#34;anchor&#34; href=&#34;#a-new-vm-in-the-void&#34; aria-label=&#34;Permalink to A new VM in the void&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;ve provisioned a new VM and need key-based SSH access. This involves generating a key
pair on your local machine, installing the public key on the remote VM, and ensuring the SSH
daemon (&lt;code&gt;sshd&lt;/code&gt;) on the VM trusts it. Done right, &lt;code&gt;ssh user@host&lt;/code&gt; drops you into a shell
without a password prompt.&lt;/p&gt;
&lt;p&gt;First, generate a key pair on your &lt;em&gt;local machine&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh-keygen -t ed25519 -C &lt;span class=&#34;s2&#34;&gt;&amp;#34;your_email@example.com&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This creates two files: a private key (&lt;code&gt;~/.ssh/id_ed25519&lt;/code&gt;) and a public key
(&lt;code&gt;~/.ssh/id_ed25519.pub&lt;/code&gt;). The private key stays local. The public key is shared.&lt;/p&gt;
&lt;h2 id=&#34;your-local-fortress&#34;&gt;Your local fortress &lt;a class=&#34;anchor&#34; href=&#34;#your-local-fortress&#34; aria-label=&#34;Permalink to Your local fortress&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Your local private key proves your identity and must be protected (&lt;code&gt;~/.ssh/id_ed25519&lt;/code&gt;). The
public key (&lt;code&gt;~/.ssh/id_ed25519.pub&lt;/code&gt;) gets copied to the VM.&lt;/p&gt;
&lt;p&gt;To view the public key locally:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat ~/.ssh/id_ed25519.pub
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Output: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG... user@local&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Copy this public key to the VM.&lt;/p&gt;
&lt;h2 id=&#34;the-remote-gatekeeper&#34;&gt;The remote gatekeeper &lt;a class=&#34;anchor&#34; href=&#34;#the-remote-gatekeeper&#34; aria-label=&#34;Permalink to The remote gatekeeper&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On the VM, &lt;code&gt;sshd&lt;/code&gt; listens for connections and authenticates users. Its configuration file,
&lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;, defines policies: whether password logins are allowed, which keys
are trusted, and which crypto settings to use. A hardened snippet might look like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /etc/ssh/sshd_config (on the VM)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PermitRootLogin no
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PasswordAuthentication no
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PubkeyAuthentication yes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AuthorizedKeysFile     .ssh/authorized_keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;With &lt;code&gt;PasswordAuthentication no&lt;/code&gt;, only keys can unlock access.&lt;/p&gt;
&lt;h2 id=&#34;authorized-keys-and-the-handshake&#34;&gt;Authorized keys and the handshake &lt;a class=&#34;anchor&#34; href=&#34;#authorized-keys-and-the-handshake&#34; aria-label=&#34;Permalink to Authorized keys and the handshake&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file on the VM decides who gets access. Add your public key
there to tell &lt;code&gt;sshd&lt;/code&gt; that anyone holding the matching private key (you) can connect.&lt;/p&gt;
&lt;p&gt;On the VM, under the user&amp;rsquo;s home directory:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir -p ~/.ssh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chmod &lt;span class=&#34;m&#34;&gt;700&lt;/span&gt; ~/.ssh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG... user@local&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &amp;gt;&amp;gt; ~/.ssh/authorized_keys
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chmod &lt;span class=&#34;m&#34;&gt;600&lt;/span&gt; ~/.ssh/authorized_keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now when you run &lt;code&gt;ssh user@host&lt;/code&gt;, the server matches your key to one in &lt;code&gt;authorized_keys&lt;/code&gt;
and lets you in.&lt;/p&gt;
&lt;h2 id=&#34;the-client-and-its-configs&#34;&gt;The client and its configs &lt;a class=&#34;anchor&#34; href=&#34;#the-client-and-its-configs&#34; aria-label=&#34;Permalink to The client and its configs&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Your local SSH client can be configured via &lt;code&gt;~/.ssh/config&lt;/code&gt; to simplify hostnames, ports,
and key paths:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ~/.ssh/config (on your local machine)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Host myvm
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    HostName 203.0.113.10
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    User ubuntu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    IdentityFile ~/.ssh/id_ed25519
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Port &lt;span class=&#34;m&#34;&gt;22&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now you can connect with:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh myvm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;known-hosts-and-server-identity&#34;&gt;Known hosts and server identity &lt;a class=&#34;anchor&#34; href=&#34;#known-hosts-and-server-identity&#34; aria-label=&#34;Permalink to Known hosts and server identity&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you connect to the VM for the first time, SSH prompts you to confirm its identity.
Accepting it adds the server&amp;rsquo;s host key to &lt;code&gt;~/.ssh/known_hosts&lt;/code&gt;. SSH will detect any
identity changes during subsequent connections:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;203.0.113.10 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This prevents man-in-the-middle attacks.&lt;/p&gt;
&lt;h2 id=&#34;agent-and-forwarding&#34;&gt;Agent and forwarding &lt;a class=&#34;anchor&#34; href=&#34;#agent-and-forwarding&#34; aria-label=&#34;Permalink to Agent and forwarding&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If your private key has a passphrase, typing it constantly is annoying. &lt;code&gt;ssh-agent&lt;/code&gt; caches
the key in memory, so you only unlock it once:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;eval&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;ssh-agent -s&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh-add ~/.ssh/id_ed25519
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Enter passphrase once&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For hopping through multiple servers (&lt;code&gt;local → bastion → internal server&lt;/code&gt;), enable agent
forwarding:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ~/.ssh/config (local)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Host myvm
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    HostName 203.0.113.10
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    User ubuntu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    IdentityFile ~/.ssh/id_ed25519
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ForwardAgent yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, you can connect through intermediary hosts without copying private keys around.&lt;/p&gt;
&lt;h2 id=&#34;configuring-and-hardening-the-daemon&#34;&gt;Configuring and hardening the daemon &lt;a class=&#34;anchor&#34; href=&#34;#configuring-and-hardening-the-daemon&#34; aria-label=&#34;Permalink to Configuring and hardening the daemon&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On the VM, refine &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; to enforce stricter security:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /etc/ssh/sshd_config (on the VM)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PasswordAuthentication no
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PermitRootLogin prohibit-password
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AllowUsers ubuntu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PubkeyAuthentication yes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KexAlgorithms curve25519-sha256@libssh.org
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MACs hmac-sha2-512-etm@openssh.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;These settings ensure that only trusted keys with modern crypto algorithms can connect.&lt;/p&gt;
&lt;h2 id=&#34;bringing-it-all-together&#34;&gt;Bringing it all together &lt;a class=&#34;anchor&#34; href=&#34;#bringing-it-all-together&#34; aria-label=&#34;Permalink to Bringing it all together&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a quick summary of setting up SSH connection to a new machine:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate a key pair on your local machine.&lt;/li&gt;
&lt;li&gt;Copy the public key to the &lt;code&gt;authorized_keys&lt;/code&gt; file on the VM.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;sshd_config&lt;/code&gt; on the VM to allow key-based authentication.&lt;/li&gt;
&lt;li&gt;Set up &lt;code&gt;~/.ssh/config&lt;/code&gt; on your local machine to simplify SSH commands (e.g., &lt;code&gt;ssh myvm&lt;/code&gt;
instead of &lt;code&gt;ssh &amp;lt;vm-ip&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Confirm the server&amp;rsquo;s identity and save it to &lt;code&gt;known_hosts&lt;/code&gt; on your local machine.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ssh-agent&lt;/code&gt; on your local machine to cache your private key and avoid typing the
passphrase repeatedly.&lt;/li&gt;
&lt;li&gt;Enable agent forwarding in your SSH config to connect through intermediary servers
without copying keys.&lt;/li&gt;
&lt;li&gt;Harden &lt;code&gt;sshd_config&lt;/code&gt; on the VM to enforce modern crypto algorithms and stricter security
policies.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;┌──────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│      LOCAL           │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│  ~/.ssh/config       │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│  ~/.ssh/id_*         │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│  ~/.ssh/known_hosts  │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│  ssh-agent           │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└───▲───────┬──────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       │ SSH Connection (Port 22)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;┌────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│     REMOTE             │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│ /etc/ssh/sshd_config   │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│ ~/.ssh/authorized_keys │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│ sshd daemon            │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└────────────────────────┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;</content:encoded>
      <category>Shell</category>
      <category>Unix</category>
      <category>Networking</category>
      <category>Security</category>
    </item>
    <item>
      <title>Docker mount revisited</title>
      <link>https://rednafi.com/misc/docker-mount/</link>
      <pubDate>Tue, 22 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/docker-mount/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Master Docker mount types: volumes, bind mounts, tmpfs, and build cache. Clear syntax comparison between -v and --mount options with docker-compose.</description>
      <content:encoded>&lt;p&gt;I always get tripped up by Docker&amp;rsquo;s different mount types and their syntax, whether I&amp;rsquo;m
stringing together some CLI commands or writing a &lt;code&gt;docker-compose&lt;/code&gt; file. Docker&amp;rsquo;s docs cover
these, but for me, the confusion often comes from how &amp;ldquo;bind&amp;rdquo; is used in various contexts and
how &amp;ldquo;volume&amp;rdquo; and &amp;ldquo;bind&amp;rdquo; sometimes get mixed up in the documentation.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my attempt to disentangle some of my most-used mount commands.&lt;/p&gt;
&lt;h2 id=&#34;volume-mounts&#34;&gt;Volume mounts &lt;a class=&#34;anchor&#34; href=&#34;#volume-mounts&#34; aria-label=&#34;Permalink to Volume mounts&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/storage/volumes/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Volume mounts (opens in new tab)&#34;&gt;Volume mounts&lt;/a&gt; let you store data outside the container in a location managed by Docker.
The data persists even after the container stops. On non-Linux systems, volume mounts are
faster than bind mounts because data doesn&amp;rsquo;t need to cross the virtualization boundary.&lt;/p&gt;
&lt;h3 id=&#34;the--v-option&#34;&gt;The &lt;code&gt;-v&lt;/code&gt; option &lt;a class=&#34;anchor&#34; href=&#34;#the--v-option&#34; aria-label=&#34;Permalink to The -v option&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;-v&lt;/code&gt; flag is the older and more common way to define volume mounts in the Docker CLI.
For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run -v myvolume:/usr/share/nginx/html:ro nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s what each part means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;myvolume&lt;/code&gt;: The name of the Docker-managed volume on the host.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/share/nginx/html&lt;/code&gt;: The mount point inside the container.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:ro&lt;/code&gt;: Mounts the volume as read-only inside the container. The host can still write to
the volume, but the container cannot.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The general syntax is:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-v &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;SOURCE&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;:&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;TARGET&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;:&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;OPTIONS&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;It can be tricky to remember which part is the host and which is the container, especially
since with volumes, the &lt;code&gt;SOURCE&lt;/code&gt; is a volume name, not a host path.&lt;/p&gt;
&lt;h3 id=&#34;the---mount-option&#34;&gt;The &lt;code&gt;--mount&lt;/code&gt; option &lt;a class=&#34;anchor&#34; href=&#34;#the---mount-option&#34; aria-label=&#34;Permalink to The --mount option&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To make things clearer, Docker introduced the &lt;code&gt;--mount&lt;/code&gt; option, which uses key-value pairs.
The same volume mount using &lt;code&gt;--mount&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --mount &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;volume,source&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;myvolume,target&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/usr/share/nginx/html,readonly nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Or using shorthands:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --mount &lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;volume,src&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;myvolume,dst&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/usr/share/nginx/html,ro nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I find this syntax more explicit and less error-prone, even if it&amp;rsquo;s a bit more verbose.&lt;/p&gt;
&lt;h3 id=&#34;in-docker-composeyml&#34;&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt; &lt;a class=&#34;anchor&#34; href=&#34;#in-docker-composeyml&#34; aria-label=&#34;Permalink to In docker-compose.yml&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;docker-compose&lt;/code&gt;, volumes can be defined using both the old and new syntax. Here&amp;rsquo;s how
they compare:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Old style&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;yaml&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;myvolume:/usr/share/nginx/html:ro&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;myvolume:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;New style&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;yaml&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;volume&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;source&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;myvolume&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;read_only&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;myvolume:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I prefer the new style because it reduces ambiguity and makes the configuration clearer.&lt;/p&gt;
&lt;h2 id=&#34;bind-mounts&#34;&gt;Bind mounts &lt;a class=&#34;anchor&#34; href=&#34;#bind-mounts&#34; aria-label=&#34;Permalink to Bind mounts&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/storage/bind-mounts/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Bind mounts (opens in new tab)&#34;&gt;Bind mounts&lt;/a&gt; let you directly mount a file or directory from the host into the container.
This is especially useful in development when you want the container to have access to your
code or data.&lt;/p&gt;
&lt;p&gt;The key difference between volume mounts and bind mounts is that volumes are fully managed
by Docker and stored in a special location, while bind mounts rely on specific host paths.
Volumes are more portable and isolated from the host, whereas bind mounts give you direct
access to host files but can introduce permission issues and depend on the exact file
structure of the host.&lt;/p&gt;
&lt;h3 id=&#34;the--v-option-1&#34;&gt;The &lt;code&gt;-v&lt;/code&gt; option &lt;a class=&#34;anchor&#34; href=&#34;#the--v-option-1&#34; aria-label=&#34;Permalink to The -v option&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using the &lt;code&gt;-v&lt;/code&gt; syntax for bind mounts:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run -v /path/on/host:/usr/share/nginx/html:ro nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/path/on/host&lt;/code&gt;: The path on the host machine. This must be an absolute path.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/share/nginx/html&lt;/code&gt;: The mount point inside the container.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:ro&lt;/code&gt;: Mounts the directory as read-only inside the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;the---mount-option-1&#34;&gt;The &lt;code&gt;--mount&lt;/code&gt; option &lt;a class=&#34;anchor&#34; href=&#34;#the---mount-option-1&#34; aria-label=&#34;Permalink to The --mount option&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using &lt;code&gt;--mount&lt;/code&gt; for a bind mount:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --mount &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;bind,src&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/path/on/host,dst&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/usr/share/nginx/html,ro nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This syntax makes it clear that you&amp;rsquo;re using a bind mount and specifies exactly which paths
are involved.&lt;/p&gt;
&lt;h3 id=&#34;in-docker-composeyml-1&#34;&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt; &lt;a class=&#34;anchor&#34; href=&#34;#in-docker-composeyml-1&#34; aria-label=&#34;Permalink to In docker-compose.yml&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;docker-compose&lt;/code&gt;, bind mounts can be specified like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Old style&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;yaml&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./path/on/host:/usr/share/nginx/html:ro&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;New style&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;yaml&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;bind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;source&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;./path/on/host&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;read_only&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In &lt;code&gt;docker-compose&lt;/code&gt;, if you specify a &lt;code&gt;source&lt;/code&gt; that doesn&amp;rsquo;t start with &lt;code&gt;/&lt;/code&gt; (an
absolute path) or &lt;code&gt;./&lt;/code&gt; (a relative path), Docker might think you&amp;rsquo;re referring to a volume.
To ensure it&amp;rsquo;s interpreted as a bind mount, start the path with &lt;code&gt;./&lt;/code&gt; or &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;/blockquote&gt;&lt;h2 id=&#34;tmpfs-mounts&#34;&gt;Tmpfs mounts &lt;a class=&#34;anchor&#34; href=&#34;#tmpfs-mounts&#34; aria-label=&#34;Permalink to Tmpfs mounts&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/storage/tmpfs/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Tmpfs mounts (opens in new tab)&#34;&gt;Tmpfs mounts&lt;/a&gt; store data in the host&amp;rsquo;s memory, not on disk. This makes them ideal for
temporary storage that doesn&amp;rsquo;t need to persist after the container stops. They&amp;rsquo;re great for
things like caches or scratch space.&lt;/p&gt;
&lt;h3 id=&#34;the---tmpfs-option&#34;&gt;The &lt;code&gt;--tmpfs&lt;/code&gt; option &lt;a class=&#34;anchor&#34; href=&#34;#the---tmpfs-option&#34; aria-label=&#34;Permalink to The --tmpfs option&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Docker provides a &lt;code&gt;--tmpfs&lt;/code&gt; option to create a tmpfs mount more concisely:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run --tmpfs /app/tmp:rw,size&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;64m nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/app/tmp&lt;/code&gt;: The target directory inside the container.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rw&lt;/code&gt;: This option allows read and write access to the tmpfs mount.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size=64m&lt;/code&gt;: Sets the size of the tmpfs mount to 64 MB.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;the---mount-option-2&#34;&gt;The &lt;code&gt;--mount&lt;/code&gt; option &lt;a class=&#34;anchor&#34; href=&#34;#the---mount-option-2&#34; aria-label=&#34;Permalink to The --mount option&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alternatively, using the more flexible &lt;code&gt;--mount&lt;/code&gt; option:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker run &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --mount &lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;tmpfs,target&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/app/tmp,tmpfs-size&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;64m,tmpfs-mode&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1777&lt;/span&gt; nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s what each part means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;type=tmpfs&lt;/code&gt;: Specifies that this is a tmpfs mount, using the host&amp;rsquo;s memory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target=/app/tmp&lt;/code&gt;: The directory inside the container where the tmpfs mount is mounted.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tmpfs-size=64m&lt;/code&gt;: Limits the size of the tmpfs mount to 64 MB.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tmpfs-mode=1777&lt;/code&gt;: Sets permissions for the tmpfs mount (1777 grants read, write, and
execute permissions to everyone).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;in-docker-composeyml-2&#34;&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt; &lt;a class=&#34;anchor&#34; href=&#34;#in-docker-composeyml-2&#34; aria-label=&#34;Permalink to In docker-compose.yml&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;docker-compose&lt;/code&gt;, tmpfs mounts can be defined using both the old and new syntax.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Old style&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;yaml&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;tmpfs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;/app/tmp:size=64m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;New style&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;yaml&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;tmpfs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;/app/tmp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;tmpfs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;64m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;mode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1777&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;build-cache-mounts&#34;&gt;Build cache mounts &lt;a class=&#34;anchor&#34; href=&#34;#build-cache-mounts&#34; aria-label=&#34;Permalink to Build cache mounts&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/build/cache/optimize/#use-cache-mounts&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Build cache mounts (opens in new tab)&#34;&gt;Build cache mounts&lt;/a&gt; help speed up Docker image builds by caching intermediate files like
package downloads or compiled artifacts. They&amp;rsquo;re used during the build process and aren&amp;rsquo;t
part of the final container image.&lt;/p&gt;
&lt;p&gt;In a &lt;code&gt;Dockerfile&lt;/code&gt;, you might use a build cache like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;dockerfile&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; --mount&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;cache,target&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/var/cache/apt &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    apt-get update &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install -y curl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s what each option does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--mount=type=cache&lt;/code&gt;: Defines a cache mount that stores the files from the &lt;code&gt;apt-get&lt;/code&gt;
commands to speed up future builds by reusing the downloaded packages.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target=/var/cache/apt&lt;/code&gt;: Specifies the location inside the container where the cache will
be stored during the build process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In my Python projects, I cache my dependencies and install them with &lt;code&gt;uv&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;dockerfile&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; --mount&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;cache,target&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/root/.cache/uv &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --mount&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;bind,source&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;uv.lock,target&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;uv.lock &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --mount&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;bind,source&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;pyproject.toml,target&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;pyproject.toml &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    uv sync --no-install-project --locked --no-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Above, the cache mount at &lt;code&gt;/root/.cache/uv&lt;/code&gt; saves dependencies so they don&amp;rsquo;t need to be
re-downloaded in future builds if nothing changes. The bind mounts provide access to
&lt;code&gt;uv.lock&lt;/code&gt; and &lt;code&gt;pyproject.toml&lt;/code&gt; from the host, allowing the container to read these config
files during the build. Any changes to the files are picked up, while cached dependencies
are reused unless the configurations have been updated.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>TIL</category>
      <category>Docker</category>
      <category>DevOps</category>
    </item>
    <item>
      <title>Discovering direnv</title>
      <link>https://rednafi.com/misc/direnv/</link>
      <pubDate>Wed, 02 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/direnv/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Automate environment variables per directory with direnv. Load .envrc files on entry, unload on exit. Integrate with Python venv and uv workflow.</description>
      <content:encoded>&lt;p&gt;I&amp;rsquo;m not a big fan of shims - code that messes with commands in the shell or prompt. That&amp;rsquo;s
why, aside from occasional dabbling, I tend to eschew tools like &lt;code&gt;asdf&lt;/code&gt; or &lt;code&gt;pyenv&lt;/code&gt; and just
use &lt;code&gt;apt&lt;/code&gt; or &lt;code&gt;brew&lt;/code&gt; for installs, depending on the OS.&lt;/p&gt;
&lt;p&gt;Then recently, I saw &lt;a href=&#34;https://x.com/hynek/status/1838076629249044533&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Hynek extolling direnv (opens in new tab)&#34;&gt;Hynek extolling direnv&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;If you&amp;rsquo;re old-school like me, my &lt;code&gt;.envrc&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;uv sync --frozen
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;source&lt;/span&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The sync ensures there&amp;rsquo;s always a &lt;code&gt;.venv&lt;/code&gt;, so no memory-baking required.&lt;/p&gt;

&lt;/blockquote&gt;&lt;p&gt;And &lt;a href=&#34;https://x.com/brandur/status/1837104038854164645&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Brandur doing the same (opens in new tab)&#34;&gt;Brandur doing the same&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;This is embarrassing, but after using direnv for 10+ years, I only discovered the
&lt;code&gt;source_env&lt;/code&gt; directive yesterday.&lt;/p&gt;
&lt;p&gt;Game changer. I used it to improve our project&amp;rsquo;s dev configuration ergonomics so new
environment variables are easily distributed via Git.&lt;/p&gt;

&lt;/blockquote&gt;&lt;p&gt;So I got curious and wanted to try the tool to see if it fits into my workflow, or if I&amp;rsquo;ll
quickly abandon it when something goes wrong.&lt;/p&gt;
&lt;p&gt;When I first visited their &lt;a href=&#34;https://direnv.net/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;landing page (opens in new tab)&#34;&gt;landing page&lt;/a&gt;, I was a bit confused by the tagline:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;direnv – unclutter your .profile&lt;/p&gt;

&lt;/blockquote&gt;&lt;p&gt;But I don&amp;rsquo;t have anything custom in my &lt;code&gt;.profile&lt;/code&gt;, or more specifically, my &lt;code&gt;.zprofile&lt;/code&gt;.
Here&amp;rsquo;s what&amp;rsquo;s in it currently:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat ~/.zprofile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;eval &amp;#34;$(/opt/homebrew/bin/brew shellenv)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Added by OrbStack: command-line tools and integration
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;source ~/.orbstack/shell/init.zsh 2&amp;gt;/dev/null || :&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then I realized that &lt;code&gt;.profile&lt;/code&gt; is used here as a general term for various configuration
files like &lt;code&gt;.*profile&lt;/code&gt;, &lt;code&gt;.*rc&lt;/code&gt;, and &lt;code&gt;.*env&lt;/code&gt;. I have quite a bit set up in both my &lt;code&gt;~/.zshrc&lt;/code&gt;
and &lt;code&gt;~/.zshenv&lt;/code&gt; - a mix of global and project-specific commands and environment variables.&lt;/p&gt;
&lt;p&gt;To explain: &lt;code&gt;.*profile&lt;/code&gt; files (like &lt;code&gt;.profile&lt;/code&gt; or &lt;code&gt;.bash_profile&lt;/code&gt;) are used by login shells,
which are started when you log into a system, such as through SSH or a terminal login. In
contrast, files like &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt; are for interactive shells, meaning they run when
you open a new terminal window or tab. For Zsh, &lt;code&gt;.zshenv&lt;/code&gt; is sourced by all types of
shells - both login and interactive - making it useful for global environment settings.&lt;/p&gt;
&lt;h2 id=&#34;what-problem-it-solves&#34;&gt;What problem it solves &lt;a class=&#34;anchor&#34; href=&#34;#what-problem-it-solves&#34; aria-label=&#34;Permalink to What problem it solves&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Direnv solves the hassle of managing environment variables across different projects by
automatically loading them when you enter a directory and unloading them when you leave. It
keeps your global environment clean and avoids cluttering up your shell configuration files.&lt;/p&gt;
&lt;p&gt;It checks for an &lt;code&gt;.envrc&lt;/code&gt; (or &lt;code&gt;.env&lt;/code&gt;) file in the current or parent directories before each
prompt. If found and authorized, it loads the file into a bash sub-shell and applies the
environment variables to the current shell.&lt;/p&gt;
&lt;p&gt;It supports hooks for common shells like Bash, Zsh, Tcsh, and Fish, allowing you to manage
project-specific environment variables without cluttering your &lt;code&gt;~/.profile&lt;/code&gt;. Since it&amp;rsquo;s a
fast, single static executable, direnv runs seamlessly and is language-agnostic, meaning you
can easily use it alongside tools like &lt;code&gt;rbenv&lt;/code&gt;, &lt;code&gt;pyenv&lt;/code&gt;, and &lt;code&gt;phpenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You might argue that &lt;code&gt;source .env&lt;/code&gt; works just fine, but it&amp;rsquo;s an extra step to remember.
Also, being able to communicate the project-specific environment commands and variables, and
having them sourced automatically, is a nice bonus.&lt;/p&gt;
&lt;h2 id=&#34;why-envrc-file-and-not-just-a-plain-env-file&#34;&gt;Why .envrc file and not just a plain .env file &lt;a class=&#34;anchor&#34; href=&#34;#why-envrc-file-and-not-just-a-plain-env-file&#34; aria-label=&#34;Permalink to Why .envrc file and not just a plain .env file&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This was the first question that came to my mind: why not just use a &lt;code&gt;.env&lt;/code&gt; file? Why
introduce another configuration file? Grokking the docs clarified things.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;.envrc&lt;/code&gt; file is treated like a shell script, where you can also list arbitrary shell
commands that you want to be executed when you enter a project directory. You can&amp;rsquo;t do that
with a plain &lt;code&gt;.env&lt;/code&gt; file. However, direnv does support &lt;code&gt;.env&lt;/code&gt; files too.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s such a simple idea that opens up many possibilities.&lt;/p&gt;
&lt;h2 id=&#34;how-i-use-it&#34;&gt;How I use it &lt;a class=&#34;anchor&#34; href=&#34;#how-i-use-it&#34; aria-label=&#34;Permalink to How I use it&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are a few things I&amp;rsquo;m using it for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatically loading environment variables from a &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Loading different sets of values for the same environment keys, e.g., local vs. staging
values.&lt;/li&gt;
&lt;li&gt;Activating the virtual environment when I enter the directory of a Python project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s say you want to load your environment variables automatically when you &lt;code&gt;cd&lt;/code&gt; into a
directory and have them removed from the shell environment when you leave it. Suppose the
project directory looks like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;svc/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .env
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .env.staging
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── .envrc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file contains environment variables for local development:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;FOO=&amp;#34;foo&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BAR=&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;And the &lt;code&gt;.env.staging&lt;/code&gt; file contains the variables for staging:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;FOO=&amp;#34;foo-staging&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BAR=&amp;#34;bar-staging&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;.envrc&lt;/code&gt; file can have just one command to load the default &lt;code&gt;.env&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;dotenv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, from the &lt;code&gt;svc&lt;/code&gt; directory, you&amp;rsquo;ll need to allow direnv to load the environment variables
into the current shell:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;direnv allow&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This prints:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;direnv: loading ~/canvas/rednafi.com/svc/.envrc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;direnv: export +BAR +FOO&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can now print the values of the environment variables like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;FOO&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-default&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;BAR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-default&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This returns:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;foo
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;bar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If you want to load different variables depending on the environment, you can add the
following shell script to the &lt;code&gt;.envrc&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;ENVIRONMENT&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;staging&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; -f &lt;span class=&#34;s2&#34;&gt;&amp;#34;.env.staging&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      dotenv .env.staging
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  *&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; -f &lt;span class=&#34;s2&#34;&gt;&amp;#34;.env&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      dotenv
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;esac&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The script loads the &lt;code&gt;.env.staging&lt;/code&gt; file if the value of &lt;code&gt;$ENVIRONMENT&lt;/code&gt; is &lt;code&gt;staging&lt;/code&gt;;
otherwise, it loads the default &lt;code&gt;.env&lt;/code&gt; file. From the &lt;code&gt;svc&lt;/code&gt; root, run:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;direnv allow&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This will still load the variables from &lt;code&gt;.env&lt;/code&gt;. To load variables from &lt;code&gt;.env.staging&lt;/code&gt;, run:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;ENVIRONMENT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;staging &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; direnv allow&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This time, printing the variables returns the staging values:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;foo-staging
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;bar-staging&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Oh, and when you leave the directory, the environment variables will be automatically
unloaded from your working shell.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ..&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;direnv: unloading&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can do a lot more with the idea, but going overboard with environment variables can be
risky. You don&amp;rsquo;t want to accidentally load something into the environment you didn&amp;rsquo;t intend
to. Keeping it simple with sane defaults is the way to go.&lt;/p&gt;
&lt;p&gt;Like Hynek, I&amp;rsquo;ve adopted &lt;a href=&#34;https://github.com/astral-sh/uv&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;uv (opens in new tab)&#34;&gt;uv&lt;/a&gt; in my Python workflow, and now my default &lt;code&gt;.envrc&lt;/code&gt; has these
two commands:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;uv sync --frozen
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;source&lt;/span&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The first command updates the project&amp;rsquo;s environment without changing the &lt;code&gt;uv.lock&lt;/code&gt; file, and
the second ensures I never need to remember to activate the virtual environment before
running commands. Now, when I &lt;code&gt;cd&lt;/code&gt; into a Python project and run:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$VIRTUAL_ENV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;It shows that the local &lt;code&gt;.venv&lt;/code&gt; is active:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/Users/rednafi/canvas/rednafi.com/.venv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;No more worrying about mucking up my global Python installation while running some commands.&lt;/p&gt;
&lt;p&gt;Another neat directive is &lt;code&gt;source_up&lt;/code&gt;, which lets you inherit environment variables from the
parent directory. Normally, when you move into a child directory, direnv unloads the parent
directory&amp;rsquo;s environment variables. But with the &lt;code&gt;source_up&lt;/code&gt; directive in your &lt;code&gt;.envrc&lt;/code&gt;,
it&amp;rsquo;ll keep those variables around in the child directory.&lt;/p&gt;
&lt;p&gt;Then there&amp;rsquo;s the &lt;code&gt;source_env&lt;/code&gt; directive, which lets you pull one &lt;code&gt;.envrc&lt;/code&gt; file into another.
So, if you&amp;rsquo;ve got some common, non-secret variables in an &lt;code&gt;.envrc.local&lt;/code&gt; file, you can
easily reuse them in your &lt;code&gt;.envrc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example &lt;code&gt;.envrc.local&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;API_URL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;http://localhost:5222&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;postgres://localhost:5432/project-db&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can import &lt;code&gt;.env.local&lt;/code&gt; into the &lt;code&gt;.envrc&lt;/code&gt; file like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;source_env .envrc.local
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Other commands and variables go here&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I haven&amp;rsquo;t used &lt;code&gt;source_env&lt;/code&gt; much yet, but I love the possibilities it unlocks.&lt;/p&gt;
&lt;p&gt;The biggest reason I&amp;rsquo;ve adopted it everywhere is that it lets me share my shell environment
variables and the magic commands without having anything stashed away in my &lt;code&gt;~/.zshrc&lt;/code&gt; or
&lt;code&gt;~/.zshenv&lt;/code&gt;, so there&amp;rsquo;s no need for out-of-band communication.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>TIL</category>
      <category>Shell</category>
      <category>Unix</category>
      <category>CLI</category>
    </item>
    <item>
      <title>Notes on building event-driven systems</title>
      <link>https://rednafi.com/misc/notes-on-event-driven-systems/</link>
      <pubDate>Sat, 21 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/notes-on-event-driven-systems/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Deep dive into event-driven architecture patterns. Learn publish-subscribe, CQRS, outbox pattern, eventual consistency, and handling microservice coupling.</description>
      <content:encoded>&lt;p&gt;I spent the evening watching this incredibly grokkable talk on event-driven services by
James Eastham at NDC London 2024. Below is a cleaned-up version of my notes.&lt;/p&gt;
&lt;p&gt;I highly recommend watching the full talk if you&amp;rsquo;re interested before reading this
distillation.&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
			&lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/qcJASFx-F5g?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
		&lt;/div&gt;

&lt;h2 id=&#34;the-curse-of-tightly-coupled-microservices&#34;&gt;The curse of tightly coupled microservices &lt;a class=&#34;anchor&#34; href=&#34;#the-curse-of-tightly-coupled-microservices&#34; aria-label=&#34;Permalink to The curse of tightly coupled microservices&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Microservices often start with HTTP-based request-response communication, which seems
straightforward but quickly becomes a pain as systems grow. Coupling - where one service
depends on another - creates a few issues. Take the &lt;em&gt;order processing service&lt;/em&gt; in a
fictional Plant-Based Pizza company. It has to talk to the &lt;em&gt;pickup service&lt;/em&gt;, &lt;em&gt;delivery
service&lt;/em&gt;, &lt;em&gt;kitchen&lt;/em&gt;, and &lt;em&gt;loyalty point&lt;/em&gt; service. They&amp;rsquo;re all tied together, so if one
fails, the whole system could go down.&lt;/p&gt;
&lt;p&gt;The system relies on all services being up at the same time, which causes issues when any
service crashes. Even something like loyalty points can take the whole thing offline, making
you wonder if the order processing service really needs to care about that.&lt;/p&gt;
&lt;p&gt;On top of that, there&amp;rsquo;s semantic coupling - things like data formats. &lt;em&gt;&amp;ldquo;How do you handle
null values in strings? What casing is your JSON using - camelCase?&amp;rdquo;&lt;/em&gt; These details might
seem minor, but in tightly coupled systems, they pile up, making the integrations fragile
and complicated.&lt;/p&gt;
&lt;h2 id=&#34;what-event-driven-architecture-solves&#34;&gt;What event-driven architecture solves &lt;a class=&#34;anchor&#34; href=&#34;#what-event-driven-architecture-solves&#34; aria-label=&#34;Permalink to What event-driven architecture solves&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Event-driven architecture offers a way to decouple services. Instead of one service needing
to communicate directly with another, services react to events, giving more flexibility and
scalability. &lt;em&gt;&amp;ldquo;Event-driven architecture is about reversing dependencies - reversing the
lines of integration.&amp;rdquo;&lt;/em&gt; Now, the order processing service doesn&amp;rsquo;t need to know where the
downstream services are. It simply publishes an event, and the downstream services react to
it.&lt;/p&gt;
&lt;p&gt;This shift is powerful because it frees services from having to know about each other. In
this model, the kitchen doesn&amp;rsquo;t wait for the order processing service to send a direct HTTP
request. Instead, it listens for an event that triggers its response without direct
integration. &lt;em&gt;&amp;ldquo;You&amp;rsquo;ve removed that runtime coupling because producers and consumers no
longer need to know each other exist.&amp;rdquo;&lt;/em&gt; By decoupling systems, event-driven architecture
improves fault tolerance, scalability, and flexibility.&lt;/p&gt;
&lt;h2 id=&#34;the-nature-of-an-event&#34;&gt;The nature of an event &lt;a class=&#34;anchor&#34; href=&#34;#the-nature-of-an-event&#34; aria-label=&#34;Permalink to The nature of an event&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At the core of event-driven systems is the concept of an event. An event is &lt;em&gt;&amp;ldquo;an immutable
fact. It&amp;rsquo;s something that&amp;rsquo;s happened in the past. It cannot be changed.&amp;rdquo;&lt;/em&gt; When an event is
published, it&amp;rsquo;s a record of something that has already occurred, like &lt;em&gt;&amp;ldquo;order confirmed&amp;rdquo;&lt;/em&gt; or
&lt;em&gt;&amp;ldquo;pizza boxed.&amp;rdquo;&lt;/em&gt; Events are simple, factual, and unchangeable.&lt;/p&gt;
&lt;p&gt;The analogy of a light switch brings this to life: &lt;em&gt;&amp;ldquo;You hit the light switch, and that
raises a light switched-on event. You can&amp;rsquo;t un-switch on a light.&amp;rdquo;&lt;/em&gt; To turn the light off,
you generate a new event - &lt;em&gt;&amp;ldquo;light switched-off&amp;rdquo;&lt;/em&gt; - but you don&amp;rsquo;t undo the original. This
principle of immutability ensures that events in the system are reliable and unambiguous,
forming the foundation for how systems react.&lt;/p&gt;
&lt;h2 id=&#34;event-driven-vs-event-based-systems&#34;&gt;Event-driven vs. event-based systems &lt;a class=&#34;anchor&#34; href=&#34;#event-driven-vs-event-based-systems&#34; aria-label=&#34;Permalink to Event-driven vs. event-based systems&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s easy to confuse event-driven systems with event-based systems, but the distinction is
crucial. Event-driven systems are driven by business-specific events - things that reflect
real-world actions and decisions, not just technical events like a button click. &lt;em&gt;&amp;ldquo;An
event-driven system uses events like these: &amp;lsquo;order confirmed,&amp;rsquo; &amp;lsquo;pizza boxed,&amp;rsquo; &amp;lsquo;staff member
clocked in.&amp;rsquo;&amp;rdquo;&lt;/em&gt; These are business-level events that reflect the narrative of the company,
not just low-level system changes.&lt;/p&gt;
&lt;p&gt;In contrast, event-based systems simply react to any change, such as a file being added to
S3 or a button being clicked in a UI. &lt;em&gt;&amp;ldquo;We&amp;rsquo;ve been building event-based systems for years&amp;hellip;
that doesn&amp;rsquo;t make it event-driven.&amp;rdquo;&lt;/em&gt; The difference is significant because event-driven
systems align technical architecture with business needs, creating a more meaningful,
coherent system where the events reflect the organization&amp;rsquo;s core processes.&lt;/p&gt;
&lt;h2 id=&#34;fat-vs-sparse-events&#34;&gt;Fat vs. sparse events &lt;a class=&#34;anchor&#34; href=&#34;#fat-vs-sparse-events&#34; aria-label=&#34;Permalink to Fat vs. sparse events&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the critical design decisions in event-driven systems is choosing between fat events
(which carry a lot of data) and sparse events (which carry minimal data). Fat events, also
known as Event-Carried State Transfer (ECST), include all the information a consumer might
need. For instance, &lt;em&gt;&amp;ldquo;the kitchen can consume this event - it&amp;rsquo;s got the list of items on the
order, so now it knows what it needs to cook.&amp;rdquo;&lt;/em&gt; This reduces the need for callbacks or
additional requests for data back to the original system that publishes the event, making
the system more robust in terms of runtime interaction.&lt;/p&gt;
&lt;p&gt;However, fat events come with risks. &lt;em&gt;&amp;ldquo;The downside of that is that you get more coupling at
the schema level.&amp;rdquo;&lt;/em&gt; Because fat events contain so much information, it becomes harder to
change the event format without impacting multiple consumers. As more services depend on
that data, the risk of breaking something grows.&lt;/p&gt;
&lt;p&gt;In contrast, sparse events are lightweight but require callbacks to get additional
information. Initially, this might seem more efficient, but as more services join the
system, the number of callbacks increases exponentially. &lt;em&gt;&amp;ldquo;Now you&amp;rsquo;ve got this potentially
infinite number of downstream services that are all making calls back to get more
information.&amp;rdquo;&lt;/em&gt; The result is a more tightly coupled system, albeit in a different form.&lt;/p&gt;
&lt;p&gt;So which one of these is the right one to choose? The answer is: it depends. Sparse events
reduce the need for frequent changes, but fat events reduce the need for constant
back-and-forth communication. Often, a combination of both is necessary, depending on the
use case.&lt;/p&gt;
&lt;h2 id=&#34;publish-subscribe-pattern-and-the-role-of-the-broker&#34;&gt;Publish-subscribe pattern and the role of the broker &lt;a class=&#34;anchor&#34; href=&#34;#publish-subscribe-pattern-and-the-role-of-the-broker&#34; aria-label=&#34;Permalink to Publish-subscribe pattern and the role of the broker&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The core of event-driven architecture lies in the publish-subscribe pattern, facilitated by
an event broker. &lt;em&gt;&amp;ldquo;At its core, an event-driven architecture is made up of three parts: You
have a producer, a consumer, and some kind of event broker in the middle.&amp;rdquo;&lt;/em&gt; The producer
generates the event, the broker routes it, and the consumer processes it. The beauty of this
system is that producers and consumers don&amp;rsquo;t need to know about each other&amp;rsquo;s existence.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;The first thing you&amp;rsquo;ll notice is that the producer and the consumer here have no idea each
other exists - the communication is managed by the broker.&amp;rdquo;&lt;/em&gt; This decoupling makes the
system more flexible and scalable. A consumer can be added or removed without impacting the
producer. The broker ensures that events are delivered, allowing the system to continue
functioning smoothly even as it evolves.&lt;/p&gt;
&lt;p&gt;However, one responsibility remains: &lt;em&gt;&amp;ldquo;The schema of your event - the format of that event -
is the biggest part of the coupling that you will see in event-driven architecture.&amp;rdquo;&lt;/em&gt; While
runtime coupling is removed, semantic coupling still exists. Producers must ensure that the
event schema doesn&amp;rsquo;t change in ways that break existing consumers.&lt;/p&gt;
&lt;h2 id=&#34;handling-constraints-and-governance&#34;&gt;Handling constraints and governance &lt;a class=&#34;anchor&#34; href=&#34;#handling-constraints-and-governance&#34; aria-label=&#34;Permalink to Handling constraints and governance&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In event-driven systems, the responsibility for handling constraints shifts from the
producer to the consumer. Producers generate events as quickly as they can, without worrying
about the load on consumers. &lt;em&gt;&amp;ldquo;As a producer, it&amp;rsquo;s not your responsibility to care about how
your events are used&amp;hellip; that&amp;rsquo;s the subscriber&amp;rsquo;s responsibility.&amp;rdquo;&lt;/em&gt; Consumers must handle
their own ingestion rates and ensure they don&amp;rsquo;t get overloaded.&lt;/p&gt;
&lt;p&gt;Governance plays a critical role in managing these systems, particularly as they evolve.
When changes are made to event schemas, it&amp;rsquo;s essential to communicate those changes to all
consumers. &lt;em&gt;&amp;ldquo;Governance is really important with event-driven architecture because you&amp;rsquo;ve
got these systems that just don&amp;rsquo;t care about each other.&amp;rdquo;&lt;/em&gt; One effective method for managing
this is through Request for Comments (RFCs), which allow for collaborative discussion before
any changes are implemented.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Rather than just publishing an event and hoping for the best, introducing governance
ensures that events remain consistent and understandable across teams.&amp;rdquo;&lt;/em&gt; This helps prevent
breaking changes that could take down systems you didn&amp;rsquo;t even know were relying on your
events.&lt;/p&gt;
&lt;h2 id=&#34;metadata-data-pattern-for-evolvability&#34;&gt;Metadata-data pattern for evolvability &lt;a class=&#34;anchor&#34; href=&#34;#metadata-data-pattern-for-evolvability&#34; aria-label=&#34;Permalink to Metadata-data pattern for evolvability&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To enhance the evolvability of an event-driven system, East recommends using the
metadata-data pattern. This pattern separates the event&amp;rsquo;s core data from its metadata,
allowing for greater flexibility. &lt;em&gt;&amp;ldquo;Splitting your event down into a metadata section and
the data section helps you to stay evolvable.&amp;rdquo;&lt;/em&gt; The data contains the specifics of the
event, while the metadata includes information like &lt;em&gt;&amp;ldquo;event type,&amp;rdquo;&lt;/em&gt; &lt;em&gt;&amp;ldquo;event ID,&amp;rdquo;&lt;/em&gt; and
&lt;em&gt;&amp;ldquo;version.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This separation allows consumers to understand and process events more easily while
providing room for schema changes. For example, &lt;em&gt;&amp;ldquo;event versioning allows you to introduce
breaking changes in a controlled manner.&amp;rdquo;&lt;/em&gt; By publishing multiple versions of an event, you
can ensure backward compatibility while encouraging consumers to upgrade to the latest
schema.&lt;/p&gt;
&lt;h2 id=&#34;eventual-consistency-in-event-driven-systems&#34;&gt;Eventual consistency in event-driven systems &lt;a class=&#34;anchor&#34; href=&#34;#eventual-consistency-in-event-driven-systems&#34; aria-label=&#34;Permalink to Eventual consistency in event-driven systems&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the trade-offs in event-driven architecture is that systems must embrace eventual
consistency. In a request-response system, actions happen immediately and are reflected in
real-time. But in an event-driven system, updates propagate over time. &lt;em&gt;&amp;ldquo;Eventually, over
time, these systems will converge on the same view of the world.&amp;rdquo;&lt;/em&gt; This is a shift in
mindset for many developers used to strong consistency.&lt;/p&gt;
&lt;p&gt;To illustrate this, consider a card payment: &lt;em&gt;&amp;ldquo;When you make a card transaction, all you&amp;rsquo;re
doing is making a theoretical guarantee that, at some point in the future, that money is
going to move from your bank account to theirs.&amp;rdquo;&lt;/em&gt; While the system is eventually consistent,
the end result will be correct, just not immediately. Event-driven architecture functions
similarly - updates happen asynchronously, and systems eventually reach a consistent state.&lt;/p&gt;
&lt;h2 id=&#34;handling-http-communication-in-an-event-driven-world&#34;&gt;Handling HTTP communication in an event-driven world &lt;a class=&#34;anchor&#34; href=&#34;#handling-http-communication-in-an-event-driven-world&#34; aria-label=&#34;Permalink to Handling HTTP communication in an event-driven world&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Not every system can fully adopt event-driven architecture, and many still rely on
HTTP-based communication. To integrate these systems into an event-driven world, you need a
middle layer. For example, if your loyalty point service is being replaced by a third-party
SaaS product that only supports an HTTP API, you&amp;rsquo;d still have a service managing that
integration. This service listens for events and translates them into HTTP requests for
systems that aren&amp;rsquo;t event-driven.&lt;/p&gt;
&lt;p&gt;To handle differences in response times and reliability between HTTP-based and event-driven
systems, introducing a queue or intermediary storage is crucial. &lt;em&gt;&amp;ldquo;Introducing this queue
means you can keep this amount of durability&amp;hellip; you can process to the third-party API as
and when you need to.&amp;rdquo;&lt;/em&gt; This queue adds resilience, allowing your system to continue
functioning smoothly, even when interacting with external services that don&amp;rsquo;t follow
event-driven principles.&lt;/p&gt;
&lt;h2 id=&#34;asynchronous-commands&#34;&gt;Asynchronous commands &lt;a class=&#34;anchor&#34; href=&#34;#asynchronous-commands&#34; aria-label=&#34;Permalink to Asynchronous commands&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Commands in an event-driven system don&amp;rsquo;t always need to be synchronous. Instead of waiting
for an immediate response, systems can issue commands asynchronously, allowing for greater
flexibility and non-blocking workflows. &lt;em&gt;&amp;ldquo;You want to send an email, but you might not
necessarily want it to be completely request-response.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;An asynchronous command might still send a request to a service, but the response isn&amp;rsquo;t
required to continue processing. This allows systems like the email notification service to
handle requests at its own pace, rather than blocking the core order processing service.
&lt;em&gt;&amp;ldquo;Your email service can still expose an endpoint, but as opposed to that being an
HTTP-based endpoint, that could just be a message channel.&amp;rdquo;&lt;/em&gt; This approach decouples the
services further and ensures more efficient use of resources.&lt;/p&gt;
&lt;h2 id=&#34;cqrs-for-separating-reads-and-writes&#34;&gt;CQRS for separating reads and writes &lt;a class=&#34;anchor&#34; href=&#34;#cqrs-for-separating-reads-and-writes&#34; aria-label=&#34;Permalink to CQRS for separating reads and writes&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Command Query Responsibility Segregation (CQRS) is a powerful pattern that pairs well with
event-driven architecture. CQRS separates the system into two parts: one for handling
commands (writes) and another for handling queries (reads). &lt;em&gt;&amp;ldquo;In CQRS, you split your system
into two completely independent services - one for processing commands, one for handling
queries.&amp;rdquo;&lt;/em&gt; This allows each part of the system to be optimized for its specific workload.&lt;/p&gt;
&lt;p&gt;For example, the command service focuses on writing data to the database and publishing
events, while the query service listens for those events and updates a read-optimized view
of the world. This separation enables more efficient scaling, as the query service can be
tuned for fast reads, potentially storing data in caches like Redis or even keeping it in
memory.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a bit skeptical about CQRS since I&amp;rsquo;ve worked on a system with a terrible implementation
that went horribly wrong. But I intend to keep an open mind.&lt;/p&gt;
&lt;h2 id=&#34;handling-failure-with-the-outbox-pattern&#34;&gt;Handling failure with the outbox pattern &lt;a class=&#34;anchor&#34; href=&#34;#handling-failure-with-the-outbox-pattern&#34; aria-label=&#34;Permalink to Handling failure with the outbox pattern&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In event-driven systems, failure is inevitable, so you need strategies to handle situations
where events fail to publish. The outbox pattern is one such approach. &lt;em&gt;&amp;ldquo;At the point you
write the data to the main database&amp;hellip; you also write the data to a secondary table.&amp;rdquo;&lt;/em&gt; This
outbox table ensures that if the event fails to publish initially, it can be retried later.&lt;/p&gt;
&lt;p&gt;This creates consistency across the system by acting as a buffer between the database and
the event bus. Alternatively, systems can use change data capture to respond directly to
changes in the database. &lt;em&gt;&amp;ldquo;As a record is written to the database, you can stream that - you
can react to that - and you can publish events off the back of that.&amp;rdquo;&lt;/em&gt; Both methods ensure
reliability, preventing events from being lost due to temporary failures.&lt;/p&gt;
&lt;p&gt;The outbox pattern sounds great in theory, but in practice, if you have a large system with
many services publishing to the broker, managing an extra process for each service to read
from the outbox table and publish to the event bus becomes a hassle. Instead, on the
publisher side, retrying with a circuit breaker has worked better for me. Also, fun fact: I
was asked about the outbox pattern in 4 of the last 5 places I interviewed for a backend
role.&lt;/p&gt;
</content:encoded>
      <category>Networking</category>
      <category>Distributed Systems</category>
      <category>Design Patterns</category>
    </item>
    <item>
      <title>Bash namerefs for dynamic variable referencing</title>
      <link>https://rednafi.com/misc/bash-namerefs/</link>
      <pubDate>Fri, 20 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/bash-namerefs/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Master Bash namerefs with declare -n to create dynamic variable references. Build generic functions for arrays and associative arrays without eval.</description>
      <content:encoded>&lt;p&gt;While going through a script at work today, I came across Bash&amp;rsquo;s &lt;code&gt;nameref&lt;/code&gt; feature. It uses
&lt;code&gt;declare -n ref=&amp;quot;$1&amp;quot;&lt;/code&gt; to set up a variable that allows you to reference another variable by
name - kind of like pass-by-reference in C. I&amp;rsquo;m pretty sure I&amp;rsquo;ve seen it before, but I
probably just skimmed over it.&lt;/p&gt;
&lt;p&gt;As I dug into the &lt;a href=&#34;https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;man pages (opens in new tab)&#34;&gt;man pages&lt;/a&gt;, I realized there&amp;rsquo;s a gap in my understanding of how variable
references actually work in Bash - probably because I never gave it proper attention and
just got by cobbling together scripts.&lt;/p&gt;
&lt;h2 id=&#34;namerefs&#34;&gt;Namerefs &lt;a class=&#34;anchor&#34; href=&#34;#namerefs&#34; aria-label=&#34;Permalink to Namerefs&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, Bash variables are global unless declared as &lt;code&gt;local&lt;/code&gt; within a function. However,
when you pass variables as arguments to a function, they are accessed via positional
parameters like &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, etc., and any changes to these parameters inside the function do
not affect the original variables outside the function.&lt;/p&gt;
&lt;p&gt;Namerefs allow you to essentially define a pointer to another variable. By creating a
nameref, you can indirectly reference and manipulate the target variable without knowing its
name beforehand. This is incredibly useful for writing generic functions that can operate on
different variables based on input parameters.&lt;/p&gt;
&lt;h2 id=&#34;basic-usage&#34;&gt;Basic usage &lt;a class=&#34;anchor&#34; href=&#34;#basic-usage&#34; aria-label=&#34;Permalink to Basic usage&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Declare a variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;original_var&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Function that creates a nameref to a variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;create_ref&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;ref_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -n &lt;span class=&#34;nv&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ref_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello from nameref!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Call the function with the name of the variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;create_ref original_var
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Print the updated variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$original_var&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Running this will print:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Hello from nameref!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;By running the &lt;code&gt;create_ref&lt;/code&gt; function, we can dynamically update the value of
&lt;code&gt;$original_var&lt;/code&gt;, which exists outside of it. Notice that the function doesn&amp;rsquo;t even need to
know about &lt;code&gt;$original_var&lt;/code&gt;; it works on any variable name provided, making it generic.&lt;/p&gt;
&lt;p&gt;In this script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We declare a variable &lt;code&gt;original_var&lt;/code&gt; with the value &lt;code&gt;&amp;quot;Hello, World!&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;create_ref&lt;/code&gt; function takes the name of a variable as an argument.&lt;/li&gt;
&lt;li&gt;Inside the function, &lt;code&gt;declare -n ref=&amp;quot;$ref_name&amp;quot;&lt;/code&gt; creates a nameref &lt;code&gt;ref&lt;/code&gt; that points to
the variable named by &lt;code&gt;$ref_name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;By setting &lt;code&gt;ref=&amp;quot;Hello from nameref!&amp;quot;&lt;/code&gt;, we indirectly update &lt;code&gt;original_var&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, we print &lt;code&gt;original_var&lt;/code&gt; to see the updated value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without the nameref, you could achieve the same thing with this &lt;code&gt;eval&lt;/code&gt; (read: evil) trick:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Declare a variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;original_var&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Function that updates a variable dynamically using eval&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;create_ref&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;var_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;new_value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello from eval!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;eval&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$var_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;=\&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$new_value&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;\&amp;#34;&amp;#34;&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# eval 😈&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Call the function with the name of the variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;create_ref original_var
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Print the updated variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$original_var&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This achieves the same result. The &lt;code&gt;eval &amp;quot;$var_name=\&amp;quot;$new_value\&amp;quot;&amp;quot;&lt;/code&gt; dynamically updates the
&lt;code&gt;$original_var&lt;/code&gt; variable through &lt;code&gt;$var_name&lt;/code&gt;. However, &lt;code&gt;eval&lt;/code&gt; can be risky for security, and
the nameref approach looks much cleaner syntactically.&lt;/p&gt;
&lt;h2 id=&#34;managing-multiple-arrays&#34;&gt;Managing multiple arrays &lt;a class=&#34;anchor&#34; href=&#34;#managing-multiple-arrays&#34; aria-label=&#34;Permalink to Managing multiple arrays&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Namerefs shine when you need to manage multiple arrays dynamically. Consider a scenario
where you have several datasets stored in different arrays, and you want to process them
using a single function.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Declare multiple arrays&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -a &lt;span class=&#34;nv&#34;&gt;dataset1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;3&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;4&lt;/span&gt; 5&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -a &lt;span class=&#34;nv&#34;&gt;dataset2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;10&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;20&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;30&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;40&lt;/span&gt; 50&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -a &lt;span class=&#34;nv&#34;&gt;dataset3&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;200&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;300&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;400&lt;/span&gt; 500&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Function to calculate the sum of an array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;array_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -n &lt;span class=&#34;nv&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$array_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; num in &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[@]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nv&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$((&lt;/span&gt;sum &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; num&lt;span class=&#34;k&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Sum of &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$array_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;: &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$sum&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Process each dataset&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array dataset1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array dataset2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array dataset3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This returns:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Sum of dataset1: 15
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Sum of dataset2: 150
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Sum of dataset3: 1500&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We declare three arrays: &lt;code&gt;dataset1&lt;/code&gt;, &lt;code&gt;dataset2&lt;/code&gt;, and &lt;code&gt;dataset3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;sum_array&lt;/code&gt; function takes the name of an array as an argument.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;declare -n arr=&amp;quot;$array_name&amp;quot;&lt;/code&gt;, we create a nameref &lt;code&gt;arr&lt;/code&gt; that points to the
specified array.&lt;/li&gt;
&lt;li&gt;We then iterate over the elements of &lt;code&gt;arr&lt;/code&gt; to calculate the sum.&lt;/li&gt;
&lt;li&gt;Finally, we call &lt;code&gt;sum_array&lt;/code&gt; for each dataset, and the function correctly processes each
array based on the reference.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without the nameref, you could again use the &lt;code&gt;eval&lt;/code&gt; trick to achieve the same thing, but
this time it looks even uglier:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Declare multiple arrays&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -a &lt;span class=&#34;nv&#34;&gt;dataset1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;3&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;4&lt;/span&gt; 5&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -a &lt;span class=&#34;nv&#34;&gt;dataset2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;10&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;20&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;30&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;40&lt;/span&gt; 50&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -a &lt;span class=&#34;nv&#34;&gt;dataset3&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;200&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;300&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;400&lt;/span&gt; 500&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Function to calculate the sum of an array without namerefs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;array_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; array_length
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;eval&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;array_length=\${#&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$array_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;[@]}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;((&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;index&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; index&amp;lt;array_length&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; index++ &lt;span class=&#34;o&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;eval&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;element=\${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$array_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$index&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;]}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nv&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$((&lt;/span&gt;sum &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; element&lt;span class=&#34;k&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Sum of &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$array_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;: &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$sum&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Process each dataset&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array dataset1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array dataset2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array dataset3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This approach is more complex, less secure, and harder to read in general. But the above
&lt;code&gt;eval&lt;/code&gt; example was a bit contrived to make it look bad. You can achieve the same thing
without &lt;code&gt;eval&lt;/code&gt; or nameref in this particular case like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Declare multiple arrays&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;dataset1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;3&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;4&lt;/span&gt; 5&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;dataset2&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;10&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;20&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;30&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;40&lt;/span&gt; 50&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;dataset3&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;200&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;300&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;400&lt;/span&gt; 500&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Function to calculate the sum of an array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; element in &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nv&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$((&lt;/span&gt;sum &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; element&lt;span class=&#34;k&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Sum: &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$sum&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Process each dataset&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;dataset1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[@]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;dataset2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[@]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sum_array &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;dataset3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[@]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, instead of passing the name of the dataset arrays as strings, we pass the elements of
the array to the function and add them. But I digress!&lt;/p&gt;
&lt;h2 id=&#34;associative-arrays-and-nested-references&#34;&gt;Associative arrays and nested references &lt;a class=&#34;anchor&#34; href=&#34;#associative-arrays-and-nested-references&#34; aria-label=&#34;Permalink to Associative arrays and nested references&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Namerefs also work with associative arrays and can be used for more complex data structures.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Declare an associative array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -A &lt;span class=&#34;nv&#34;&gt;user_info&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;name&lt;span class=&#34;o&#34;&gt;]=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;age&lt;span class=&#34;o&#34;&gt;]=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;email&lt;span class=&#34;o&#34;&gt;]=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;alice@example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Function to update user information&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;update_info&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;info_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;new_value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -n &lt;span class=&#34;nv&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$info_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    info&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$key&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$new_value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Update the user&amp;#39;s email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;update_info user_info email &lt;span class=&#34;s2&#34;&gt;&amp;#34;alice@newdomain.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Print updated information&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; key in &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!user_info[@]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$key&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;user_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;It prints:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;name: Alice
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;age: 30
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;email: alice@newdomain.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;And voilà! We have a function that can dynamically update the values in an associative
array. This technique is useful for changing environments or contexts (staging/production)
in shell scripts.&lt;/p&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We declare an associative array &lt;code&gt;user_info&lt;/code&gt; containing user details.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;update_info&lt;/code&gt; function takes the name of the associative array, the key to update, and
the new value.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;declare -n info=&amp;quot;$info_name&amp;quot;&lt;/code&gt;, we create a nameref &lt;code&gt;info&lt;/code&gt; pointing to &lt;code&gt;user_info&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We update the specified key in the array.&lt;/li&gt;
&lt;li&gt;Finally, we echo the updated user information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Doing this with &lt;code&gt;eval&lt;/code&gt; isn&amp;rsquo;t pretty. I&amp;rsquo;ll leave that as an exercise for you if you like to
torment yourself.&lt;/p&gt;
&lt;h2 id=&#34;implementing-generic-setter-and-getter-functions&#34;&gt;Implementing generic setter and getter functions &lt;a class=&#34;anchor&#34; href=&#34;#implementing-generic-setter-and-getter-functions&#34; aria-label=&#34;Permalink to Implementing generic setter and getter functions&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Building on the earlier examples, you can use namerefs to create generic setter and getter
functions, making it easier to manage configuration variables or environment settings in
scripts.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Generic setter function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;set_var&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;var_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$2&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -n &lt;span class=&#34;nv&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$var_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$value&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Generic getter function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;get_var&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;var_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;declare&lt;/span&gt; -n &lt;span class=&#34;nv&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$var_name&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ref&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Usage example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;staging&amp;#34;&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# Can be passed as an argument to the script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Define default variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;db_host&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;db_port&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;db_user&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;db_pass&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;secret&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Set different values based on the environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$env&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;production&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  set_var &lt;span class=&#34;s2&#34;&gt;&amp;#34;db_host&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;prod.db.example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  set_var &lt;span class=&#34;s2&#34;&gt;&amp;#34;db_user&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;prod_admin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;elif&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$env&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;staging&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  set_var &lt;span class=&#34;s2&#34;&gt;&amp;#34;db_host&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;staging.db.example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  set_var &lt;span class=&#34;s2&#34;&gt;&amp;#34;db_user&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;staging_admin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Retrieve and display values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Using Database: &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;get_var &lt;span class=&#34;s2&#34;&gt;&amp;#34;db_host&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Database User: &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;get_var &lt;span class=&#34;s2&#34;&gt;&amp;#34;db_user&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;To keep things simple, the &lt;code&gt;env&lt;/code&gt; variable isn&amp;rsquo;t a CLI argument. Based on whether &lt;code&gt;env&lt;/code&gt; is
set to staging or production, the script will print the relevant database values.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;staging&lt;/code&gt;, you&amp;rsquo;ll see:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Using Database: staging.db.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Database User: staging_admin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For &lt;code&gt;production&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Using Database: prod.db.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Database User: prod_admin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Oh, one extra thing: nameref was introduced in Bash 4.3, so you might run into problems if
you&amp;rsquo;re using an ancient version like the one shipped with macOS.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>Shell</category>
      <category>Unix</category>
      <category>TIL</category>
      <category>CLI</category>
    </item>
    <item>
      <title>Behind the blog</title>
      <link>https://rednafi.com/misc/behind-the-blog/</link>
      <pubDate>Sat, 14 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/behind-the-blog/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>How this blog is built: Hugo static site generator, GitHub Actions deployment, Cloudflare caching, and R2 storage. Simple, stable, and cost-free.</description>
      <content:encoded>&lt;p&gt;When I started writing here about five years ago, I made a promise to myself that I wouldn&amp;rsquo;t
give in to the trend of starting a blog, adding one overly enthusiastic entry about the
stack behind it, and then vanishing into the ether.&lt;/p&gt;
&lt;p&gt;I was somewhat successful at that and wanted to write something I can link to when people
are curious about the machinery that drives this site. The good thing is that the tech stack
is simple and has remained stable over the years since I&amp;rsquo;ve only made changes when
absolutely necessary.&lt;/p&gt;
&lt;h2 id=&#34;markdown&#34;&gt;Markdown &lt;a class=&#34;anchor&#34; href=&#34;#markdown&#34; aria-label=&#34;Permalink to Markdown&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I write plain Markdown files in my editor of choice, which has been VSCode since its launch.
Once I&amp;rsquo;m finished, &lt;code&gt;make format&lt;/code&gt; runs the local Markdown cleanup, including &lt;a href=&#34;https://prettier.io/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;prettier (opens in new tab)&#34;&gt;prettier&lt;/a&gt;, to
fix line length and formatting.&lt;/p&gt;
&lt;h2 id=&#34;hugo&#34;&gt;Hugo &lt;a class=&#34;anchor&#34; href=&#34;#hugo&#34; aria-label=&#34;Permalink to Hugo&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://gohugo.io/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Hugo (opens in new tab)&#34;&gt;Hugo&lt;/a&gt; is the static site generator that turns the Markdown files into HTML. I chose it
because I needed something that can build the site quickly, even with lots of content. It
lets me hot reload the server and check my changes as I write. Plus, I don&amp;rsquo;t get to write Go
at work, so messing with Hugo templates or its source code gives me a reason to play around
with Go.&lt;/p&gt;
&lt;p&gt;I initially tried some JS-based SSGs but dropped them pretty quickly because I couldn&amp;rsquo;t keep
up with the constant tooling changes in the JavaScript universe. I use a handrolled theme
and have tweaked the CSS over time.&lt;/p&gt;
&lt;h2 id=&#34;github-issues&#34;&gt;GitHub Issues &lt;a class=&#34;anchor&#34; href=&#34;#github-issues&#34; aria-label=&#34;Permalink to GitHub Issues&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I use &lt;a href=&#34;https://github.com/rednafi/rednafi.com/issues/125&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;GitHub Issues (opens in new tab)&#34;&gt;GitHub Issues&lt;/a&gt; to brainstorm ideas and keep track of my writing. I usually gather
ideas throughout the week, log them in Issues, and then write something over the weekend.
This workflow is heavily inspired by &lt;a href=&#34;https://simonwillison.net/2022/Nov/26/productivity/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Simon Willison’s blog (opens in new tab)&#34;&gt;Simon Willison&amp;rsquo;s blog&lt;/a&gt; on his workflow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blob.rednafi.com/misc/behind-the-blog/image-01-49b2d84a79f0.png&#34; alt=&#34;GitHub Issues used as a research notebook&#34; width=&#34;1908&#34; height=&#34;948&#34; referrerpolicy=&#34;no-referrer&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;github-actions-and-github-pages&#34;&gt;GitHub Actions and GitHub Pages &lt;a class=&#34;anchor&#34; href=&#34;#github-actions-and-github-pages&#34; aria-label=&#34;Permalink to GitHub Actions and GitHub Pages&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once I push content to the main branch, &lt;a href=&#34;https://github.com/features/actions&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;GitHub Actions (opens in new tab)&#34;&gt;GitHub Actions&lt;/a&gt; automatically runs the checks,
builds the site, and deploys it to &lt;a href=&#34;https://pages.github.com/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;GitHub Pages (opens in new tab)&#34;&gt;GitHub Pages&lt;/a&gt;. There&amp;rsquo;s nothing to maintain, and I don&amp;rsquo;t
have to worry about scaling, even if one of my posts hits the front page of Hacker News.
Aside from the domain, this site costs me nothing to run, and I plan to keep it that way.&lt;/p&gt;
&lt;h2 id=&#34;cloudflare-cache-and-r2&#34;&gt;Cloudflare Cache and R2 &lt;a class=&#34;anchor&#34; href=&#34;#cloudflare-cache-and-r2&#34; aria-label=&#34;Permalink to Cloudflare Cache and R2&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m a huge fan of Cloudflare and often try to shoehorn their offerings into my projects.
Since my domain is registered with them, setting up their proxy with my domain&amp;rsquo;s DNS and
turning on caching took just a few minutes. Their caching layer absorbs most of the traffic,
and less than 10% of the requests hit the origin server. Plus, having the proxy layer gives
me access to more accurate analytics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blob.rednafi.com/misc/behind-the-blog/image-02-82e60a201213.png&#34; alt=&#34;Cloudflare cache analytics dashboard&#34; width=&#34;2224&#34; height=&#34;1304&#34; referrerpolicy=&#34;no-referrer&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;Static assets like images, CSS, JS, and other files are stored on &lt;a href=&#34;https://developers.cloudflare.com/r2/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Cloudflare R2 (opens in new tab)&#34;&gt;Cloudflare R2&lt;/a&gt;. I used to
host my images with GitHub Issues and serve CSS and JS from the origin, but I recently
switched everything to R2. Now I can manage it all from one place without worrying about
costs. Their free plan is super generous - there&amp;rsquo;s no egress bandwidth fee, and because of
caching, I barely use any of the quota. It&amp;rsquo;s fantastic!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blob.rednafi.com/misc/behind-the-blog/image-03-0c1e60d3ade7.png&#34; alt=&#34;Cloudflare R2 dashboard&#34; width=&#34;2302&#34; height=&#34;1200&#34; referrerpolicy=&#34;no-referrer&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;oxipng&#34;&gt;Oxipng &lt;a class=&#34;anchor&#34; href=&#34;#oxipng&#34; aria-label=&#34;Permalink to Oxipng&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/shssoichiro/oxipng&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Oxipng (opens in new tab)&#34;&gt;Oxipng&lt;/a&gt; is used to losslessly compress PNG screenshots before uploading them to the
Cloudflare R2 bucket with the &lt;a href=&#34;https://developers.cloudflare.com/workers/wrangler/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;wrangler (opens in new tab)&#34;&gt;wrangler&lt;/a&gt; CLI. Images are immutable: each uploaded object
gets a content hash in the filename and long-lived cache headers, so Cloudflare can serve
repeat reads from cache without revalidating R2.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make upload-post-image &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;content/go/request_coalescing.md &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/tmp/singleflight-flow.png &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;singleflight-flow&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command derives the R2 directory from the post, normalizes names to kebab-case, runs
&lt;code&gt;oxipng -o max --strip safe&lt;/code&gt; for PNGs, uploads with
&lt;code&gt;Cache-Control: public, max-age=31536000, immutable&lt;/code&gt;, and prints the canonical URL. Most
article images are PNG screenshots, so the default path is screenshot to PNG, lossless max
compression, upload, paste the printed URL into Markdown.&lt;/p&gt;
&lt;p&gt;Standard.site records use the same R2-hosted site cover. CI downloads and verifies it before
publishing, then commits any generated metadata changes.&lt;/p&gt;
&lt;h2 id=&#34;google-analytics&#34;&gt;Google Analytics &lt;a class=&#34;anchor&#34; href=&#34;#google-analytics&#34; aria-label=&#34;Permalink to Google Analytics&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m still using &lt;a href=&#34;https://analytics.google.com/&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;Google Analytics (opens in new tab)&#34;&gt;Google Analytics&lt;/a&gt;, even though I&amp;rsquo;m not a huge fan. Cloudflare already gives
me better traffic insights, but the free version doesn&amp;rsquo;t show how many hits each page gets.
At some point, I might just pay for Cloudflare&amp;rsquo;s upgraded plan so I can get rid of the
bulky, intrusive analytics scripts for good.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/rednafi/rednafi.com&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;source code (opens in new tab)&#34;&gt;source code&lt;/a&gt; and content for this site are all publicly available on GitHub.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- i usually use github issues like this --&gt;
&lt;!-- cloudflare wrangler --&gt;
&lt;!-- source code of this site --&gt;
&lt;!-- github issues as a research notebook --&gt;
&lt;!-- cloudflare cache analytics --&gt;
&lt;!-- cloudflare r2 dashboard --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>Essay</category>
      <category>Hugo</category>
      <category>DevOps</category>
    </item>
    <item>
      <title>Shell redirection syntax soup</title>
      <link>https://rednafi.com/misc/shell-redirection/</link>
      <pubDate>Thu, 12 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/shell-redirection/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Complete guide to Bash redirection. Master stdout, stderr, pipes, tee, file descriptors, and shorthand syntax with practical examples for every case.</description>
      <content:encoded>&lt;p&gt;I always struggle with the syntax for redirecting multiple streams to another command or a
file. LLMs do help, but beyond the most obvious cases, it takes a few prompts to get the
syntax right. When I know exactly what I&amp;rsquo;m after, scanning a quick post is much faster than
wrestling with a non-deterministic kraken. So, here&amp;rsquo;s a list of the redirection and piping
syntax I use the most, with real examples.&lt;/p&gt;
&lt;h2 id=&#34;redirecting-stdout-and-stderr&#34;&gt;Redirecting stdout and stderr &lt;a class=&#34;anchor&#34; href=&#34;#redirecting-stdout-and-stderr&#34; aria-label=&#34;Permalink to Redirecting stdout and stderr&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&#34;redirect-stdout-to-a-file&#34;&gt;Redirect stdout to a file &lt;a class=&#34;anchor&#34; href=&#34;#redirect-stdout-to-a-file&#34; aria-label=&#34;Permalink to Redirect stdout to a file&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Standard way:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &amp;gt; file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This replaces the content of &lt;code&gt;file&lt;/code&gt; with the stdout of &lt;code&gt;command&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt; &amp;gt; hello.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect to file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee hello.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This prints &amp;ldquo;Hello, world!&amp;rdquo; to the terminal and also writes it to &lt;code&gt;hello.txt&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;redirect-stderr-to-a-file&#34;&gt;Redirect stderr to a file &lt;a class=&#34;anchor&#34; href=&#34;#redirect-stderr-to-a-file&#34; aria-label=&#34;Permalink to Redirect stderr to a file&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Standard way:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; 2&amp;gt; file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Sends all errors (stderr) to &lt;code&gt;file&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file 2&amp;gt; error.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect stderr to file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; 2&amp;gt; &amp;gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;tee file&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file 2&amp;gt; &amp;gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;tee error.log&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;redirect-both-stdout-and-stderr-to-a-file&#34;&gt;Redirect both stdout and stderr to a file &lt;a class=&#34;anchor&#34; href=&#34;#redirect-both-stdout-and-stderr-to-a-file&#34; aria-label=&#34;Permalink to Redirect both stdout and stderr to a file&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Common approach:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &amp;gt; file 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Combines stdout and stderr into one stream and saves them to &lt;code&gt;file&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file &amp;gt; output.log 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect both to file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee output.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Convenient shorthand:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&amp;gt; file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&amp;gt; output.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;append-instead-of-overwriting&#34;&gt;Append instead of overwriting &lt;a class=&#34;anchor&#34; href=&#34;#append-instead-of-overwriting&#34; aria-label=&#34;Permalink to Append instead of overwriting&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Append stdout to a file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &amp;gt;&amp;gt; file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Appending line&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; hello.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and append stdout to file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee -a file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Appending line&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee -a hello.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Append both stdout and stderr (explicit):&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &amp;gt;&amp;gt; file 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file &amp;gt;&amp;gt; output.log 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and append both stdout and stderr to file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee -a file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee -a output.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Convenient shorthand for appending both:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&amp;gt;&amp;gt; file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&amp;gt;&amp;gt; output.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;piping-output&#34;&gt;Piping output &lt;a class=&#34;anchor&#34; href=&#34;#piping-output&#34; aria-label=&#34;Permalink to Piping output&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&#34;pipe-stdout-to-another-command&#34;&gt;Pipe stdout to another command &lt;a class=&#34;anchor&#34; href=&#34;#pipe-stdout-to-another-command&#34; aria-label=&#34;Permalink to Pipe stdout to another command&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Basic usage:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;command1 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; command2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This sends the stdout of &lt;code&gt;command1&lt;/code&gt; to the input of &lt;code&gt;command2&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect piped stdout to file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;command1 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee file &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; command2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee output.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;pipe-both-stdout-and-stderr&#34;&gt;Pipe both stdout and stderr &lt;a class=&#34;anchor&#34; href=&#34;#pipe-both-stdout-and-stderr&#34; aria-label=&#34;Permalink to Pipe both stdout and stderr&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Common way:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;command1 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; command2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Combines stdout and stderr, then pipes the combined stream to &lt;code&gt;command2&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s2&#34;&gt;&amp;#34;No&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect both stdout and stderr to file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;command1 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee file &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; command2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee output.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s2&#34;&gt;&amp;#34;No&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;shorthand-for-piping-both-stdout-and-stderr-&#34;&gt;Shorthand for piping both stdout and stderr (&lt;code&gt;|&amp;amp;&lt;/code&gt;) &lt;a class=&#34;anchor&#34; href=&#34;#shorthand-for-piping-both-stdout-and-stderr-&#34; aria-label=&#34;Permalink to Shorthand for piping both stdout and stderr (|&amp;amp;)&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Shorthand syntax:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;command1 &lt;span class=&#34;p&#34;&gt;|&amp;amp;&lt;/span&gt; command2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is equivalent to &lt;code&gt;command1 2&amp;gt;&amp;amp;1 | command2&lt;/code&gt;, combining stdout and stderr. For
example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file &lt;span class=&#34;p&#34;&gt;|&amp;amp;&lt;/span&gt; grep &lt;span class=&#34;s2&#34;&gt;&amp;#34;No&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect both stdout and stderr using &lt;code&gt;|&amp;amp;&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;command1 &lt;span class=&#34;p&#34;&gt;|&amp;amp;&lt;/span&gt; tee file &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; command2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;bash&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file &lt;span class=&#34;p&#34;&gt;|&amp;amp;&lt;/span&gt; tee output.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s2&#34;&gt;&amp;#34;No&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;redirecting-file-descriptors&#34;&gt;Redirecting file descriptors &lt;a class=&#34;anchor&#34; href=&#34;#redirecting-file-descriptors&#34; aria-label=&#34;Permalink to Redirecting file descriptors&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&#34;custom-file-descriptors&#34;&gt;Custom file descriptors &lt;a class=&#34;anchor&#34; href=&#34;#custom-file-descriptors&#34; aria-label=&#34;Permalink to Custom file descriptors&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a new file descriptor (e.g., &lt;code&gt;3&lt;/code&gt;) and redirect stdout to it:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;gt; outputfile
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This sends the stdout of &lt;code&gt;command&lt;/code&gt; to file descriptor &lt;code&gt;3&lt;/code&gt;, which points to &lt;code&gt;outputfile&lt;/code&gt;.
For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;gt; custom_output.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Using FD 3&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect stdout to custom file descriptor:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;gt; custom_output.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Using FD 3&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee /dev/tty &amp;gt; /dev/fd/3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This prints &amp;ldquo;Using FD 3&amp;rdquo; to the terminal and simultaneously writes it to
&lt;code&gt;custom_output.txt&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;redirect-stderr-to-a-file-descriptor&#34;&gt;Redirect stderr to a file descriptor &lt;a class=&#34;anchor&#34; href=&#34;#redirect-stderr-to-a-file-descriptor&#34; aria-label=&#34;Permalink to Redirect stderr to a file descriptor&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Common case:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Redirects stderr to file descriptor &lt;code&gt;3&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;gt; error_output.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and redirect stderr to custom file descriptor:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; 2&amp;gt; &amp;gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;tee &amp;gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;cat &amp;gt; /dev/fd/3&lt;span class=&#34;o&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file 2&amp;gt; &amp;gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;tee &amp;gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;cat &amp;gt; /dev/fd/3&lt;span class=&#34;o&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;redirect-both-stdout-and-stderr-to-a-file-descriptor&#34;&gt;Redirect both stdout and stderr to a file descriptor &lt;a class=&#34;anchor&#34; href=&#34;#redirect-both-stdout-and-stderr-to-a-file-descriptor&#34; aria-label=&#34;Permalink to Redirect both stdout and stderr to a file descriptor&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Common way:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &amp;gt; /dev/fd/3 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Combines stdout and stderr, and redirects them to file descriptor &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: There&amp;rsquo;s no shorthand equivalent for redirecting both stdout and stderr to a
file descriptor. You need to use the full syntax. For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;gt; combined_output.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file existing_file &amp;gt; /dev/fd/3 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;discarding-output&#34;&gt;Discarding output &lt;a class=&#34;anchor&#34; href=&#34;#discarding-output&#34; aria-label=&#34;Permalink to Discarding output&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&#34;send-stdout-and-stderr-to-devnull&#34;&gt;Send stdout and stderr to /dev/null &lt;a class=&#34;anchor&#34; href=&#34;#send-stdout-and-stderr-to-devnull&#34; aria-label=&#34;Permalink to Send stdout and stderr to /dev/null&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Common:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &amp;gt; /dev/null 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Silences all output (stdout and stderr). For example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file &amp;gt; /dev/null 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print and discard stdout and stderr (not sure why you&amp;rsquo;d ever need this):&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee /dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tee /dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Convenient shorthand:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls non_existing_file &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;at-a-glance&#34;&gt;At a glance &lt;a class=&#34;anchor&#34; href=&#34;#at-a-glance&#34; aria-label=&#34;Permalink to At a glance&#34;&gt;&lt;span aria-hidden=&#34;true&#34;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redirect stdout&lt;/strong&gt;: &lt;code&gt;command &amp;gt; file&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redirect stderr&lt;/strong&gt;: &lt;code&gt;command 2&amp;gt; file&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redirect both stdout and stderr&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Standard: &lt;code&gt;command &amp;gt; file 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Shorthand: &lt;code&gt;command &amp;amp;&amp;gt; file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Append stdout&lt;/strong&gt;: &lt;code&gt;command &amp;gt;&amp;gt; file&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Append both stdout and stderr&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Standard: &lt;code&gt;command &amp;gt;&amp;gt; file 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Shorthand: &lt;code&gt;command &amp;amp;&amp;gt;&amp;gt; file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pipe stdout&lt;/strong&gt;: &lt;code&gt;command1 | command2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pipe both stdout and stderr&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Standard: &lt;code&gt;command1 2&amp;gt;&amp;amp;1 | command2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Shorthand: &lt;code&gt;command1 |&amp;amp; command2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom file descriptors&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create and redirect stdout: &lt;code&gt;exec 3&amp;gt; file; command &amp;gt;&amp;amp;3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Redirect stderr: &lt;code&gt;command 2&amp;gt;&amp;amp;3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Redirect both stdout and stderr: &lt;code&gt;command &amp;gt; /dev/fd/3 2&amp;gt;&amp;amp;1&lt;/code&gt; (no shorthand available)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discard stdout and stderr&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Standard: &lt;code&gt;command &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Shorthand: &lt;code&gt;command &amp;amp;&amp;gt;/dev/null&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded>
      <category>Shell</category>
      <category>Unix</category>
    </item>
    <item>
      <title>HTTP requests via /dev/tcp</title>
      <link>https://rednafi.com/misc/http-requests-via-dev-tcp/</link>
      <pubDate>Thu, 08 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/http-requests-via-dev-tcp/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Make raw HTTP requests with Bash&amp;#39;s /dev/tcp file descriptor. Build health check scripts without curl or wget using TCP socket connections.</description>
      <content:encoded>&lt;p&gt;I learned this neat Bash trick today where you can make a raw HTTP request using the
&lt;code&gt;/dev/tcp&lt;/code&gt; file descriptor without using tools like &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;wget&lt;/code&gt;. This came in handy
while writing a health check script that needed to make a TCP request to a service.&lt;/p&gt;
&lt;p&gt;The following script opens a TCP connection and makes a simple GET request to &lt;code&gt;example.com&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Open TCP connection to example.com:80 and assign file descriptor 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# exec keeps /dev/fd/3 open; 3&amp;lt;&amp;gt; enables bidirectional read-write&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;lt;&amp;gt;/dev/tcp/example.com/80
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Send the HTTP GET request to the server (&amp;gt;&amp;amp; redirects to /dev/fd/3)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; -e &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Read and print the server&amp;#39;s response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# &amp;lt;&amp;amp; redirects the output of /dev/fd/3 to cat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat &amp;lt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Close the file descriptor, terminating the TCP connection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Running this will print the response from the site to your console.&lt;/p&gt;
&lt;p&gt;The snippet first opens a TCP connection to &lt;code&gt;example.com&lt;/code&gt; on port 80 and assigns file
descriptor 3 to manage this connection. The &lt;code&gt;exec&lt;/code&gt; ensures that the file descriptor 3
remains open for the duration of the script, allowing multiple read and write operations
without needing to reopen the connection each time. Using a file descriptor makes the code
cleaner. Without it, we&amp;rsquo;d need to redirect input and output directly to
&lt;code&gt;/dev/tcp/example.com/80&lt;/code&gt; for each read and write operation, making the script more
cumbersome and harder to read.&lt;/p&gt;
&lt;p&gt;Then we send an HTTP GET request to the server by echoing the request to file descriptor 3.
The server&amp;rsquo;s response is read and printed using &lt;code&gt;cat &amp;lt;&amp;amp;3&lt;/code&gt;, which reads from the file
descriptor and prints the output to the console. Finally, the script closes the connection
by terminating file descriptor 3 with &lt;code&gt;exec 3&amp;gt;&amp;amp;-&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a Bash-specific trick and won&amp;rsquo;t work in other shells like Zsh or Fish. It also
allows you to open UDP connections in the same manner. The Bash manpage explains the usage
like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/tcp/host/port
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    If host is a valid hostname or Internet address, and port
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    is an integer port number or service name, bash attempts
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    to open the corresponding TCP socket.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/udp/host/port
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    If host is a valid hostname or Internet address, and port
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    is an integer port number or service name, bash attempts
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    to open the corresponding UDP socket.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I used this to write the following health check script. I didn&amp;rsquo;t want to install &lt;code&gt;curl&lt;/code&gt; in a
sidecar container that just runs a single health check process, keeping things simpler.&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Enable bash strict mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Constants&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;HOST&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PORT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;readonly&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;HEALTH_PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Open a TCP connection to the specified host and port&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;lt;&amp;gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/dev/tcp/&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;HOST&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;PORT&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Send the HTTP GET request to the server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; -e &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;GET &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;HEALTH_PATH&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; HTTP/1.1\r\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Host: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;HOST&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;\r\nConnection: close\r\n\r\n&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Read the HTTP status from the server&amp;#39;s response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; -r HTTP_RESPONSE &amp;lt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;HTTP_STATUS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;HTTP_RESPONSE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep -o &lt;span class=&#34;s2&#34;&gt;&amp;#34;HTTP/1.1 [0-9]*&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; cut -d &lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; -f &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;HTTP_STATUS&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;200&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Service is healthy.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Service is not healthy. HTTP status: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;HTTP_STATUS&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Close the file descriptor, terminating the TCP connection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; 3&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The script makes a GET request to the service and checks that the HTTP status from the raw
response is 200. If not, it exits with a non-zero status.&lt;/p&gt;
&lt;p&gt;Note that the script will fail if your service returns a 301 redirect code. Plus, you need
to make raw textual HTTP requests, which can become cumbersome if you need to do anything
beyond a simple GET call. At that point, you&amp;rsquo;re better off using &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;
</content:encoded>
      <category>TIL</category>
      <category>Shell</category>
      <category>Unix</category>
      <category>Networking</category>
    </item>
    <item>
      <title>The *nix install command</title>
      <link>https://rednafi.com/misc/install/</link>
      <pubDate>Sun, 28 Jul 2024 00:00:00 +0000</pubDate>
      <guid>https://rednafi.com/misc/install/</guid>
      <dc:creator>Redowan Delowar</dc:creator>
      <description>Replace mkdir, cp, and chmod with a single install command. Copy files, create directories, and set permissions in one step with GNU coreutils.</description>
      <content:encoded>&lt;p&gt;TIL about the &lt;code&gt;install&lt;/code&gt; command on *nix systems. A quick &lt;a href=&#34;https://github.com/search?q=%22install&amp;#43;-D%22&amp;#43;&amp;#43;language%3Ash&amp;#43;NOT&amp;#43;npm&amp;amp;type=code&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;GitHub search for the term (opens in new tab)&#34;&gt;GitHub search for the term&lt;/a&gt;
brought up a ton of matches. I&amp;rsquo;m surprised I just found out about it now.&lt;/p&gt;
&lt;p&gt;Often, in shell scripts I need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a directory hierarchy&lt;/li&gt;
&lt;li&gt;Copy a config or binary file to the new directory&lt;/li&gt;
&lt;li&gt;Set permissions on the file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It usually looks like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Create directory hierarchy (-p creates parent directories)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir -p ~/.config/app
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Copy current config to the newly created directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cp conf ~/.config/app/conf
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Set the file permission&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chmod &lt;span class=&#34;m&#34;&gt;755&lt;/span&gt; ~/.config/app/conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Turns out, the &lt;code&gt;install&lt;/code&gt; command in &lt;a href=&#34;https://www.gnu.org/software/coreutils/manual/html_node/install-invocation.html#install-invocation&#34; rel=&#34;noopener noreferrer&#34; target=&#34;_blank&#34; aria-label=&#34;GNU coreutils (opens in new tab)&#34;&gt;GNU coreutils&lt;/a&gt; can do all that in one line:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;install -D -m &lt;span class=&#34;m&#34;&gt;755&lt;/span&gt; conf ~/.config/app/conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can check the file status with:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;stat ~/.config/app/conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;On my machine, this prints:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; File: /Users/rednafi/.config/app
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Device: 1,16    Inode: 16439606    Links: 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Access: (0755/-rwxr-xr-x)  Uid: (  501/ rednafi)   Gid: (   20/   staff)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Access: 2024-07-28 20:51:42.793765043 +0200
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Modify: 2024-07-28 20:51:42.793765043 +0200
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Change: 2024-07-28 20:51:42.793907876 +0200
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Birth: 2024-07-28 20:51:42.793765043 +0200&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-D&lt;/code&gt; flag directs &lt;code&gt;install&lt;/code&gt; to create the destination directories if they don&amp;rsquo;t exist,
and the &lt;code&gt;-m&lt;/code&gt; flag sets file permissions. The result is the same as the three lines of
commands before.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s common for Makefiles in C/C++ projects to install binaries like this:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;install -D -m &lt;span class=&#34;m&#34;&gt;744&lt;/span&gt; app_bin /usr/local/bin/app_bin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;It copies &lt;code&gt;app_bin&lt;/code&gt; to &lt;code&gt;/usr/local/bin&lt;/code&gt;, creates the parent directory hierarchy if
necessary, and sets permissions on the binary so only the current user has read, write, and
execute permissions, while others have read-only access.&lt;/p&gt;
&lt;p&gt;You can also set directory permissions:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;install -d -m &lt;span class=&#34;m&#34;&gt;600&lt;/span&gt; foo/bar/bazz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This creates the directory hierarchy first and then sets the permission. Here&amp;rsquo;s how they
look:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tree foo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Output:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;txt&#34;&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;foo
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── bar
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    └── bazz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3 directories, 0 files&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then you can copy a file to the destination and set file permissions with another &lt;code&gt;install&lt;/code&gt;
command if needed.&lt;/p&gt;
&lt;p&gt;You can also set user or group ownership while copying a file:&lt;/p&gt;
&lt;div class=&#34;codeblock&#34; translate=&#34;no&#34; data-lang=&#34;sh&#34; data-copyable&gt;
  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;install -D -m &lt;span class=&#34;m&#34;&gt;644&lt;/span&gt; -o root -g root seed.db /var/lib/app/seed.db&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command copies &lt;code&gt;seed.db&lt;/code&gt; to the destination, creates the directory if needed, and gives
access to the root user and group with the &lt;code&gt;-o&lt;/code&gt; and &lt;code&gt;-g&lt;/code&gt; flags, respectively.&lt;/p&gt;
&lt;p&gt;There are a few other options you can read about in the man pages, but I haven&amp;rsquo;t needed
anything beyond the above.&lt;/p&gt;
&lt;!-- references --&gt;
&lt;!-- prettier-ignore-start --&gt;
&lt;!-- prettier-ignore-end --&gt;
</content:encoded>
      <category>Shell</category>
      <category>Unix</category>
      <category>TIL</category>
      <category>CLI</category>
    </item>
  </channel>
</rss>

