I made a shortcut for posting on my new microblog:
\alias nug='nuggets_path=$HOME/wonger.dev/dist/nuggets.html export new_nugget=$( cat $nuggets_path | htmlq .card:first-of-type -r '\''.card > *:not(.bottom-line)'\'' | sed '\''s/datetime=".*"/datetime="'\''$(date +%Y-%m-%d)'\''"/'\'' | perl -pe '\''s/(#|n)([0-9]+)/$1.($2+1)/e'\'' ) vim $nuggets_path \ -c /class=\"card \ -c "let @/ = \"\"" \ -c "normal O" \ -c "r !echo \"\$new_nugget\"" \ -c "normal 2o" \ -c "normal 6ki<p></p>" \ -c "normal ^f<" \ -c startinsert'
It's ugly, I know. But it's a nice opportunity to share what I've learned about shell scripting.
OVERVIEW a
The goal: easily add a new entry to the microblog.
The old way: open
nuggets.html
, copy-paste the latest entry, manually change the ID and date, then start typing.
This takes me ~50 keystrokes, or ~8 steps:vim /path/to/nuggets.html
, then search forclass="nugget"
, then copy around tag, then paste above, then goto inner tag and delete, then goto both IDs and increment them, then figure out today's date and change the datestamp.
Normally I don't mind copy-paste. I write all the HTML on this site by hand. I go manual until it hurts. But nuggets are supposed to be a low-friction outlet for me, so I need to eliminate these steps.
The new way:
nug
, then start typing. So effortless! (screencast.mp4)
STARTING WITH HTMLQ a
htmlq
is a tool for extracting bits of HTML.
I used
htmlq
to automate the "copy" part of my copy-paste workflow:
cat nuggets.html | htmlq '.nugget:first-of-type' --remove-nodes '.nugget > *:not(.bottom-line)'
That means "select the first
.nugget
and remove all inner elements except the
.bottom-line
".
Here's what the query returns:
<div class="nugget" id="n14"> <div class="bottom-line"> #14<time datetime="2025-01-28"></time> </div> </div>
I feel like I should say more about
htmlq
, but there's nothing else to explain.
It takes CSS selectors and spits out HTML.
It's boring and I love it.
FIND AND REPLACE WITH REGULAR EXPRESSIONS a
The previous command returned a nugget, but the nugget had an old ID and date. This command replaces the date:
sed 's/datetime=".*"/datetime="'$(date +%Y-%m-%d)'"/'
And this command increments the IDs:
perl -pe 's/(#|n)([0-9]+)/$1.($2+1)/e'
Do these commands look intimidating?
The first one isn't bad if you ignore the quotation marks.
The command follows the format
s/find/replace/
.
It finds the pattern
datetime="<anything>"
and replaces it with
datetime="<today>"
.
The second command is a little more advanced.
It means "anytime the character
#
or
n
is followed by a number, increase the number by one".
In pseudocode:
let $1 = find # or n let $2 = the string of digits immediately afterwards return "$1" + "($2 + 1)"
I would've used
sed
for this substitution, but sed cannot perform arithmetic operations.
I tried
awk
too.
Like
sed
,
awk
can do substitution, along with math, control flow, functions, and variables.
But I couldn't fit this substitution into a simple one-liner.
The closest I got was
gawk '{ match($0, /(#|n)([0-9]+)/, groups); print gensub(/(#|n)([0-9]+)/, "\\1" groups[2]+1, "g")}'
which felt gross.
I ended up finding a
perl
solution.
Why did I waste time writing clumsy
awk
?
Apparently, the Perl nerds have been
saying this for decades, but I didn't hear them until now.
VIM STARTUP COMMANDS a
The final part of the alias automates the "paste" part of my copy-paste workflow.
Vim inserts the
$new_nugget
above the other nuggets and opens with the cursor at a specfic position.
Here's the command I used:
vim nuggets.html \ -c '/class="nugget"' \ -c 'let @/ = ""' \ -c 'normal O' \ -c 'read !echo "$new_nugget"' \ -c 'normal 4ki<p></p>' \ -c 'normal ^f<'
This means "goto the first occurrence of
class="nugget"
, clear the search register, insert a line above, insert the contents of
$new_nugget
, move four lines up, insert a paragraph element, and move to the second occurrence of
<
on that line."
The-c
argument gives you complete control overvim
. It's essentially a command to send keypresses at startup. I never knew this feature existed until today. There are other ways to controlvim
at startup, too. Check out:h startup-options
.
Here's what vim looks like after the first
-c
argument.
Pretend the solid rectangle is the cursor:
... <div █lass="nugget" id="n14"> <p>just a test, don't mind me</p> <div class="bottom-line"> #14<time datetime="2025-01-28"></time> </div> </div> ...
Here's the final result, ready for me to start typing:
... <div class="nugget" id="n15"> <p>█/p> <div class="bottom-line"> #15<time datetime="2025-01-29"></time> </div> </div> <div class="nugget" id="n14"> <p>just a test, don't mind me</p> <div class="bottom-line"> #14<time datetime="2025-01-28"></time> </div> </div> ...
BASH ALIAS TIPS a
I used to avoid
bash
because I didn't understand it.
Now, having written this post and learned a few things, ...I still don't like
bash
.
But I can endure it.
Here are the things I learned while writing aliases:
1) WRAP WITH SINGLE QUOTES, NOT DOUBLE QUOTES
I wrap all my aliases in single quotes. Double quotes could work too, but they often behave unexpectedly. Consider these examples:
alias date1="echo $(date)" # bad alias date2='echo $(date)' # good alias date3="echo \$(date)" # good
In the first alias,
$(date)
surprisingly evaluates when
bash
is initialized.
In the other aliases,
$(date)
evaluates as expected at runtime.
To confirm this, run
alias
.
It prints all active aliases:
$ alias ... alias date1='echo Sun Feb 9 11:00:59 AM EST 2025' alias date2='echo $(date)' alias date3='echo $(date)' ...
2) NESTED SINGLE QUOTES ARE ACTUALLY ESCAPED AND APPENDED
If a command contains single quotes, then it's a pain to wrap the command in another pair of single quotes. For example, this command:
sed 's/find/replace/'
...becomes this alias:
alias uglier='sed '\''s/find/replace/'\'''
Contrary to most programming languages, the middle quotes are not actually nested. Instead, the original string terminates, a single escaped quote is appended, and a new string begins.
That makes a little more sense if you know about string concatenation in
bash
.
These are all equivalent:
'hello world' hello\ world hello' 'world 'hello'' ''world' ''hell'o w'orl'd'
If it's any consolation, you don't have to manually escape commands with single quotes.
The shell automatically wraps and escapes quoted expressions in the parameter expansion
${parameter@Q}
.
For example:
$ myvar="sed 's/find/replace/'" $ echo ${myvar@Q} 'sed '\''s/find/replace/'\'''
3) ALIASES CAN ACCEPT ARGUMENTS
Naysayers claim aliases are inferior to functions.
They point out that aliases such as
alias wontwork='echo $1 $2'
cannot accept arguments.
Well, aliases actually can accept arguments, despite the naysayers. The trick is to declare a temporary function. In programming lingo, this is essentially an anonymous function, or a lambda:
$ alias myalias='f(){ for arg in "$@"; do echo "$arg"; done; unset -f f; }; f' $ myalias arg1 arg2 arg1 arg2
4) AN EASIER ALIAS WORKFLOW
After writing aliases in my
.bashrc
, I have to remember to
source ~/.bashrc
so the aliases take effect.
I made a shortcut so I don't have to remember that step:
alias aliases='vim ~/.bashrc -c "normal G"; source ~/.bashrc'
I have a similar gripe with the
alias
command.
When I create aliases from the command line, I must rewrite them in my
.bashrc
if I want them to be saved permanently.
I made a shortcut to skip that step too:
\alias alias='f(){ _trigger="${1%%=*}" _command="${1#*=}" _quoted_command="${_command@Q}" echo "\\alias $_trigger=$_quoted_command" >> ~/.bashrc source ~/.bashrc unset -f f }; f'
This replaces the builtin
alias
with my own implementation.
Now when I create an alias from the command line, the alias is automatically appended to my
.bashrc
.
The builtin command can still accessed with
\alias
.
In fact, all my aliases must use that prefix, or else they trigger an infinite loop.
It's a small price to pay for a greater convenience.
REFLECTIONS... a
ON BASH
Apologies for all that code vomit.
Bash is ugly, but it's important.
It's the glue of so many programming environments.
Sticking with
bash
exemplifies one of my software philosophies:
use popular old tools.
- Learning old tools is worth the investment of time and energy. If they have been popular for decades, then they'll probably be popular for many more
- Learning old tools gives you the context to understand strengths and weaknesses of new tools
- Popular old tools are thoroughly documented — in Q&A forums, in blog posts, and even inside LLMs. That is unrivaled tech support
- Compare that to slippery new tools, which are sparsely documented and often changing
- Popular old programming languages are lingua francas. You can share scripts with people, and they can share with you, because you're speaking a common language
On the other hand,
bash
suffers from a thousand papercuts.
People pad
bash
with all sorts of bandaids, such as
shellcheck
linting,
fzf
autocompletion, and
atuin
history management, on top of
.bashrc
customizations like prompt messages and aliases.
So I might return to friendly
fish
for my interactive shell and reserve
bash
for scripts.
A few final notes about shell scripting.
1) I'm not an expert.
I'm just sharing what works and what makes sense to me.
2) I realize this alias could've been a vimscript, an editor snippet, or a number of other implementations.
3) I say
bash
throughout this page, but I'm often referring to the POSIX-subset of shell syntax, and only occasionaly referring to bash-only syntax sugar.
4) If you're using hipster shells like nushell or elvish, let me know.
I'm curious how they feel as daily drivers.
ON AUTOMATION
As always, I'm thinking about
xkcd 1205
— will the time saved using
nug
be greater than the time spent writing
nug
?
Yes, actually! I plan on frequent nugget-posting, which will add up to entire minutes of savings. /nuggets is the first corner of my website where I can write freely. Anything goes. I have so much to share.
Also: automation is not always about saving time. Shortcuts reduce cognitive load, too. That's the stuffy way of saying "I'm crawling out from months of icky, foggy burnout, and I had zero energy to toil with computers, so please don't make me think too hard." Maybe it only takes thirty seconds to create an un-aliased nugget. But thirty seconds of sustained focus and decisionmaking could take just enough activation energy to turn me off from posting altogether.
I'm thankful to have highly configurable tools like
vim
and the shell environment.
I can tweak them until everything is perfectly cozy.
I think a lot of programmers feel that way — control freaks, in a way.
ON MICROBLOGGING
It's great! I have so much to say about short-form posting. But this page is already too long. I'll see you on the microblog instead :)