I carry around a (very humble) collection of fzf utilities, where I intend to leverage a tacit promise
of Vim: I can edit content,
right there in the terminal, between two commands, and be done earlier than a
corporate IDE would be
flashing its splash screen1.
But let's put aside the speed: I mean to talk about the availability and convenience.
With moreutils,
this elevator pitch needs to be challenged: between
two commands? Laughable. The adept functional
programmer in me should
have sniffed it out some time ago: I can use Vim like I use sed.
⚓ The script-friendly editor
Let's start with a quick round of qualifications against the few IDEs I've had the occasion to use over the last couple of years: VS Code, Eclipse and IntelliJ.
- all three can be made to accept a file to open as an argument, though only one doesn't require prior set-up or extra gymnastics,
- only two can be spawned in
--waitmode, blocking progress of the invoking script until their interface is closed, - none of them provide a reasonable way to error out (
exit 1).
These functionalities don't sound like much, but they're part of what I would expect from a script-friendly editor.
⚓ Building a quaint example
I've prepared for this article a little utility to illustrate how neatly a terminal editor can integrate to your scripting. Let's build a script that will:
- let the user select a quote from some collection,
- prepare an e-mail sharing that quote to some unsuspecting recipient,
- let the user customise that e-mail,
- send the e-mail to the unsuspecting recipient.
Let's start off with steps 1 and 2:
MAIL=
| |
$MAIL: all the variables in the script are expansion-safe!We create a temporary2 file with mktemp, using a specific file
extension with the --suffix flag: that'll let our text editor figure out how
to highlight our document's syntax.
The Web server at
https://zenquotes.io/api/quotes will yield 50 randomised quotes (with their
respective authors' name) as a JSON array.
We'll process them with jq
and pass them along to fzf, offering an interactive interface for the user to
pick an entry. The selected quote is finally written to $MAIL, in a format
suitable for plain-text e-mails:
Date: Mon, 11 Aug 2025 00:20:43 +0200
From: ccjmne@gmail.com
To: sherlock.inbox@221b.uk
Subject: This quote made me think of you
Success is getting what you want, happiness is wanting what you get.
— W.P. Kinsella
On with the last two steps: if the user did select a quote, they'll get to edit
their e-mail in their favourite $EDITOR, with adequate highlighting for the
e-mail format. When they're done, if they neither emptied or deleted the file,
nor emitted an error (:cq), their e-mail is sent off to the unsuspecting
recipient:
if [; then
if [ && [; then
fi
fi
There you go, all in a few lines of shell scripting, while you're out
and about surfing the command line. Spawn it in a tmux pop-up and start spamming to your heart's content,
barely interrupting your flow long enough to personalise the e-mail. Fantastic!
But it gets better: you can integrate it right inside an outright pipeline.
⚓
More friendliness with moreutils
Enter moreutils, a "collection of the
Unix tools that nobody thought to write long ago when Unix was young".
It's got some niceties that do find some fame online, such as errno, sponge and ts, but the treats of the day are vipe and ifne.
With vipe, you can edit the standard input
in your favourite editor, then pipe the result to the next command. It accepts
the same3 --suffix argument as mktemp to provide your $EDITOR
with some context as to the syntax of your content.
With ifne, you can guard the execution of a command on the condition
that the standard input is not empty.
That all sounds like it could replace the entire second half of our script. Could it? Let's try it out:
-MAIL=$(mktemp --suffix .eml)
curl https://zenquotes.io/api/quotes \
| jq -r '.[] | .a + "@" + .q' \
| fzf --delimiter=@ \
--with-nth=2 \
--bind='enter:become:cat <<-EOF
...
EOF' \
- > $MAIL
-
-if [ -s $MAIL ]; then
- $EDITOR $MAIL
- if [ $? -eq 0 ] && [ -s $MAIL ]; then
- msmtp sherlock.inbox@221b.uk < $MAIL
- fi
-fi
-rm $MAIL
+ | ifne vipe --suffix eml \
+ | msmtp sherlock.inbox@221b.uk
Well, what do you know: it can. That tedious business from the earlier
script? Gone. The variable we were passing around? Gone as well.
We're left with a single pipeline: curl | jq | fzf | vipe
| msmtp. You can pretty much map it one-to-one to the original
requirements! And they say that object-oriented
programming is modelling real-world concepts...
| | | |
⚓ Compose your mastery
In isolation, this isn't anything earth-shattering, but here's the point: I can now write this up in one go; no fumbling, no hesitation.
Wielding simple, generic, composable tools lets you to reify your ideas without friction, as if you were merely transposing them more formally. That concept, and its consequences on your ability to explore, play and eventually build up mastery of your craft, are indeed of remarkable value.
Have fun!
-
So we're clear: it's not just about shaving off a few seconds here and there: wait until you learn about
fc! ↩ -
These files will routinely be cleared (historically, some systems would do that on each boot). On mine,
/tmpis a mount-point to some RAM-backed file system: the stuff going in there never makes it to an actual storage device. ↩ -
Essentially the same as that of
mktemp, though with more ingenuity: it will assume the implied leading.in your suffix. ↩
⚓ Referenced tools
| fzf | |
|---|---|
| from extra/fzf | |
| manual | repository |
| jq | |
|---|---|
| from extra/jq | |
| manual | repository |
| vipe | |
|---|---|
| from extra/moreutils | |
| manual | repository |
| ifne | |
|---|---|
| from extra/moreutils | |
| manual | repository |