<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tim's blog (Posts about ed)</title><link>https://blog.thechases.com</link><description></description><atom:link href="https://blog.thechases.com/categories/ed.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:blog@tim.thechases.com"&gt;Tim Chase&lt;/a&gt; </copyright><lastBuildDate>Sat, 21 Feb 2026 00:24:27 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Old Computer Challenge</title><link>https://blog.thechases.com/posts/old-computer-challenge/</link><dc:creator>Tim Chase</dc:creator><description>&lt;h2 id="history"&gt;History&lt;/h2&gt;

&lt;p&gt;
This is the third year
in which
&lt;a href="https://bsd.network/@solene"&gt;Solene&lt;/a&gt;
has run the
&lt;a href="https://dataswamp.org/~solene/tag-oldcomputerchallenge.html"&gt;Old Computer Challenge&lt;/a&gt;
and I've tried to participate in each of them.
&lt;/p&gt;

&lt;p&gt;
Participating in these requires a little flexibility
since I work remotely for
&lt;tt&gt;$DAYJOB&lt;/tt&gt;.
The low-end computer challenges posed less issue
since neither the
&lt;abbr title="Virtual Private Network"&gt;VPN&lt;/abbr&gt;
nor
&lt;code&gt;rdesktop&lt;/code&gt;
required much in the way of resources.
Using the smaller screen-dimensions
on the older laptops
made it a bit more challenging,
but I made it work.
&lt;/p&gt;

&lt;p&gt;
None of these challenges shifted any usage to my phone.
I prefer not to use my phone for anything beyond the barest of essentials:
phone-calls,
texting,
podcasts,
timers,
lists,
and weather.
&lt;/p&gt;

&lt;h2 id="first"&gt;First year (2021)&lt;/h2&gt;

&lt;p&gt;
The
&lt;a href="https://dataswamp.org/~solene/2021-07-07-old-computer-challenge.html"&gt;first year&lt;/a&gt;
and
&lt;a href="https://dataswamp.org/~solene/2023-06-04-old-computer-challenge-v3.html"&gt;third year&lt;/a&gt;
both focused on limited hardware,
&lt;/p&gt;

&lt;p&gt;
I chose a
&lt;a href="https://www.manualslib.com/manual/270448/Gateway-Solo-1200.html#manual"&gt;Gateway Solo 1200&lt;/a&gt;
as my primary machine for the first year.
Boasting an 800MHz Celeron processor,
a 120GB spinning-rust
&lt;abbr title="hard disk drive"&gt;HDD&lt;/abbr&gt;,
a 10mbit wired LAN connection
(it also had an internal
&lt;tt&gt;wi0&lt;/tt&gt;
wireless card
and a
&lt;abbr title="Personal Computer Memory Card International Association"&gt;PCMCIA&lt;/abbr&gt;
Intel wifi option,
but both only supported
&lt;a href="https://en.wikipedia.org/wiki/Wired_Equivalent_Privacy" title="Wired Equivalent Privacy"&gt;WEP&lt;/a&gt;
rather than
&lt;a href="https://en.wikipedia.org/wiki/Wpa2" title="Wi-fi Protected Access"&gt;WPA2&lt;/a&gt;
so I stuck with the wired option),
and upgraded to its maximum of 320MB of RAM.
This machine infamously arrived on 9/11
with the UPS driver delivering it
as I watched the twin towers fall on TV.
&lt;/p&gt;

&lt;p&gt;
The machine ran the latest release of OpenBSD without issue.
The limited CPU &amp;amp; RAM limited my choice of software notably.
Fortunately, other than web-browsing,
much of what I do happens at the command-line.
&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;&lt;dl&gt;
 &lt;dt&gt;Email&lt;/dt&gt;
 &lt;dd&gt;
  I had used
  &lt;a href="https://www.claws-mail.org/"&gt;Claws Mail&lt;/a&gt;
  (a
  &lt;abbr title="Graphical User Interface"&gt;GUI&lt;/abbr&gt;
  mail program)
  for many years but it started using more and more system resources.
  So I had aspired to switch to
  &lt;a href="http://mutt.org"&gt;&lt;code&gt;mutt&lt;/code&gt;&lt;/a&gt;
  or
  &lt;a href="https://neomutt.org/"&gt;&lt;code&gt;neomutt&lt;/code&gt;&lt;/a&gt;.
  The Old Computer Challenge gave me the kick I needed.
  Dealing with multiple accounts
  and catch-all mailboxes
  posed the worst pain-points.
  Otherwise, it ran fine
  within the limited system resources.
  And they provide a lot of power
  to mow through piles of email.
 &lt;/dd&gt;

 &lt;dt&gt;Music&lt;/dt&gt;
 &lt;dd&gt;
  I've long used
  &lt;code&gt;cmus&lt;/code&gt;
  for playing my music collection,
  and
  &lt;code&gt;pianobar&lt;/code&gt;
  for streaming.
  Both ran fine even on this ancient hardware.
 &lt;/dd&gt;

 &lt;dt&gt;Calendar&lt;/dt&gt;
 &lt;dd&gt;
  I've moved my calendaring to
  &lt;a href="https://blog.thechases.com/posts/remind/"&gt;&lt;code&gt;remind&lt;/code&gt;&lt;/a&gt;
  and it runs fine at the
  &lt;abbr title="command-line interface"&gt;CLI&lt;/abbr&gt;.
  It did have a noticeable lag on startup
  but I suspect that my 3000+ reminders/events
  cause that.
  When I winnow it down to a much more sensible volume
  of reminders, it runs in a blink.
 &lt;/dd&gt;

 &lt;dt&gt;Coding&lt;/dt&gt;
 &lt;dd&gt;
  All my coding happens at the CLI
  using a mix of
  &lt;code&gt;vi&lt;/code&gt;,
  &lt;code&gt;vim&lt;/code&gt;,
  and
  &lt;code&gt;ed&lt;/code&gt;
  for editing,
  and doing version-control with
  &lt;code&gt;git&lt;/code&gt;
  or
  &lt;code&gt;rcs&lt;/code&gt;
  so not much changed here.
  I did find notable startup lag
  both in starting
  &lt;code&gt;vim&lt;/code&gt;
  and executing Python code.
  It made me appreciate the fast startup times
  for utilities that compiled down to native code.
  I also found myself using
  &lt;code&gt;awk&lt;/code&gt;
  in a lot of places since it had a faster startup time than Python.
 &lt;/dd&gt;

 &lt;dt&gt;RSS&lt;/dt&gt;
 &lt;dd&gt;
  I've long used
  &lt;a href="https://github.com/wking/rss2email"&gt;&lt;code&gt;rss2email&lt;/code&gt;&lt;/a&gt;
  to gather my RSS feeds
  and deliver them to my inbox,
  reducing the RSS-reader issue
  to a mail issue.
  I experienced no disruption here,
  since
  &lt;code&gt;mutt&lt;/code&gt;
  let me keep reading my feeds
  just as I had done in Claws.
 &lt;/dd&gt;

 &lt;dt&gt;Social media&lt;/dt&gt;
 &lt;dd&gt;
  I accessed Twitter with
  &lt;a href="https://github.com/orakaro/rainbowstream"&gt;Rainbowstream&lt;/a&gt;,
  Mastodon with
  &lt;a href="https://github.com/magicalraccoon/tootstream/"&gt;Tootstream&lt;/a&gt;,
  and
  &lt;a href="https://github.com/michael-lazar/rtv"&gt;&lt;code&gt;rtv&lt;/code&gt;&lt;/a&gt;
  (&lt;a href="https://gist.github.com/michael-lazar/8c31b9f637c3b9d7fbdcbb0eebcf2b0a"&gt;now obsolete&lt;/a&gt;)
  for Reddit.
  For text posts and commenting,
  I loved them all.
  But for image/video posts,
  they fell short.
  I wish I had a quality CLI interface for Facebook
  to keep in touch with friends &amp;amp; family
  who only share things there.
 &lt;/dd&gt;

 &lt;dt&gt;Office stuff&lt;/dt&gt;
 &lt;dd&gt;
  Thankfully, I don't have to deal with Office documents often.
  And almost never outside of
  &lt;tt&gt;$DAYJOB&lt;/tt&gt;
  so I could use Word or Excel remotely.
  I did install Abiword for the occasional MS-Word document
  and Gnumeric for the occasional spreadsheet.
  Both provided reasonable fidelity and speed
  while running within the confines of the limited hardware.
 &lt;/dd&gt;

 &lt;dt&gt;Gaming&lt;dt&gt;
 &lt;/dt&gt;&lt;/dt&gt;&lt;dd&gt;
  I don't game much,
  so this didn't impact me much.
  I think I played a couple rounds of
  &lt;code&gt;cribbage(6)&lt;/code&gt;
  and
  &lt;code&gt;&lt;abbr title="Air Traffic Control"&gt;atc(6)&lt;/abbr&gt;&lt;/code&gt;
  as a proof-of-concept,
  but certainly no high-end
  &lt;abbr title="First Person Shooter"&gt;FPS&lt;/abbr&gt;
  games here.
 &lt;/dd&gt;

&lt;/dl&gt;


&lt;p&gt;
Web browsing hurt the most.
Firefox &amp;amp; Chromium?
Completely unusable without gobs of RAM.
For some basic browsing,
&lt;code&gt;lynx&lt;/code&gt;
and
&lt;code&gt;dillo&lt;/code&gt;
provided lightweight options,
while Epiphany clocked in at barely-usable
(but still better than Firefox &amp;amp; Chromium)
for sites requiring JavaScript.
&lt;/p&gt;

&lt;h2 id="second"&gt;Second year (2022)&lt;/h2&gt;

&lt;p&gt;
The
&lt;a href="https://dataswamp.org/~solene/2022-07-01-oldcomputerchallenge-v2-rtc.html"&gt;second year&lt;/a&gt;
focused more on limiting network usage
(both total-time and bandwidth).
&lt;/p&gt;&lt;p&gt;

&lt;/p&gt;&lt;p&gt;
I had to segregate life here since
&lt;tt&gt;$DAYJOB&lt;/tt&gt;
requires remoting into my work machine
so I didn't count that time against my allotted 1hr.
&lt;/p&gt;

&lt;p&gt;
I didn't know how to count my
&lt;code&gt;cron&lt;/code&gt;
job that downloads my podcasts nightly
since I don't have much control over
how long they run
or how much data they download.
I decided that,
since the challenge only ran for a week,
and I batch podcasts roughly every three weeks,
I could load a fresh batch to my player
before the challenge,
disable the
&lt;code&gt;cron&lt;/code&gt;
job for the week,
and then re-enable the 
&lt;code&gt;cron&lt;/code&gt;
job after completing the challenge.
Not quite the spirit of the challenge,
but also a lot like how I would download things in high-school,
where I would walk to the local campus library to download
&lt;span title="absolutely huge, like multiple megabytes!"&gt;large files&lt;/span&gt;
and bring them home.
&lt;/p&gt;

&lt;p&gt;
Email didn't pose a great concern,
since
&lt;a href="https://www.offlineimap.org/"&gt;OfflineIMAP&lt;/a&gt;
let me batch download my emails from the server,
and my local
&lt;abbr title="Mail Transfer Agent"&gt;MTA&lt;/abbr&gt;
would batch up outbound emails until I reconnected,
sending them all to my smart-host mail-server in one go.
&lt;/p&gt;

&lt;p&gt;
However, the second year really cut into social-media usage.
Its model simply doesn't accommodate offline use well.
&lt;/p&gt;

&lt;h2 id="third"&gt;Third year (2023)&lt;/h2&gt;

&lt;p&gt;
Similar to the
&lt;a href="https://blog.thechases.com/posts/old-computer-challenge/#first"&gt;first year&lt;/a&gt;
the tools remained largely the same.
However this year I did the challenge
while on vacation.
Cheating?
Maybe.
But also enforcing since I didn't take any other laptop.
This time I took a
&lt;a href="https://en.wikipedia.org/wiki/Dell_Inspiron_Mini_Series#10_Series"&gt;Dell Mini10 netbook&lt;/a&gt;
with me.
This hand-me-down came to me with 2GB of RAM,
but I'd made a few upgrades:
 &lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;
   replaced the 120MB HDD with a 60GB
   &lt;abbr title="Solid State Drive"&gt;SSD&lt;/abbr&gt;
   giving a bit of extra pep
  &lt;/li&gt;
  &lt;li&gt;
   replaced the rubbish Broadcom wireless
   half-height PCI card with an Atheros chipset 
  &lt;/li&gt;
  &lt;li&gt;
   installed OpenBSD 7.3 in place of Windows Vista
  &lt;/li&gt;
 &lt;/ul&gt;


&lt;p&gt;
The netbook has no fan,
relying on passive cooling instead.
This meant that using
&lt;code&gt;apm -L&lt;/code&gt;
kept the system running cool.
I could manually 
&lt;code&gt;apm -H&lt;/code&gt;
to get the full 1.x GHz
but it came with a warm price,
discouraging me from doing so.
&lt;/p&gt;

&lt;p&gt;
The tiny 1024×600 screen resolution
gave even greater constraints
when remoting into
&lt;tt&gt;$DAYJOB&lt;/tt&gt;
but, that helped me stay in vacation-mode
rather than try to sneak in hours.
Additionally, X seemed to think the display
offered 1024×768 resolution,
so everything rendered with a squishing/scaling
that ruined friends' pictures.
And equally bad,
the Poulsbo chipset
lacked support in X,
so it rendered
&lt;em&gt;very slowly&lt;/em&gt;
using VESA.
But I had times where I could watch text render character-by-character,
and could type full paragraphs of text
before the first couple words appeared on the screen.
With better graphics-support,
I suspect it would have felt notably snappier.
&lt;/p&gt;

&lt;h2 id="future"&gt;Future challenges&lt;/h2&gt;

&lt;p&gt;
After returning from that vacation,
I purchased a new laptop for travel,
and got rid of four of my old junker laptops
(my beloved rejoices at fewer laptops on my desk).
I still have the Mini10
and a
&lt;abbr title="PowerPC"&gt;PPC&lt;/abbr&gt;
&lt;a href="https://en.wikipedia.org/wiki/IBook#iBook_G4_(%22Snow%22)"&gt;iBook G4&lt;/a&gt;
running OpenBSD,
so I can participate in future challenges.
&lt;/p&gt;</description><category>ed</category><category>openbsd</category><guid>https://blog.thechases.com/posts/old-computer-challenge/</guid><pubDate>Mon, 10 Jul 2023 13:52:33 GMT</pubDate></item><item><title>Using mail(1)</title><link>https://blog.thechases.com/posts/using-mail/</link><dc:creator>Tim Chase</dc:creator><description>&lt;h2 id="intro"&gt;Intro&lt;/h2&gt;

&lt;figure&gt;
&lt;pre&gt;
CONGRATULATIONS! Your OpenBSD install has been successfully completed!

When you login to your new system the first time, please read your mail
using the 'mail' command.
&lt;/pre&gt;
&lt;figcaption&gt;&lt;a href="https://openbsd.org"&gt;OpenBSD&lt;/a&gt;'s
post-install message&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Oh, no!

You have a fresh install
and the only way to read
your welcome message from Theo
requires using
&lt;code&gt;mail&lt;/code&gt;.

&lt;!-- TEASER_END --&gt;

Or perhaps you've exited some long-running program
and your shell informs you
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
You have mail in /var/mail/&lt;var&gt;$USER&lt;/var&gt;
&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;
However, upon launching
&lt;code&gt;mail&lt;/code&gt;,
most users find that the interface has all the friendliness of
&lt;a href="https://blog.thechases.com/posts/cli/unsorted-ed-tips-and-tricks/"&gt;&lt;code&gt;ed(1)&lt;/code&gt;&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
For decades,
Unix-like systems have offered
&lt;a href="https://man.openbsd.org/mail.1"&gt;&lt;code&gt;mail(1)&lt;/code&gt;&lt;/a&gt;
as a way to read the system mailbox
and send mail.
While not as flashy or featureful as more modern
&lt;abbr title="Mail User Agent"&gt;MUA&lt;/abbr&gt;s
like
&lt;a href="https://www.sdaoden.eu/code.html#s-nail"&gt;s-nail&lt;/a&gt;,
&lt;a href="http://mutt.org"&gt;Mutt&lt;/a&gt;,
&lt;a href="https://neomutt.org"&gt;NeoMutt&lt;/a&gt;,
&lt;a href="https://alpineapp.email/"&gt;Alpine&lt;/a&gt;,
or
&lt;a href="https://aerc-mail.org/"&gt;Aerc&lt;/a&gt;
(or even graphical MUAs like
&lt;a href="https://claws-mail.org/"&gt;Claws Mail&lt;/a&gt;,
&lt;a href="https://www.thunderbird.net/"&gt;Thunderbird&lt;/a&gt;,
&lt;a href="https://apps.kde.org/kmail2/"&gt;KMail&lt;/a&gt;,
or Gnome
&lt;a href="https://wiki.gnome.org/Apps/Evolution"&gt;Evolution&lt;/a&gt;),
it provides a surprising bit of functionality
hidden under the hood.
&lt;/p&gt;

&lt;h2 id="history"&gt;History&lt;/h2&gt;

&lt;p&gt;
Over the years,
a variety of
&lt;abbr title="Command Line Interface"&gt;CLI&lt;/abbr&gt;
programs have called themselves some form of "mail",
including
&lt;code&gt;mail&lt;/code&gt;,
&lt;code title="Note the capital M"&gt;Mail&lt;/code&gt;,
&lt;code&gt;mailx&lt;/code&gt;,
&lt;code&gt;heirloom-mail&lt;/code&gt;,
and
&lt;code&gt;s-nail&lt;/code&gt;,
with subtle differences in each of them.
For purposes of this post,
I'll try to stick mostly to
&lt;code&gt;mail&lt;/code&gt;/&lt;code&gt;mailx&lt;/code&gt;,
&lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/mailx.html"&gt;as defined by POSIX&lt;/a&gt;
which means
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;
  no IMAP/POP/SMTP support
 &lt;/li&gt;
 &lt;li&gt;
  no Maildir or MH support,
  only classic
  &lt;code&gt;mbox&lt;/code&gt;
  format
 &lt;/li&gt;
 &lt;li&gt;
  no built-in spam filtering
 &lt;/li&gt;
 &lt;li&gt;
  no threading
 &lt;/li&gt;
 &lt;li&gt;
  no native support for
  &lt;abbr title="Multipurpose Internet Mail Extensions"&gt;MIME&lt;/abbr&gt;
  types other than
  &lt;code&gt;text/plain&lt;/code&gt;
  (so no
  &lt;code&gt;text/html&lt;/code&gt;)
 &lt;/li&gt;
&lt;/ul&gt;
that you might find in some iterations such as
&lt;a href="https://heirloom.sourceforge.net/mailx.html"&gt;heirloom
&lt;code&gt;mailx&lt;/code&gt;&lt;/a&gt;.

Specifically, all examples here
should work in
&lt;code&gt;mail&lt;/code&gt;
as provided by
&lt;a href="https://freebsd.org"&gt;FreeBSD&lt;/a&gt;
&amp;amp;
&lt;a href="https://openbsd.org"&gt;OpenBSD&lt;/a&gt;
base systems.


&lt;p&gt;
This post assumes some
&lt;a href="https://blog.thechases.com/posts/using-mail/#system-configuration"&gt;basic system configuration&lt;/a&gt;
and will discuss the three essential modes of operation:
&lt;/p&gt;&lt;ol&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/using-mail/#mode-send"&gt;send mode&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;
  interactive
  &lt;a href="https://blog.thechases.com/posts/using-mail/#mode-read"&gt;mail reading mode&lt;/a&gt;,
  and
  &lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/using-mail/#mode-composition"&gt;mail composition mode&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
Additionally, this post will cover some common
&lt;a href="https://blog.thechases.com/posts/using-mail/#settings"&gt;configuration settings&lt;/a&gt;
as well as some
&lt;a href="https://blog.thechases.com/posts/using-mail/#tips"&gt;tips &amp;amp; tricks&lt;/a&gt;.


&lt;h2 id="system-configuration"&gt;System configuration expectations&lt;/h2&gt;

&lt;p&gt;
By default,
most Linux &amp;amp; BSD systems provide
local mail receipt &amp;amp; delivery
out of the box on a fresh install,
whether with
&lt;a href="https://www.proofpoint.com/us/products/open-source-email-solution" &lt;code&gt;sendmail&lt;/a&gt;,
&lt;a href="https://www.exim.org/"&gt;&lt;code&gt;exim&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://www.postfix.org/"&gt;Postfix&lt;/a&gt;,
or my favorite,
&lt;a href="https://opensmtpd.org/"&gt;OpenSMTPD&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
To exchange mail with external systems,
you'll have to configure your
&lt;abbr title="Mail Transfer Agent"&gt;MTA&lt;/abbr&gt;
as well as possibly modify your
&lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt;
to point your
&lt;abbr title="Mail Exchange"&gt;MX&lt;/abbr&gt;
record at your server,
set up
&lt;a href="https://en.wikipedia.org/wiki/Sender_Policy_Framework"&gt;&lt;abbr title="Sender Policy Framework"&gt;SPF&lt;/abbr&gt;&lt;/a&gt;
&amp;amp;
&lt;a href="https://en.wikipedia.org/wiki/DMARC"&gt;&lt;abbr title="Domain-based Message Authentication, Reporting and Conformance"&gt;DMARC&lt;/abbr&gt;&lt;/a&gt;
and you might want some sort of spam-filtering solution in place.

However, since MTA choice &amp;amp; configuration
fall outside the scope of this post,
use whatever you prefer
as long as your
&lt;abbr title="Mail Delivery Agent"&gt;MDA&lt;/abbr&gt;
delivers to the user's system
&lt;code&gt;mbox&lt;/code&gt;
(usually
&lt;code&gt;/var/mail/&lt;var&gt;$USER&lt;/var&gt;&lt;/code&gt;
or
&lt;code&gt;/var/spool/mail/&lt;var&gt;$USER&lt;/var&gt;&lt;/code&gt;)
and your MTA has compatibility with
&lt;code&gt;sendmail&lt;/code&gt;
(usually done through a binary or link at
&lt;code&gt;/usr/sbin/sendmail&lt;/code&gt;).
&lt;/p&gt;

&lt;p&gt;
Finally,
&lt;code&gt;mail&lt;/code&gt;
assumes that it can lock mailboxes
at the file-system level.
On local file-systems,
this doesn't generally pose a problem.
&lt;span class="warning"&gt;
However, if your system
&lt;code&gt;mbox&lt;/code&gt;
resides on an
&lt;abbr title="Network File System"&gt;NFS&lt;/abbr&gt;
share with unreliable locking,
you may encounter issues/conflicts
if more than one process attempts to modify the mailbox.
&lt;/span&gt;
&lt;/p&gt;

&lt;h2 id="modes"&gt;Essential modes of operation&lt;/h2&gt;

&lt;h3 id="mode-send"&gt;Send mode&lt;/h3&gt;

&lt;p&gt;
The most simple of the modes,
this expects the email message body on
&lt;tt&gt;stdin&lt;/tt&gt;,
an optional subject passed with the
&lt;code&gt;-s&lt;/code&gt;
option,
&lt;abbr title="Carbon Copy"&gt;CC&lt;/abbr&gt;
&amp;amp;
&lt;abbr title="Blind Carbon Copy"&gt;BCC&lt;/abbr&gt;
fields with the
&lt;code&gt;-c&lt;/code&gt;
&amp;amp;
&lt;code&gt;-b&lt;/code&gt;
options,
followed by the list of recipient(s).
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;echo "ask about surgery" |
&lt;label class="user"&gt;&amp;gt; &lt;/label&gt;mail -s "Call Mom" &lt;var&gt;$USER&lt;/var&gt;&lt;/kbd&gt;

&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;calendar |
&lt;label class="user"&gt;&amp;gt; &lt;/label&gt;mail -s "Our calendar for $(date +"%Y-%m-%d")" \
&lt;label class="user"&gt;&amp;gt; &lt;/label&gt;&lt;var&gt;me@example.net&lt;/var&gt; &lt;var&gt;spouse@example.net&lt;/var&gt;&lt;/kbd&gt;

&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;git log -10 |
&lt;label class="user"&gt;&amp;gt; &lt;/label&gt;mail -s "&lt;var&gt;What I've done this week&lt;/var&gt;" \
&lt;label class="user"&gt;&amp;gt; &lt;/label&gt;-b &lt;var&gt;owner@example.com&lt;/var&gt; \
&lt;label class="user"&gt;&amp;gt; &lt;/label&gt;-c &lt;var&gt;boss@example.com&lt;/var&gt; \
&lt;label class="user"&gt;&amp;gt; &lt;/label&gt;&lt;var&gt;coworker@example.com&lt;/var&gt;&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;
Examples of using
&lt;code&gt;mail&lt;/code&gt;
in command pipelines
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Send mode works well in scripts
to email output of commands,
and often
&lt;code&gt;cron&lt;/code&gt;
&amp;amp;
&lt;code&gt;at&lt;/code&gt;
use
&lt;code&gt;mail&lt;/code&gt;
to send the results of each task to the owner.
&lt;/p&gt;

&lt;p&gt;
If
&lt;code&gt;mail&lt;/code&gt;
reads from an interactive terminal,
it affords
&lt;a href="https://blog.thechases.com/posts/using-mail/#tilde-escapes"&gt;tilde-escape&lt;/a&gt;
operations for editing the message.
When finished with the message,
use an
&lt;abbr title="End Of File"&gt;EOF&lt;/abbr&gt;
with
&lt;kbd&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;d&lt;/kbd&gt;&lt;/kbd&gt;
to send the message.
&lt;/p&gt;

&lt;h3 id="mode-read"&gt;Mail reading mode&lt;/h3&gt;

&lt;h4 id="read-start"&gt;Starting &lt;code&gt;mail&lt;/code&gt;&lt;/h4&gt;

&lt;p&gt;
If you currently have mail in your system mailbox,
POSIX shells should inform you (roughly) every
&lt;code&gt;$MAILCHECK&lt;/code&gt;
seconds
(default=600, or 10 minutes)
if the last-modified time
of the system mailbox
(as specified by
&lt;code&gt;$MAIL&lt;/code&gt;)
has changed.

Launching
&lt;code&gt;mail&lt;/code&gt;
with no arguments opens
&lt;code&gt;mail&lt;/code&gt;,
&lt;a href="https://blog.thechases.com/posts/using-mail/#setting-header"&gt;displays
a screenful of message headers&lt;/a&gt;,
and leaves you at an
"&lt;tt&gt;&amp;amp;&lt;/tt&gt;"
prompt.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
You have mail in /var/mail/demo
&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;mail&lt;/kbd&gt;
Mail version &lt;var&gt;8.1.2 01/15/2001&lt;/var&gt;.  Type ? for help.
"/var/mail/demo": 2 messages 2 new
&amp;gt;N  1 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  2 root@example.com  Fri Dec 23 01:30   32/963   example.com daily
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;
Example of how
&lt;code&gt;mail&lt;/code&gt;
greets you
&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id="read-stop"&gt;Exiting &lt;code&gt;mail&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;
To quit back to your shell without modifying the mailbox,
use the
&lt;kbd&gt;x&lt;/kbd&gt;/&lt;kbd&gt;exit&lt;/kbd&gt;
command.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;x&lt;/kbd&gt;
&lt;label class="user"&gt;$ &lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;quitting&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
If you use the
&lt;kbd&gt;q&lt;/kbd&gt;/&lt;kbd&gt;quit&lt;/kbd&gt;
command or type
&lt;kbd&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;d&lt;/kbd&gt;&lt;/kbd&gt;
to send an
&lt;abbr title="end of file"&gt;EOF&lt;/abbr&gt;,
it will change the message status of unread messages from
&lt;samp&gt;N&lt;/samp&gt;
(new)
to
&lt;samp&gt;U&lt;/samp&gt;
(unread).
By default,
if you have read any of the messages in your system mailbox,
they will get moved to
&lt;code&gt;~/mbox&lt;/code&gt;
upon quitting with
&lt;kbd&gt;q&lt;/kbd&gt;.
The
&lt;a href="https://blog.thechases.com/posts/using-mail/#setting-hold"&gt;&lt;code&gt;hold&lt;/code&gt;&lt;/a&gt;
setting prevents
&lt;code&gt;mail&lt;/code&gt;
from moving messages out of the system mailbox
unless you explicitly (re)move them.
&lt;/p&gt;

&lt;h4 id="read-list"&gt;Listing messages&lt;/h4&gt;

&lt;p&gt;
By default,
when
&lt;code&gt;mail&lt;/code&gt;
starts up,
it shows you a screenful of message-summaries,
one per line.
Each message has an identifying number/index
that should remain valid for the entire session.
The header-listing consists of the following fields:
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;
  flags
  (&lt;strong&gt;N&lt;/strong&gt;ew,
  &lt;strong&gt;U&lt;/strong&gt;nread,
  &lt;strong&gt;P&lt;/strong&gt;reserved,
  &lt;strong&gt;M&lt;/strong&gt; send to
   &lt;code&gt;mbox&lt;/code&gt;,
  and
  &lt;strong&gt;*&lt;/strong&gt;
  saved)
  &lt;/li&gt;
 &lt;li&gt;message index&lt;/li&gt;
 &lt;li&gt;sender&lt;/li&gt;
 &lt;li&gt;date&lt;/li&gt;
 &lt;li&gt;size (in lines and bytes)&lt;/li&gt;
 &lt;li&gt;subject&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;
If you have more than one screenful of messages,
you can use the
&lt;kbd&gt;z&lt;/kbd&gt;
command to list the next screenful,
&lt;kbd&gt;z-&lt;/kbd&gt;
to list the previous screenful,
and
&lt;kbd&gt;h&lt;/kbd&gt;
to re-display the current screenful.
When you first start
&lt;code&gt;mail&lt;/code&gt;,
it displays the first page of message-summaries
as if you had typed
&lt;kbd&gt;h&lt;/kbd&gt;.
&lt;/p&gt;

&lt;p&gt;
To list message summaries matching particular criteria,
you can use the
&lt;kbd&gt;f&lt;/kbd&gt;/&lt;kbd&gt;from&lt;/kbd&gt;
command with a message-list filter.
Message-list filters can consist of a single message number,
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f 5&lt;/kbd&gt;
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
&lt;/pre&gt;
&lt;figcaption&gt;show summary for message 5&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
multiple message numbers,
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f 5 7 9&lt;/kbd&gt;
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  7 root@example.com  Sat Jan  7 00:08   22/1191  Cron &amp;lt;upload@example.com&amp;gt;
 N  9 root@example.com  Sat Jan  7 00:17 13789/971549 Cron &amp;lt;upload@example.com&amp;gt;
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages 5, 7, and 9&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
or a range of numbers:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f 5-8&lt;/kbd&gt;
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  7 root@example.com  Sat Jan  7 00:08   22/1191  Cron &amp;lt;upload@example.com&amp;gt;
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages 5–8&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
You can use a
&lt;kbd&gt;$&lt;/kbd&gt;
to indicate the last message in the mailbox:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f 7-$&lt;/kbd&gt;
 N  7 root@example.com  Sat Jan  7 00:08   22/1191  Cron &amp;lt;@example.com&amp;gt;
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
 N  9 root@example.com  Sat Jan  7 00:17 13789/971549 Cron &amp;lt;upload@example.com&amp;gt;
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages 7 through the last one&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
These can combine to list particular messages by number &amp;amp; range:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f 2 4-6 8-$&lt;/kbd&gt;
    2 root@example.com  Wed Dec 14 01:30   29/931   example.com daily
 U  4 tim               Wed Dec 14 21:11   16/597   Call Mom
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
 N  9 root@example.com  Sat Jan  7 00:17 13789/971549 Cron &amp;lt;upload@example.com&amp;gt;
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages 2, 4–6, and 8 through the last one&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
If you want to refer to all messages in the mailbox,
you can use
&lt;kbd&gt;*&lt;/kbd&gt;
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f *&lt;/kbd&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;show summary for every message in the mailbox&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
though this may scroll past
if you have more than one screenful of messages.
&lt;/p&gt;

&lt;p&gt;
Additionally, you can list messages by category of message
&lt;/p&gt;&lt;dl class="compact"&gt;
&lt;dt&gt;&lt;kbd&gt;:d&lt;/kbd&gt;&lt;/dt&gt;&lt;dd&gt;deleted messages&lt;/dd&gt;
&lt;dt&gt;&lt;kbd&gt;:n&lt;/kbd&gt;&lt;/dt&gt;&lt;dd&gt;new messages&lt;/dd&gt;
&lt;dt&gt;&lt;kbd&gt;:o&lt;/kbd&gt;&lt;/dt&gt;&lt;dd&gt;old messages&lt;/dd&gt;
&lt;dt&gt;&lt;kbd&gt;:r&lt;/kbd&gt;&lt;/dt&gt;&lt;dd&gt;read messages&lt;/dd&gt;
&lt;dt&gt;&lt;kbd&gt;:u&lt;/kbd&gt;&lt;/dt&gt;&lt;dd&gt;unread messages&lt;/dd&gt;
&lt;/dl&gt;
There's a subtle difference between
"new" and "old" messages
that might matter to some users,
but that distinction has never mattered much to me.


&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f :r&lt;/kbd&gt;
    2 root@example.com  Wed Dec 14 01:30   29/931   example.com daily
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages that you have read&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Finally, you can list messages by searching.
If you use a regular string,
&lt;code&gt;mail&lt;/code&gt;
will search the "From" header for mail from that recipient:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f bob@example.edu&lt;/kbd&gt;
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages from bob@example.edu&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
You can use partial matching as well:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f bob&lt;/kbd&gt;
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages from senders containing "bob"&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
If you prefix the search string with a
&lt;kbd&gt;/&lt;/kbd&gt;,
it will search messages' Subject field:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;f /thesis&lt;/kbd&gt;
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
&lt;/pre&gt;
&lt;figcaption&gt;show summary for messages where the subject contains "thesis"&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id="read-print"&gt;Displaying messages&lt;/h4&gt;

&lt;p&gt;
To display a message,
use the
&lt;kbd&gt;p&lt;/kbd&gt;/&lt;kbd&gt;print&lt;/kbd&gt;
command.
Without a message-list following the
&lt;kbd&gt;p&lt;/kbd&gt;/&lt;kbd&gt;print&lt;/kbd&gt;
command,
&lt;code&gt;mail&lt;/code&gt;
will print the message headers and the body
of the current message.
If the length of the message exceeds the height of the screen
(or the value of the
&lt;code&gt;crt&lt;/code&gt;
setting, if set),
the headers and body will get sent to your
&lt;code&gt;$PAGER&lt;/code&gt;.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;p &lt;var&gt;8&lt;/var&gt;&lt;/kbd&gt;
Message 8:
From bob@example.edu Sat Jan  7 00:08:53 2023
Return-Path: &amp;lt;bob@example.edu&amp;gt;
X-Original-To: demo@example.net
Delivered-To: demo@example.net
Received: by example.net (Postfix, from userid 1234)
        id 533AC0FFEE; Sat,  7 Jan 2023 00:08:53 +0000 (UTC)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Message-Id: &amp;lt;20230125170817.533AC0FFEE@example.edu&amp;gt;
Date: Sat Jan  7 00:08:53 2023 -0600 (CST)
From: Professor Bob &amp;lt;bob@example.edu&amp;gt;
To: demo@example.net
Subject: Comments on your thesis

Wow, I've never seen such rubbish.
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;printing message #8&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p id="retain"&gt;
A lot of headers clutter that output,
so I often use
&lt;code&gt;retain&lt;/code&gt;
(or
&lt;code&gt;ignore&lt;/code&gt;)
to winnow these to what I care about.
I 
&lt;code&gt;retain&lt;/code&gt;
only those headers I want to see:
&lt;/p&gt;
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;&lt;mark&gt;retain &lt;var&gt;cc date subject&lt;/var&gt;&lt;/mark&gt;&lt;/kbd&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;p &lt;var&gt;8&lt;/var&gt;&lt;/kbd&gt;
Message 8:
From bob@example.edu Sat Jan  7 00:08:53 2023
Date: Sat Jan  7 00:08:53 2023 -0600 (CST)
Subject: Comments on your thesis

Wow, I've never seen such rubbish.
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;setting retained headers&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Much better.
Well, except for the quality of the thesis.
&lt;/p&gt;

&lt;p&gt;
After using
&lt;code&gt;retain&lt;/code&gt;,
if you subsequently want to see
&lt;strong&gt;all&lt;/strong&gt;
the headers,
you can use the
&lt;kbd&gt;P&lt;/kbd&gt;/&lt;kbd&gt;Print&lt;/kbd&gt;
(with a capital "P")
command.
&lt;/p&gt;

&lt;h4 id="read-delete"&gt;Deleting (and undeleting) messages&lt;/h4&gt;

&lt;p&gt;
The
&lt;kbd&gt;d&lt;/kbd&gt;/&lt;kbd&gt;delete&lt;/kbd&gt;
command deletes the current message,
while
&lt;kbd&gt;u&lt;/kbd&gt;/&lt;kbd&gt;undelete&lt;/kbd&gt;
undeletes a deleted message.
Both accept a list of message-IDs
like the
&lt;a href="https://blog.thechases.com/posts/using-mail/#read-print"&gt;&lt;code&gt;print&lt;/code&gt;&lt;/a&gt;
command.
You can undelete messages
as long as you haven't closed
the current mailbox file.
If you accidentally delete a bunch of messages
remember you can always use
&lt;a href="https://blog.thechases.com/posts/using-mail/#read-stop"&gt;&lt;kbd&gt;x&lt;/kbd&gt;&lt;/a&gt;
to quit safely without writing any changes,
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;d &lt;var&gt;3 8 5&lt;/var&gt;&lt;/kbd&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;u &lt;var&gt;/thesis&lt;/var&gt;&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;deleting &amp;amp; undeleting messages&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id="read-reply"&gt;Replying to messages&lt;/h4&gt;

&lt;p&gt;
After reading a message,
you can use the
&lt;kbd&gt;r&lt;/kbd&gt;/&lt;kbd&gt;reply&lt;/kbd&gt;
command to reply to all the recipients,
or the
&lt;kbd&gt;R&lt;/kbd&gt;/&lt;kbd&gt;Reply&lt;/kbd&gt;
command to reply only to the author of the message.
By default,
&lt;code&gt;mail&lt;/code&gt;
doesn't include the original message,
so you may want to use the
&lt;a href="https://blog.thechases.com/posts/using-mail/#tilde-m"&gt;&lt;kbd&gt;~m&lt;/kbd&gt;&lt;/a&gt;
command to read the current message
into the reply,
prefixed with
&lt;code&gt;indentprefix&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
The section on
&lt;a href="https://blog.thechases.com/posts/using-mail/#mail-composition"&gt;mail composition&lt;/a&gt;
has more details on how to edit your reply.
&lt;/p&gt;

&lt;h4 id="read-compose"&gt;Composing new messages&lt;/h4&gt;

&lt;p&gt;
To compose a new message from within
&lt;code&gt;mail&lt;/code&gt;,
use the
&lt;kbd&gt;m&lt;/kbd&gt;/&lt;kbd&gt;mail&lt;/kbd&gt;
command,
specifying the recipient.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;mail &lt;var&gt;customerservice@example.com&lt;/var&gt;&lt;/kbd&gt;
Subject: &lt;kbd&gt;broken frobniculator
I bought a new frobniculator last week
but it broke within the first 10 minutes
of use.

How can I obtain a replacement?

Thanks!
.&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;composing a new message&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
The section on
&lt;a href="https://blog.thechases.com/posts/using-mail/#mail-composition"&gt;mail composition&lt;/a&gt;
has more details on how to compose your message.
&lt;/p&gt;

&lt;h4 id="read-save"&gt;Filing messages&lt;/h4&gt;

&lt;p&gt;
Once you have read a message,
rather than delete it,
you may prefer to file it
in a separate mbox archive
so it doesn't clutter your main inbox.
Use the
&lt;kbd&gt;s&lt;/kbd&gt;/&lt;kbd&gt;save&lt;/kbd&gt;
command to specify an mbox file.
You can specify a message-list between the
&lt;kbd&gt;save&lt;/kbd&gt;
command and the mbox filename
to save multiple messages
in one command.
&lt;/p&gt;

&lt;p&gt;
Alternatively,
if you would like to save a
&lt;em&gt;copy&lt;/em&gt;
of the message,
while keeping the original
in the current mailbox,
use the
&lt;kbd&gt;c&lt;/kbd&gt;/&lt;kbd&gt;copy&lt;/kbd&gt;
command which has the same syntax as
&lt;kbd&gt;save&lt;/kbd&gt;.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;p &lt;var&gt;/thesis&lt;/var&gt;&lt;/kbd&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;s &lt;var&gt;thesis&lt;/var&gt;&lt;/kbd&gt;
"thesis" [New file]
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;copy &lt;var&gt;10 12&lt;/var&gt; &lt;var&gt;bills&lt;/var&gt;&lt;/kbd&gt;
"bills" [Appended]
&lt;/pre&gt;
&lt;figcaption&gt;filing messages in another folder&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id="read-changing-mailboxes"&gt;Changing mailboxes&lt;/h4&gt;

&lt;p&gt;
Great.
Now you can read mail
from your system mailbox.
And you can file mail in other mbox stores.
How do you access those folders?
You can either specify it on the shell command-line with
&lt;kbd&gt;-f&lt;/kbd&gt;
or within mail using the
&lt;kbd&gt;file&lt;/kbd&gt;
command.
The
&lt;a href="https://blog.thechases.com/posts/using-mail/#setting-folder"&gt;&lt;code&gt;folder&lt;/code&gt; setting&lt;/a&gt;
allows you to prefix a folder-name with "+"
to refer to mbox stores in that folder
regardless of your current working-directory.
Additionally, you can use
"%" to refer to your system mailbox,
"#" to refer to the previous folder you had open,
and "&amp;amp;" to refer to your
&lt;a href="https://blog.thechases.com/posts/using-mail/#setting-mbox"&gt;&lt;code&gt;MBOX&lt;/code&gt; folder&lt;/a&gt;.
&lt;/p&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;set folder="mail"&lt;/kbd&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;copy &lt;var&gt;8&lt;/var&gt; +&lt;var&gt;thesis&lt;/var&gt;&lt;/kbd&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;folders&lt;/kbd&gt;
bills   thesis
&lt;/pre&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;mail -f &lt;var&gt;thesis&lt;/var&gt;&lt;/kbd&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;folders&lt;/kbd&gt;
bills   thesis
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;file +&lt;var&gt;bills&lt;/var&gt;&lt;/kbd&gt;
&lt;label class="visual"&gt;⋮ &lt;var&gt;# messages from the "bills" folder&lt;/var&gt;&lt;/label&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;file %&lt;/kbd&gt;
&lt;label class="visual"&gt;⋮ &lt;var&gt;# messages in the $MAIL spool&lt;/var&gt;&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;opening different mailboxes&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id="checking-for-new-mail"&gt;Checking for new mail&lt;/h4&gt;

&lt;p&gt;
While in a
&lt;code&gt;mail&lt;/code&gt;
session,
new mail might arrive in your system mailbox
(or an external process might have modified
your current folder).
Use the
&lt;code&gt;inc&lt;/code&gt;
command to "incorporate" new mail.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;h&lt;/kbd&gt;
 N  1 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;inc&lt;/kbd&gt;
"/var/mail/&lt;var&gt;$USER&lt;/var&gt;": 2 messages 2 new
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;h&lt;/kbd&gt;
 N  1 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
 N  2 bob@example.edu   Sat Jan  9 11:33   48/2158  More comments
&lt;/pre&gt;
&lt;figcaption&gt;Incorporating new mail&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id="mode-composition"&gt;Mail composition mode&lt;/h3&gt;

&lt;p&gt;
When creating a new message,
either via the shell
&lt;/p&gt;&lt;pre&gt;
&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;mail -s "My subj" user@example.com&lt;/kbd&gt;
&lt;/pre&gt;
or
&lt;code&gt;mail&lt;/code&gt;
command-line
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;m user@example.com&lt;/kbd&gt;
Subject: &lt;kbd&gt;My subject&lt;/kbd&gt;
&lt;/pre&gt;
by default
&lt;code&gt;mail&lt;/code&gt;
lets you enter your message
one line at time
and terminate it with a period or EOF:
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;m bob@example.edu&lt;/kbd&gt;
Subject: &lt;kbd&gt;thesis feedback
Thanks for your helpful feedback.
.&lt;/kbd&gt;
&lt;/pre&gt;


&lt;h4 id="tilde-escapes"&gt;Tilde escapes&lt;/h4&gt;

&lt;p&gt;
While convenient to type your message
and send it,
sometimes you want to edit the message
in your
&lt;code&gt;$EDITOR&lt;/code&gt;,
revise the list of recipients,
change the Subject line,
or include a message's contents
for a point-by-point reply.
By entering a message-body line
beginning with a tilde
followed by a command-letter,
&lt;code&gt;mail&lt;/code&gt;
lets you make such modifications.
And if you want to abandon the message,
use the
&lt;kbd&gt;~q&lt;/kbd&gt;
command to save the draft in
&lt;code&gt;~/dead.letter&lt;/code&gt;,
or use the
&lt;kbd&gt;~x&lt;/kbd&gt;
to abandon the message completely.
If you saved a draft,
you can read it back in with
&lt;kbd&gt;~d&lt;/kbd&gt;.
&lt;/p&gt;

&lt;p&gt;
Using
&lt;kbd&gt;~?&lt;/kbd&gt;
will give you the full list of allowed
tilde commands.
&lt;/p&gt;

&lt;p&gt;
Use
&lt;kbd&gt;~t &lt;var&gt;recipient&lt;/var&gt;&lt;/kbd&gt;,
&lt;kbd&gt;~c &lt;var&gt;recipient&lt;/var&gt;&lt;/kbd&gt;,
and
&lt;kbd&gt;~b &lt;var&gt;recipient&lt;/var&gt;&lt;/kbd&gt;
to add recipients
to the
"To:",
"CC:",
and
"BCC:"
recipient-lists respectively.
The
&lt;kbd&gt;~s&lt;/kbd&gt;
lets you edit your "Subject:" header.
Or use
&lt;kbd&gt;~h&lt;/kbd&gt;
to edit each header in turn.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;reply /thesis
~c dean@example.edu
Hey, Bob,
Copying the dean to get her input.&lt;/kbd&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;adding to the CC list&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Most usefully,
the
&lt;kbd&gt;~e&lt;/kbd&gt;
and
&lt;kbd&gt;~v&lt;/kbd&gt;
commands let you edit the current message
in your
&lt;code&gt;$EDITOR&lt;/code&gt;
and your
&lt;code&gt;$VISUAL&lt;/code&gt;
editors respectively.
When you return from your editor,
you can continue to append to the message where you left off.
To display the message as it currently stands,
use the
&lt;kbd&gt;~p&lt;/kbd&gt;
tilde command.
&lt;/p&gt;

&lt;p&gt;
As with other mailers,
sometimes you want to include the content of other messages.
The
&lt;kbd&gt;~f &lt;var&gt;message-list&lt;/var&gt;&lt;/kbd&gt;
&amp;amp;
&lt;kbd id="tilde-m"&gt;~m &lt;var&gt;message-list&lt;/var&gt;&lt;/kbd&gt;
commands let you read one or more messages
from your current mailbox
into the current message.
If you omit the
&lt;kbd&gt;&lt;var&gt;message-list&lt;/var&gt;&lt;/kbd&gt;,
&lt;code&gt;mail&lt;/code&gt;
uses the current message.
If you have set the
&lt;code&gt;indentprefix&lt;/code&gt;
option
(I recommend the internet standard "&amp;gt; "),
the
&lt;kbd&gt;~m&lt;/kbd&gt;
will prefix each line with the corresponding value:
&lt;/p&gt;&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;set indentprefix="&amp;gt; "&lt;/kbd&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;m bob@example.edu&lt;/kbd&gt;
Subject: &lt;kbd&gt;thesis feedback
~m /thesis

Thanks for your helpful feedback.
.&lt;/kbd&gt;
&lt;/pre&gt;
as commonly used for replies.
By default,
only the
&lt;a href="https://blog.thechases.com/posts/using-mail/retain"&gt;retain&lt;/a&gt;ed
headers get included.
If you want
&lt;strong&gt;all&lt;/strong&gt;
the headers,
use the uppercase versions
&lt;kbd&gt;~F&lt;/kbd&gt;
&amp;amp;
&lt;kbd&gt;~M&lt;/kbd&gt;
instead.


&lt;p&gt;
Alternatively, you might want to read
output from an external file
(with
&lt;kbd&gt;~r &lt;var&gt;filename&lt;/var&gt;&lt;/kbd&gt;)
or command
(with
&lt;kbd&gt;~! &lt;var&gt;command&lt;/var&gt;&lt;/kbd&gt;)
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;m &lt;var&gt;jsmith@example.net&lt;/var&gt;&lt;/kbd&gt;
Subject: &lt;kbd&gt;having trouble with my Acme frobniculator interface
I had trouble loading the firmware.
Here's my dmesg output:
~! dmesg

and my config file:
~r /etc/acme-frobniculator

Could you point me in the right direction?

Thanks!&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;including files and command-output in a message&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Finally, sometimes you want
to pass your message
through an external utility
like
&lt;code&gt;rot13(6)&lt;/code&gt;,
&lt;code&gt;b1ff(6)&lt;/code&gt;,
or a spell-check utility.
The
&lt;kbd&gt;~| &lt;var&gt;command&lt;/var&gt;&lt;/kbd&gt;
command lets you filter the message.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;m &lt;var&gt;unclejoe@example.mil&lt;/var&gt;&lt;/kbd&gt;
Subject: &lt;kbd&gt;dirty joke

mud, mud, mud
~| rot13&lt;/kbd&gt;
(continue)
&lt;kbd&gt;~p&lt;/kbd&gt;
-------
Message contains:
To: unclejoe@example.mil
Subject: joke

zhq, zhq, zhq
(continue)
&lt;/pre&gt;
&lt;figcaption&gt;including files and command-output in a message&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
&lt;span class="warning"&gt;Beware: if editing over &lt;code&gt;ssh&lt;/code&gt;&lt;/span&gt;
Both
&lt;code&gt;mail&lt;/code&gt;
&amp;amp;
&lt;code&gt;ssh&lt;/code&gt;
use tilde-escapes at the beginning of the line.
To send a tilde over
&lt;code&gt;ssh&lt;/code&gt;
you may have to hit
&lt;kbd&gt;~&lt;/kbd&gt;
twice to send the tilde to
&lt;code&gt;mail&lt;/code&gt;.
&lt;/p&gt;

&lt;h2 id="alias-files"&gt;Address book&lt;/h2&gt;

&lt;p&gt;
Not having the best memory,
I prefer to let my computer keep track of contacts.
I want to type "john" as the recipient
and let my mail program figure out who I mean.
Do I want to forget any family members
when sending out important family-wide updates?
No way!
The "alias" file contains tuples beginning with the keyword
&lt;code&gt;alias&lt;/code&gt;
followed by the nickname you want to use,
and one or more addresses to expand into.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;cat ~/aliases&lt;/kbd&gt;
alias mom mom@example.net
alias dad dad@example.net
alias john john@example.com
alias joe unclejoe@example.mil
alias family john, joe, mom, dad
alias bob bob@example.edu
alias dean dean@example.edu
alias thesis bob, dean
&lt;/pre&gt;
&lt;figcaption&gt;a sample alias file&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
This lets me compose a message to "john" or "family"
and have
&lt;code&gt;mail&lt;/code&gt;
expand to the correct addresses.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;mail family&lt;/kbd&gt;
Subject: &lt;kbd&gt;Family crisis
Joe ate all the ice cream again!
.
&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;using an alias&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="settings"&gt;Settings&lt;/h2&gt;

&lt;p&gt;
Your
&lt;code&gt;~/.mailrc&lt;/code&gt;
file can contain startup commands &amp;amp; settings
to tweak the behavior of
&lt;code&gt;mail&lt;/code&gt;
so this section covers the ones I find useful.
I've included my
&lt;code&gt;~/.mailrc&lt;/code&gt;
below as an example.
&lt;/p&gt;

&lt;p&gt;
As listed above,
I set my
&lt;code&gt;retain&lt;/code&gt;
to a very small subset of headers
that I actually want to see,
usually
"Subject",
"Date",
and "CC".
&lt;/p&gt;

&lt;dl&gt;

&lt;dt id="setting-crt"&gt;&lt;code&gt;crt&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
By default,
&lt;code&gt;mail&lt;/code&gt;
determines the number of lines based on the display device
and uses this to determine when to pass the output through
&lt;code&gt;$PAGER&lt;/code&gt;
for display.
Setting
&lt;code&gt;crt&lt;/code&gt;
lets you override this.
For instance, on one machine,
I set this 120 lines
and commonly read my 3–4 system messages
by issuing
&lt;kbd&gt;p *&lt;/kbd&gt;
to display all the messages.
Any one message falls below that threshold,
letting me see an individual message without paging.
But I find it handy to read all of them together in
&lt;code&gt;$PAGER&lt;/code&gt;
in one quick skim.
&lt;/dd&gt;

&lt;dt id="setting-folder"&gt;&lt;code&gt;folder&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
By default,
&lt;code&gt;mail&lt;/code&gt;
assumes all your mail-folders
reside in your
&lt;code&gt;$HOME&lt;/code&gt;
directory.
If you use the
&lt;kbd&gt;folders&lt;/kbd&gt;
command to list available mbox files,
it may return lots of non-mbox files.
Instead, if you store your mail in a subdirectory,
you can set that here.
Additionally, regardless of your current working-directory,
you can prefix folder-names with a "+"
to refer to folders in your
&lt;code&gt;folder&lt;/code&gt;
directory
&lt;/dd&gt;

&lt;dt id="setting-header"&gt;&lt;code&gt;header&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
Each time you open a folder
(whether at startup
or using the
&lt;kbd&gt;file&lt;/kbd&gt;
command),
&lt;code&gt;mail&lt;/code&gt;
displays a screen-full of headers.
If you don't want this behavior,
set
&lt;code&gt;noheader&lt;/code&gt;.
&lt;/dd&gt;

&lt;dt id="setting-hold"&gt;&lt;code&gt;hold&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
When quitting and leaving your system mailbox,
by default
&lt;code&gt;mail&lt;/code&gt;
will move every read-message
into your
&lt;a href="https://blog.thechases.com/posts/using-mail/#setting-mbox"&gt;&lt;code&gt;MBOX&lt;/code&gt;&lt;/a&gt;
store
(&lt;code&gt;~/mbox&lt;/code&gt;
by default).
By setting
&lt;code&gt;hold&lt;/code&gt;,
&lt;code&gt;mail&lt;/code&gt;
will leave messages in your system mail-store
until you manually
&lt;a href="https://blog.thechases.com/posts/using-mail/#read-save"&gt;move&lt;/a&gt;
or
&lt;a href="https://blog.thechases.com/posts/using-mail/#read-delete"&gt;delete&lt;/a&gt;
them.
&lt;/dd&gt;

&lt;dt id="setting-indentprefix"&gt;&lt;code&gt;indentprefix&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
When
&lt;a href="https://blog.thechases.com/posts/using-mail/#tilde-m"&gt;replying to a message&lt;/a&gt;,
common convention suggests
that you prefix each quoted line with the string
"&amp;gt; "
The
&lt;code&gt;indentprefix&lt;/code&gt;
setting controls this.
&lt;/dd&gt;

&lt;dt id="setting-mbox"&gt;&lt;code&gt;MBOX&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
This specifies your default "mbox"
local mail store,
defaulting to
&lt;code&gt;~/mbox&lt;/code&gt;
but I prefer to set this to
a file within my
&lt;code&gt;~/mail&lt;/code&gt;
hierarchy
&lt;/dd&gt;

&lt;dt id="setting-record"&gt;&lt;code&gt;record&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
When sending a message,
often times you want to keep that message around.
The
&lt;code&gt;record&lt;/code&gt;
setting lets you specify the folder in which to put a copy.
Unfortunately, there's no folder-name for the current folder,
but you can use something like
&lt;code&gt;+sent&lt;/code&gt;
to keep them all in the same folder.
&lt;/dd&gt;

&lt;dt id="setting-searchheaders"&gt;&lt;code&gt;searchheaders&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
Normally when specifying a message-list
by searching with
&lt;kbd&gt;/&lt;/kbd&gt;,
&lt;code&gt;mail&lt;/code&gt;
will only search the Subject header.
However, if you enable
&lt;code&gt;searchheaders&lt;/code&gt;,
you can prefix your search term with a header-name to search.
This lets you do things like
&lt;kbd&gt;f /To:&lt;var&gt;dean@example.edu&lt;/var&gt;&lt;/kbd&gt;
(to search for all messages sent to the dean)
or
&lt;kbd&gt;f /Date:&lt;var&gt;Jul&lt;/var&gt;&lt;/kbd&gt;
(to search for messages sent in July).
If you omit the header,
it defaults to searching the Subject header as usual.
&lt;/dd&gt;

&lt;/dl&gt;

&lt;p&gt;
As promised,
an excerpt of my
&lt;code&gt;~/.mailrc&lt;/code&gt;
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;$ &lt;/label&gt;&lt;kbd&gt;cat ~/.mailrc&lt;/kbd&gt;
# I prefer to keep my mbox files 
# in a mail/ subdirectory
# to keep from cluttering my
# home directory.
set folder=mail
set MBOX=+inbox
set record=+sent
set hold
set searchheaders
set indentprefix="&amp;gt; "
set crt=120
retain cc
retain date
retain subject
source ~/aliases
&lt;/pre&gt;
&lt;figcaption&gt;My ~/.mailrc file&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="tips"&gt;Tips &amp;amp; tricks&lt;/h2&gt;

&lt;h3 id="tip-delete-undelete"&gt;Deleting all-but XYZ&lt;/h3&gt;

&lt;p&gt;
I receive a number of messages from system-administration processes.
Some come daily,
some weekly,
some monthly.
Some have security advisories,
while others give general system information.
So I usually want to delete the non-security-related ones.
However, the subject lines make this more challenging.
The trick?
Delete all of them,
then undelete the security-related ones:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;d &lt;var&gt;root@myhost.example.com&lt;/var&gt;&lt;/kbd&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;u &lt;var&gt;/insecurity&lt;/var&gt;&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;deleting all system messages, then undeleting security-related ones&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
I can then review these more-relevant messages
without the noise of the other messages.
&lt;/p&gt;

&lt;h3 id="tip-read-fast"&gt;Quickly processing messages&lt;/h3&gt;

&lt;p&gt;
On some machines,
I want to read all the mail in my
&lt;code&gt;$PAGER&lt;/code&gt;
pressing
&lt;kbd&gt;space&lt;/kbd&gt;
to show a screen-full at a time
and then delete all the messages unless
something surprising shows up.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;p *&lt;/kbd&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;d *&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;displaying all messages, then deleting them&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Other times,
I want to read each message
and process as I go.
After displaying a message,
I can hit enter
(with no command)
to display the next message automatically.
Alternatively, the
&lt;kbd&gt;dp&lt;/kbd&gt;
command will delete the current message
and print the next message in one go.
It may only save one keypress,
but I find it useful
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;1&lt;/kbd&gt;
Message 1:
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;
Message 2:
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;label class="user"&gt;&amp;amp; &lt;/label&gt;&lt;kbd&gt;dp&lt;/kbd&gt;
&lt;/pre&gt;
&lt;figcaption&gt;displaying sequential messages; deleting and displaying in one command&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;!-- notes:

"reply" = reply all
"Reply" = reply to sender only

--&gt;</description><category>ed</category><category>mail</category><guid>https://blog.thechases.com/posts/using-mail/</guid><pubDate>Wed, 14 Dec 2022 17:05:34 GMT</pubDate></item><item><title>Aggressive pf(4) configuration for SSH protection</title><link>https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
If some unknown person
came up to your house
and started trying to open each window,
jiggling each door handle,
and punching in random codes on your garage-door opener,
you could safely assume that they sought trouble
and that you should stop them.
Similarly,
if some unknown computer on the internet
started probing at ports on your server
where you offer no services,
it should set off alarm-bells
and you should prevent this miscreant
from interacting with your server.
Especially
&lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt;
access.
&lt;em&gt;You&lt;/em&gt;
know what services your machine provides.
If anybody attempts to connect
to some other service on your machine,
in all likelihood they don't have your best interests in mind.
&lt;/p&gt;

&lt;p&gt;
In this post,
we configure
&lt;code&gt;pf&lt;/code&gt;
on
&lt;a href="http://openbsd.org"&gt;OpenBSD&lt;/a&gt;
to catch inappropriate connection-attempts
and block the offender.
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;ol&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#assumptions"&gt;Assumptions&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#setup"&gt;Setup&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#goals"&gt;Goals&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#sshd-port"&gt;Move the SSH port&lt;/a&gt;
  &lt;ol&gt;
   &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#sshd-config"&gt;Change the listening port&lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#ssh-config"&gt;Change the default connecting port&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
 &lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#minefield"&gt;Creating a minefield&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#ratelimit"&gt;Rate-limiting connections&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#country"&gt;Limiting by country&lt;/a&gt;
  &lt;ol&gt;
   &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#geoip-folders"&gt;Download GeoIP data&lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#automating-geoip"&gt;Automating GeoIP downloading&lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#create-geoip-user"&gt;Create a GeoIP-specific user&lt;/a&gt;
    &lt;ol&gt;
     &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#modify-geoip-access"&gt;Modify permissions on GeoIP data directory&lt;/a&gt;&lt;/li&gt;
     &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#geoip-doas"&gt;Allow access to restart pf&lt;/a&gt;&lt;/li&gt;
     &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#geoip-script"&gt;Create a script to download &amp;amp; install GeoIP info&lt;/a&gt;&lt;/li&gt;
     &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#geoip-cron"&gt;Scheduling the download&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
   &lt;/li&gt;
  &lt;/ol&gt;
 &lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#final"&gt;Final file&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="assumptions"&gt;Assumptions&lt;/h2&gt;

&lt;p&gt;
This post assumes a few things about you:
&lt;/p&gt;&lt;dl&gt;
 &lt;dt&gt;OS installed that can run &lt;code&gt;pf&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;
  While I wrote this article using
  &lt;code&gt;pf&lt;/code&gt;
  on
  &lt;a href="https://openbsd.org"&gt;OpenBSD&lt;/a&gt;,
  many of the aspects should translate to
  &lt;a href="https://freebsd.org"&gt;FreeBSD&lt;/a&gt;
  even though FreeBSD's version of
  &lt;code&gt;pf&lt;/code&gt;
  represents an older snapshot from
  when their codebases diverged.
  Particularly,
  &lt;ul&gt;
   &lt;li&gt;
    I think the abstract
    &lt;code&gt;egress&lt;/code&gt;
    interface-group may not exist
    so you'd have to specify
    the interface directly
    (either inline
    or as a macro)
   &lt;/li&gt;
   &lt;li&gt;
    Where I use
    &lt;code&gt;doas&lt;/code&gt;,
    you either have to install
    &lt;code&gt;sudo&lt;/code&gt;
    or
    &lt;code&gt;doas&lt;/code&gt;
    or else you must
    &lt;code&gt;su -&lt;/code&gt;
    to become root
    for many of these commands.
   &lt;/li&gt;
   &lt;li&gt;
    in the
    &lt;code&gt;_geoip&lt;/code&gt;
    script,
    you would need to replace
    the use of
    &lt;code&gt;ftp&lt;/code&gt;
    with either
    &lt;code&gt;fetch&lt;/code&gt;,
    &lt;code&gt;wget&lt;/code&gt;,
    or
    &lt;code&gt;curl&lt;/code&gt;

   &lt;/li&gt;
  &lt;/ul&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Administrative rights&lt;/dt&gt;
 &lt;dd&gt;
  This should go without saying,
  but if you don't administer the machine,
  you can't modify the firewall configuration.
 &lt;/dd&gt;

 &lt;dt&gt;Basic
 &lt;abbr title="Command Line Interface"&gt;CLI&lt;/abbr&gt;&lt;/dt&gt;
 &lt;dd&gt;
  In this case,
  we have minimal work to do at the command-line
  but it helps to know your way around.
  OpenBSD has a template
  &lt;code&gt;/etc/examples/pf.conf&lt;/code&gt;
  you might want to copy
  as a starting-point;
  you might find it easier to
  &lt;code&gt;cd&lt;/code&gt;
  into
  &lt;code&gt;/etc&lt;/code&gt;
  directory;
  or you might use
  &lt;code&gt;tmux&lt;/code&gt;
  and
  &lt;code&gt;ssh&lt;/code&gt;
  to access the server.
  &lt;span class="warning"&gt;Note&lt;/span&gt;
  that editing
  &lt;code&gt;/etc/pf.conf&lt;/code&gt;
  can leave your server inaccessible
  (like cutting off the tree-branch
  while sitting on it),
  so you'll want to make sure
  that you have console access
  — locally,
  via a serial
  connection/&lt;abbr title="Keyboard/Video/Mouse"&gt;KVM&lt;/abbr&gt;,
  or
  &lt;abbr title="Virtual Network Computing"&gt;VNC&lt;/abbr&gt;
  connection.
 &lt;/dd&gt;

 &lt;dt&gt;Basic networking knowledge&lt;/dt&gt;
 &lt;dd&gt;
  The basic networking ideas of
  &lt;abbr title="User Datagram Protocol"&gt;UDP&lt;/abbr&gt;,
  &lt;abbr title="Transmission Control Protocol"&gt;TCP&lt;/abbr&gt;,
  ports,
  services,
  etc.
  If you need to get up to speed here
  you might want a resource like
  &lt;a href="https://twitter.com/mwlauthor"&gt;Michael
  W. Lucas&lt;/a&gt;'s
  &lt;a href="https://mwl.io/nonfiction/networking#n4sa"&gt;Networking
  for System Administrators&lt;/a&gt;,
  O'Reilly's
  &lt;a href="http://shop.oreilly.com/product/9780596002978.do"&gt;TCP/IP
  Network Administration&lt;/a&gt;,
  or some other such book.
 &lt;/dd&gt;

 &lt;dt&gt;Edit text-files&lt;/dt&gt;
 &lt;dd&gt;
  This focuses primarily on modifying your
  &lt;code&gt;/etc/pf.conf&lt;/code&gt;
  which you can edit with your favorite
  &lt;code&gt;$EDITOR&lt;/code&gt;,
  whether
  &lt;code&gt;nano&lt;/code&gt;,
  &lt;code&gt;emacs&lt;/code&gt;/&lt;code&gt;mg&lt;/code&gt;,
  &lt;code&gt;vi&lt;/code&gt;/&lt;code&gt;vim&lt;/code&gt;/&lt;code&gt;neovim&lt;/code&gt;,
  &lt;a href="https://blog.thechases.com/posts/categories/ed/"&gt;&lt;code&gt;ed&lt;/code&gt;&lt;/a&gt;,
  &lt;a href="https://xkcd.com/378/" title="XKCD about text-editors"&gt;&lt;code&gt;cat&lt;/code&gt;,
  a magnetized needle
  and a steady hand,
  or butterflies&lt;/a&gt;.
  But this guide assumes
  that you can edit text files
  and save them.
 &lt;/dd&gt;

&lt;/dl&gt;


&lt;h2 id="setup"&gt;Setup&lt;/h2&gt;

&lt;p&gt;
For purposes here,
I'll use OpenBSD's
&lt;code&gt;doas&lt;/code&gt;
command to elevate privileges
but you can use
&lt;code&gt;sudo&lt;/code&gt;
if you have it installed on FreeBSD
or execute these commands
under a
&lt;code&gt;su -&lt;/code&gt;
to
&lt;code&gt;root&lt;/code&gt;
if you like to live dangerously.
This assumes that
&lt;code&gt;doas&lt;/code&gt;
(or
&lt;code&gt;sudo&lt;/code&gt;)
has a configuration that lets you run the commands in question.
&lt;/p&gt;

&lt;p&gt;
This also assumes starting with an empty
(or mostly-empty if you did
&lt;code&gt;doas cp /etc/examples/pf.conf /etc/&lt;/code&gt;
)
&lt;code&gt;pf.conf&lt;/code&gt;
file.
If you already have an existing configuration,
you'll need to work out how these changes integrate
with your existing setup.
&lt;/p&gt;

&lt;h2 id="goals"&gt;Goals&lt;/h2&gt;

&lt;p&gt;
This post covers several tricks
to protect the actual SSH port.
&lt;/p&gt;&lt;ol&gt;
 &lt;li&gt;change port on which SSH listens&lt;/li&gt;
 &lt;li&gt;flag any IP addresses that touch our server's minefield
  and prevent them from accessing SSH&lt;/li&gt;
 &lt;li&gt;if anybody actually finds the SSH port and hammers on it,
  ban them too&lt;/li&gt;
 &lt;li&gt;limit the country/countries from which we can SSH&lt;/li&gt;
&lt;/ol&gt;


&lt;h2 id="sshd-port"&gt;Change the
 &lt;code&gt;sshd&lt;/code&gt;
 port number&lt;/h2&gt;

&lt;p&gt;
For purposes of this post,
I configure
&lt;code&gt;sshd&lt;/code&gt;
to listen on port
&lt;var&gt;2345&lt;/var&gt;
but you should choose
a different one that suits you.
This doesn't give much
in the way of security,
but it reduces some of the noise
in your log-files
and makes the
&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#minefield"&gt;minefield&lt;/a&gt;
work by making it unknown
and surrounded by trip-ports.
&lt;/p&gt;

 &lt;h3 id="sshd-config"&gt;Change the listening port&lt;/h3&gt;

&lt;p&gt;
Edit your
&lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;
file and specify the
&lt;code&gt;Port&lt;/code&gt;
by adding/editing the line to read
&lt;figure&gt;
&lt;pre&gt;
Port 2345
&lt;/pre&gt;
&lt;figcaption&gt;/etc/ssh/sshd_config&lt;/figcaption&gt;
&lt;/figure&gt;
Save the file
and restart
&lt;code&gt;sshd&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;doas rcctl restart sshd
&lt;/pre&gt;
&lt;/figure&gt;

&lt;span class="warning"&gt;Note&lt;/span&gt;:
If you make this configuration change
while SSHed into the server,
you might cut off your access.
As mentioned,
make sure that you have console,
serial, or VNC access
in case you need to recover from making an error here.
&lt;/p&gt;

 &lt;h3 id="ssh-config"&gt;Change the default connecting port&lt;/h3&gt;

&lt;p&gt;
On my local machine,
I find it painful to remember
how to specify the port to various
&lt;code&gt;ssh&lt;/code&gt;
commands.
Some take
&lt;code&gt;-p &lt;var&gt;$PORTNUM&lt;/var&gt;&lt;/code&gt;
or
&lt;code&gt;-P &lt;var&gt;$PORTNUM&lt;/var&gt;&lt;/code&gt;,
while others take
&lt;code&gt;-o Port=&lt;var&gt;$PORTNUM&lt;/var&gt;&lt;/code&gt;
and yet others expect it as an environment variable.
To simplify this,
edit (or create) your
&lt;code&gt;~/.ssh/config&lt;/code&gt;
to specify the port-number there
&lt;figure&gt;
&lt;pre&gt;
host &lt;var&gt;example&lt;/var&gt;
    HostName &lt;var&gt;example.com&lt;/var&gt;
    Port &lt;var&gt;2345&lt;/var&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.ssh/config&lt;/figcaption&gt;
&lt;/figure&gt;
Now
&lt;code&gt;ssh&lt;/code&gt;,
&lt;code&gt;scp&lt;/code&gt;,
&lt;code&gt;sftp&lt;/code&gt;,
&lt;code&gt;rsync&lt;/code&gt;,
etc,
should all default to the right port-number
without you needing to specify it explicitly.
&lt;/p&gt;

&lt;h2 id="minefield"&gt;Creating a minefield&lt;/h2&gt;

&lt;p&gt;
Here we configure a minefield of ports
such that if anybody tries to connect
on any of these fake ports,
they get added to a table of troublemakers.
For the minefield,
I default to
&lt;var&gt;1024:9999&lt;/var&gt;
but if you want a broader range,
check the output of
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;sysctl net.inet.ip.port{first,last}
&lt;/pre&gt;
&lt;/figure&gt;
which gives a larger swath.
If you extend it below 1024,
make sure you don't interfere
with other services that you might actually want to provide.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
##########
# macros #
##########
minefield="&lt;var&gt;1024:9999&lt;/var&gt;"
ssh_alternate_port=&lt;var&gt;2345&lt;/var&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
##########
# tables #
##########
table &amp;lt;troublemakers&amp;gt; persist
&lt;label class="visual"&gt;⋮&lt;/label&gt;
#########
# rules #
#########
block quick proto tcp from &amp;lt;troublemakers&amp;gt; \
  to (egress) port $ssh_alternate_port

## these are just randomly probing

# port 22 is a dead-giveaway
# because we know we moved our SSH server elsewhere
# Also, we have no telnet or SMB
# so anyone poking there
# is up to no good
pass in on egress proto tcp \
  from any \
  to (egress) port { \
    telnet, \
    ssh, \
    netbios-ns, \
    netbios-ssn, \
    microsoft-ds \
  } \
  synproxy state \
  tag trouble

# tag stuff in the range $minefield as trouble
pass in on egress proto tcp from any to (egress) port $minefield \
  synproxy state \
  tag trouble

# unless it's to our hole-in-one
pass in proto tcp from any to (egress) port $ssh_alternate_port tag good

# if we're still trouble, add to the troublemakers
pass proto tcp from any to (egress) port $ssh_alternate_port \
  tagged trouble \
  synproxy state \
  (max-src-conn 1, max-src-conn-rate 1/10, \
  overload &amp;lt;troublemakers&amp;gt; flush global \
  )
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;/etc/pf.conf&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
We only specify TCP
because malicious actors
could spoof UDP source-addresses,
blocking innocent folks.
&lt;/p&gt;

&lt;p&gt;
Feel free to add other services
to the list of services you don't provide
(I have telnet, SSH, and
&lt;abbr title="Server Message Block"&gt;SMB&lt;/abbr&gt;
in my list)
if you notice strangers knocking elsewhere.
&lt;/p&gt;

&lt;p&gt;
We tag packets as
"&lt;code&gt;trouble&lt;/code&gt;",
and then tag the one port we know as
"&lt;code&gt;good&lt;/code&gt;".
If a packet still has the
"&lt;code&gt;trouble&lt;/code&gt;"
tag,
we use some trickery
(involving
&lt;code&gt;max-src-conn&lt;/code&gt;
&amp;amp;
&lt;code&gt;max-src-conn-rate&lt;/code&gt;
to limit them to one connection
and to one attempt every 10 seconds)
to add them to the
&lt;code&gt;troublemakers&lt;/code&gt;
table, banning all future SSH access for them.
The
&lt;code&gt;flush global&lt;/code&gt;
keyword kills any other existing connections
this offending visitor currently has.
&lt;/p&gt;

&lt;p&gt;
We also need the
&lt;code&gt;synproxy state&lt;/code&gt;
which allows
&lt;code&gt;pf&lt;/code&gt;
to handle the connection to ports
that don't actually have services backing them.
(Aside: I
&lt;em&gt;think&lt;/em&gt;
that all ports that don't have a real service behind them
need the
&lt;code&gt;synproxy state&lt;/code&gt;
but it might turn out that only the
&lt;code&gt;overload&lt;/code&gt;
item needs it.)
&lt;/p&gt;

&lt;p&gt;
With all that in place,
any attempt to connect to a port in our minefield
(or select services we know we don't run)
results in the remote IP address
getting added to the
&lt;code&gt;troublemakers&lt;/code&gt;
table and subsequently blocked from our SSH port.
&lt;/p&gt;

&lt;h2 id="ratelimit"&gt;Rate-limiting connections&lt;/h2&gt;

&lt;p&gt;
If an attacker does manage to guess
our actual port
&lt;var&gt;2345&lt;/var&gt;
on the first try,
they will likely try to hammer on it.
So we want to rate-limit them:
&lt;figure&gt;
&lt;pre&gt;
##########
# Tables #
##########
table &amp;lt;bruteforce&amp;gt; persist
&lt;label class="visual"&gt;⋮&lt;/label&gt;
#########
# Rules #
#########
block quick proto tcp \
  from &amp;lt;bruteforce&amp;gt; \
  to (egress) port $ssh_alternate_port

# these made it through to the SSH port but abusing it
pass proto tcp from any to (egress) \
  port {$ssh_alternate_port} \
  flags S/SA keep state \
  (max-src-conn 5, max-src-conn-rate 5/5, \
  overload &amp;lt;bruteforce&amp;gt; flush global \
  )
&lt;/pre&gt;
&lt;figcaption&gt;/etc/pf.conf&lt;/figcaption&gt;
&lt;/figure&gt;
This limits to 5 concurrent connections
(&lt;code&gt;max-src-conn 5&lt;/code&gt;)
and 5 connection-attempts during a 10-second window
(&lt;code&gt;max-src-conn-rate 5/10&lt;/code&gt;).
&lt;/p&gt;

&lt;h2 id="country"&gt;Limiting by country&lt;/h2&gt;

&lt;p&gt;
To begin with,
I know that I will
almost certainly never
connect to SSH
from outside the United States.
&lt;a href="https://www.ipdeny.com/ipblocks/"&gt;IPDeny.com&lt;/a&gt;
provides regularly-updated lists
of IPv4 &amp;amp; IPv6 ranges for each country
that I can use as a rough
determination of country-of-origin.
Yes, this can get thrown off by
&lt;abbr title="Virtual Private Networks"&gt;VPNs&lt;/abbr&gt;
but it cuts off a surprising number of attackers.

 &lt;/p&gt;&lt;h3 id="geoip-folders"&gt;Download GeoIP data&lt;/h3&gt;

&lt;p&gt;
First, create the folder where
&lt;code&gt;pf&lt;/code&gt;
will find the GeoIP tables.
Inside that, create a
&lt;code&gt;tmp/&lt;/code&gt;
directory so we can download there,
compare the results with what we already have,
and move the new data atop the old data atomically:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;DEST="/usr/local/share/geoip"
&lt;label class="user"&gt;user$ &lt;/label&gt;doas mkdir -p "$DEST/tmp"
&lt;label class="user"&gt;user$ &lt;/label&gt;doas chmod 755 "$DEST"
&lt;label class="user"&gt;user$ &lt;/label&gt;doas chmod 750 "$DEST/tmp"
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
For now, we will manually download
the IPv4 and IPv6 GeoIP blocks:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;DEST="/usr/local/share/geoip"
&lt;label class="user"&gt;user$ &lt;/label&gt;V4URL="https://www.ipdeny.com/ipblocks/data/aggregated/us-aggregated.zone"
&lt;label class="user"&gt;user$ &lt;/label&gt;V6URL="https://www.ipdeny.com/ipv6/ipaddresses/aggregated/us-aggregated.zone"
&lt;label class="user"&gt;user$ &lt;/label&gt;V4DEST="$DEST/ipv4_us-aggregated.zone"
&lt;label class="user"&gt;user$ &lt;/label&gt;V6DEST="$DEST/ipv6_us-aggregated.zone"
&lt;label class="user"&gt;user$ &lt;/label&gt;ftp -o "$V4DEST" "$V4URL"
&lt;label class="user"&gt;user$ &lt;/label&gt;ftp -o "$V6DEST" "$V6URL"
&lt;/pre&gt;
&lt;/figure&gt;
Now with these files,
we can point
&lt;code&gt;pf&lt;/code&gt;
at them and prohibit access
from non-US visitors:
&lt;figure&gt;
&lt;pre&gt;
##########
# Tables #
##########
table &amp;lt;domesticv4&amp;gt; \
  persist file "/usr/local/share/geoip/ipv4_us-aggregated.zone"
table &amp;lt;domesticv6&amp;gt; \
  persist file "/usr/local/share/geoip/ipv6_us-aggregated.zone"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
#########
# Rules #
#########
# non-domestic IPv4/IPv6 connections simply not allowed to touch SSH
block quick inet proto tcp \
  from ! &amp;lt;domesticv4&amp;gt; \
  to (egress) port $ssh_alternate_port
block quick inet6 proto tcp \
  from ! &amp;lt;domesticv6&amp;gt; \
  to (egress) port $ssh_alternate_port
&lt;/pre&gt;
&lt;figcaption&gt;/etc/pf.conf&lt;/figcaption&gt;
&lt;/figure&gt;

With this in place,
any visitors from a non-US IP address
simply cannot access the alternate SSH port.
&lt;/p&gt;

 &lt;h3 id="automating-geoip"&gt;Automating GeoIP downloading&lt;/h3&gt;

&lt;p&gt;
Occasionally the GeoIP ranges change
so you may want to download them automatically
and have
&lt;code&gt;pf&lt;/code&gt;
pick them up automatically.
For this we'll
&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#create-geoip-user"&gt;create a stand-alone
&lt;code&gt;_geoip&lt;/code&gt;
user&lt;/a&gt;,
&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#modify-geoip-access"&gt;modify the permissions
of the GeoIP data directory&lt;/a&gt;,
set up
&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#geoip-script"&gt;a script to automatically download GeoIP data&lt;/a&gt;,
compare it to existing data,
and (if it changed) replace the old data
and restart
&lt;code&gt;pf&lt;/code&gt;.
&lt;/p&gt;

 &lt;h4 id="create-geoip-user"&gt;Create a
  &lt;code&gt;_geoip&lt;/code&gt;
  user&lt;/h4&gt;

&lt;p&gt;
Here we use the
&lt;code&gt;adduser&lt;/code&gt;
command to create a new user.
I put this user in the "daemon" login-class,
specify an empty password,
and then disable password logins:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;doas adduser &lt;var&gt;_geoip&lt;/var&gt;
Use option ``-silent'' if you don't want to see all warnings and questions.

Reading /etc/shells
Check /etc/master.passwd
Check /etc/group

Ok, let's go.
Don't worry about mistakes. There will be a chance later to correct any input.
Enter username []: &lt;var&gt;_geoip&lt;/var&gt;
Enter full name []: &lt;var&gt;GeoIP downloader&lt;/var&gt;
Enter shell csh git-shell ksh nologin sh [ksh]:
Uid [1002]:
Login group _geoip [_geoip]:
Login group is ``_geoip''. Invite _geoip into other groups: guest &lt;mark&gt;no &lt;/mark&gt;
[no]:
Login class authpf bgpd daemon default pbuild staff unbound
[default]: &lt;mark&gt;daemon&lt;/mark&gt;
Enter password []:
Disable password logins for the user? (y/n) [n]: &lt;mark&gt;y&lt;/mark&gt;

Name:  _geoip
Password:    ****
Fullname:    GeoIP downloader
Uid:   1002
Gid:   1002 (_geoip)
Groups:      _geoip
Login Class: daemon
HOME:  /home/_geoip
Shell:       /bin/ksh
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
You could specify the shell as
&lt;code&gt;nologin&lt;/code&gt;
but then have to do some work-arounds
to get a shell to
&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#geoip-script"&gt;create  the script&lt;/a&gt;
and set up the
&lt;code&gt;cron&lt;/code&gt;
job.
After configuring the setup below,
you can go back and use
&lt;code&gt;chsh&lt;/code&gt;
to change the shell to
&lt;code&gt;nologin&lt;/code&gt;
for added lockdown.
&lt;/p&gt;

  &lt;h4 id="modify-geoip-access"&gt;Modify permissions on GeoIP data directory&lt;/h4&gt;

&lt;p&gt;
Now we need to make sure that the
&lt;code&gt;_geoip&lt;/code&gt;
user can write to the directories
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;doas chown -R _geoip:_geoip "$DEST"
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

  &lt;h4 id="geoip-doas"&gt;Allow the
   &lt;code&gt;_geoip&lt;/code&gt;
   user access to restart
   &lt;code&gt;pf&lt;/code&gt;
   &lt;/h4&gt;

&lt;p&gt;
In order for the
&lt;code&gt;_geoip&lt;/code&gt;
user to instruct
&lt;code&gt;pf&lt;/code&gt;
to reload its configuration
and pick up the new GeoIP files,
we need to add a line in our
&lt;code&gt;/etc/doas.conf&lt;/code&gt;
file:

&lt;figure&gt;
&lt;pre&gt;
permit nopass _geoip cmd /sbin/pfctl args -f /etc/pf.conf
&lt;/pre&gt;
&lt;figcaption&gt;/etc/doas.conf&lt;/figcaption&gt;
&lt;/figure&gt;

This allows the
&lt;code&gt;_geoip&lt;/code&gt;
user to run only
&lt;code&gt;pfctl&lt;/code&gt;
as specified by full path,
and only with the arguments
&lt;code&gt;-f /etc/pf.conf&lt;/code&gt;
to minimize things.
If you want to lock it down even further
you can limit the
&lt;code&gt;_geoip&lt;/code&gt;
user to only reload those two tables
with something like this
(untested):
&lt;figure&gt;
&lt;pre&gt;
permit nopass _geoip \
  cmd /sbin/pfctl \
  args -t domesticv4 -T replace -f "/usr/local/share/geoip/ipv4_us-aggregated.zone"
permit nopass _geoip \
  cmd /sbin/pfctl \
  args -t domesticv6 -T replace -f "/usr/local/share/geoip/ipv6_us-aggregated.zone"
&lt;/pre&gt;
&lt;figcaption&gt;/etc/doas.conf&lt;/figcaption&gt;
&lt;/figure&gt;
If you choose to go this route,
make sure that you
&lt;a href="https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/#script"&gt;update the script below&lt;/a&gt;
to use
&lt;figure&gt;
&lt;pre&gt;
/sbin/pfctl -t domesticv4 -T replace -f "/usr/local/share/geoip/ipv4_us-aggregated.zone"
/sbin/pfctl -t domesticv6 -T replace -f "/usr/local/share/geoip/ipv6_us-aggregated.zone"
&lt;/pre&gt;
&lt;figcaption&gt;~_geoip/bin/update_zone.sh&lt;/figcaption&gt;
&lt;/figure&gt;
instead.
&lt;/p&gt;

  &lt;h4 id="geoip-script"&gt;Create a script to download &amp;amp; install GeoIP info&lt;/h4&gt;

&lt;p&gt;
I switch to the
&lt;code&gt;_geoip&lt;/code&gt;
user and create the script to use
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;doas su - _geoip
&lt;label class="user"&gt;_geoip$ &lt;/label&gt;mkdir -p ~/bin
&lt;label class="user"&gt;_geoip$ &lt;/label&gt;touch ~/bin/update_zone.sh
&lt;label class="user"&gt;_geoip$ &lt;/label&gt;chmod +x ~/bin/update_zone.sh
&lt;/pre&gt;
&lt;/figure&gt;
I then used my
&lt;code&gt;$EDITOR&lt;/code&gt;
to populate that
&lt;code&gt;~/bin/update_zone.sh&lt;/code&gt;
file with the following script:
&lt;figure&gt;
&lt;pre&gt;
#!/bin/sh
FTPOPTS="-MV"
DEST="/usr/local/share/geoip"
V4URL="https://www.ipdeny.com/ipblocks/data/aggregated/us-aggregated.zone"
V6URL="https://www.ipdeny.com/ipv6/ipaddresses/aggregated/us-aggregated.zone"
V4TMP="$DEST/tmp/ipv4_us-aggregated.zone"
V4DEST="$DEST/ipv4_us-aggregated.zone"
V6TMP="$DEST/tmp/ipv6_us-aggregated.zone"
V6DEST="$DEST/ipv6_us-aggregated.zone"

CHANGED=false
# fetch them to the temp location
# and if successful and they differ,
# replace the original
ftp $FTPOPTS -o $V4TMP "$V4URL" &amp;amp;&amp;amp; ! cmp -s "$V4TMP" "$V4DEST" &amp;amp;&amp;amp; mv "$V4TMP" "$V4DEST" &amp;amp;&amp;amp; CHANGED=true
ftp $FTPOPTS -o $V6TMP "$V6URL" &amp;amp;&amp;amp; ! cmp -s "$V6TMP" "$V6DEST" &amp;amp;&amp;amp; mv "$V6TMP" "$V6DEST" &amp;amp;&amp;amp; CHANGED=true

# if it changed, reload pf.conf
$CHANGED &amp;amp;&amp;amp; (echo "Loading new domestic zones for SSH blocking" ; doas /sbin/pfctl -f /etc/pf.conf )
&lt;/pre&gt;
&lt;figcaption&gt;~_geoip/bin/update_zone.sh" id="script&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

  &lt;h4 id="geoip-cron"&gt;Scheduling the download&lt;/h4&gt;
&lt;p&gt;
On OpenBSD 6.7 and later
&lt;code&gt;cron&lt;/code&gt;
allows the use of the
&lt;code&gt;~&lt;/code&gt;
operator to indicate a randomly chosen time
to help distribute the running of tasks
without everything happening at one time.
So after issuing
&lt;code&gt;su - _geoip&lt;/code&gt;
to become the
&lt;code&gt;_geoip&lt;/code&gt;
user,
I used
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;_geoip$ &lt;/label&gt;crontab -e
&lt;/pre&gt;
&lt;/figure&gt;
to edit the
&lt;code&gt;crontab&lt;/code&gt;
and add an entry to run the script:
&lt;figure&gt;
&lt;pre&gt;
# min, hr, day-of-mon, mon, day-of-wk
&lt;mark&gt;~ 1~3 * * 2 $HOME/bin/install_us_zone.sh&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;crontab -e&lt;/figcaption&gt;
&lt;/figure&gt;
This picks some random time between
1:00am and 3:59am on Tuesday ("2")
to run our script.
If you use another
&lt;code&gt;cron&lt;/code&gt;
or an older version on OpenBSD,
choose an appropriate time.
The
&lt;a href="https://www.ipdeny.com/usagelimits.php"&gt;IPDeny
terms-and-conditions&lt;/a&gt;
offer very liberal usage limits
so you
&lt;em&gt;could&lt;/em&gt;
download them more frequently.
However, I find that the data doesn't change too frequently,
so weekly works for me.
&lt;/p&gt;

&lt;h2 id="final"&gt;Final file&lt;/h2&gt;
&lt;p&gt;
Putting it all together
my final file looks something like this
(including the initial entries from
&lt;code&gt;/etc/examples/pf.conf&lt;/code&gt;)
&lt;figure&gt;
&lt;pre&gt;
##########
# Macros #
##########
# see also `sysctl net.inet.ip.port{first,last}`
minefield="1024:9999"
ssh_alternate_port=2345
&lt;label class="visual"&gt;⋮&lt;/label&gt;

##########
# Tables #
##########
table &amp;lt;bruteforce&amp;gt; persist
table &amp;lt;troublemakers&amp;gt; persist
table &amp;lt;domesticv4&amp;gt; persist \
  file "/usr/local/share/geoip/ipv4_us-aggregated.zone"
table &amp;lt;domesticv6&amp;gt; persist \
  file "/usr/local/share/geoip/ipv6_us-aggregated.zone"
&lt;label class="visual"&gt;⋮&lt;/label&gt;

###########
# Options #
###########
set skip on lo
&lt;label class="visual"&gt;⋮&lt;/label&gt;

#########
# Rules #
#########
block return    # block stateless traffic
pass      # establish keep-state

# block brute-forcers
block quick proto tcp from &amp;lt;bruteforce&amp;gt; \
  to (egress) port $ssh_alternate_port
block quick proto tcp from &amp;lt;troublemakers&amp;gt; \
  to (egress) port $ssh_alternate_port

# non-domestic v4/v6 connections simply not allowed to touch SSH
block quick inet proto tcp \
  from ! &amp;lt;domesticv4&amp;gt; \
  to (egress) port $ssh_alternate_port
block quick inet6 proto tcp \
  from ! &amp;lt;domesticv6&amp;gt; \
  to (egress) port $ssh_alternate_port

# Port build user does not need network
block return out log proto {tcp udp} user _pbuild

# these made it through to the SSH port but abusing it
pass proto tcp from any to (egress) \
  port {$ssh_alternate_port} \
  flags S/SA keep state \
  (max-src-conn 5, max-src-conn-rate 5/5, \
  overload &amp;lt;bruteforce&amp;gt; flush global \
  )

## these are just randomly probing

# port 22 is a dead-giveaway
# because we know we moved our SSH server elsewhere
# Also, we have no telnet or SMB
# so anyone poking there
# is up to no good
pass in on egress proto tcp \
  from any \
  to (egress) port { \
    telnet, \
    ssh, \
    netbios-ns, \
    netbios-ssn, \
    microsoft-ds \
  } \
  synproxy state \
  tag trouble

# tag stuff in the range $minefield as trouble
pass in on egress proto tcp from any to (egress) port $minefield \
  synproxy state \
  tag trouble

# unless it's to our hole-in-one
pass in proto tcp from any to (egress) port $ssh_alternate_port tag good

# if we're still trouble, add to the troublemakers
pass proto tcp from any to (egress) port $ssh_alternate_port \
  tagged trouble \
  synproxy state \
  (max-src-conn 1, max-src-conn-rate 1/10, \
  overload &amp;lt;troublemakers&amp;gt; flush global \
  )

pass proto tcp from any to any port {http https} \
  keep state (max-src-conn 10, max-src-conn-rate 10/3)

&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;/etc/pf.conf&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;
With all this in place,
the SSH port now has protection from
non-domestic IP addresses,
folks probing around randomly
to ports they have no business connecting to,
and folks who actually find the port
but bang against it too hard.
From here, one might also implement
&lt;a href="https://www.fail2ban.org"&gt;&lt;code&gt;fail2ban&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://www.freebsd.org/doc/handbook/firewalls-blacklistd.html"&gt;&lt;code&gt;blacklistd&lt;/code&gt;&lt;/a&gt;,
or
&lt;a href="https://www.sshguard.net/"&gt;&lt;code&gt;sshguard&lt;/code&gt;&lt;/a&gt;
to monitor your
&lt;code&gt;sshd&lt;/code&gt;
logs, watching for failed logins
and then banning based on repeated login failures.
&lt;/p&gt;</description><category>ed</category><guid>https://blog.thechases.com/posts/bsd/aggressive-pf-config-for-ssh-protection/</guid><pubDate>Sun, 29 Mar 2020 18:14:54 GMT</pubDate></item><item><title>How I use remind(1)</title><link>https://blog.thechases.com/posts/remind/</link><dc:creator>Tim Chase</dc:creator><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;

&lt;p&gt;
The
&lt;code&gt;&lt;a href="https://dianne.skoll.ca/projects/remind/"&gt;remind&lt;/a&gt;&lt;/code&gt;
calendar utility offers an amazing degree of power
for doing calendar-related scheduling unmatched by
any other calendar program I've used.
If you haven't encountered it before,
this
&lt;a href="https://dianne.skoll.ca/projects/remind/download/remind-oclug.pdf"&gt;presentation (PDF)&lt;/a&gt;
gives a good overview of some of the features,
as do
&lt;a href="https://www.linuxjournal.com/article/3529"&gt;Linux Journal&lt;/a&gt;
and
&lt;a href="http://www.43folders.com/2005/02/24/guest-mike-harris-looks-at-remind"&gt;43 Folders&lt;/a&gt;.
In this post,
I'll walk through
how I use both the basic
and advanced features.
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;p&gt;
This post makes a couple assumptions
about you as the reader:

&lt;/p&gt;&lt;dl&gt;
 &lt;dt&gt;
  General working knowledge of the
  &lt;abbr title="Command Line Interface"&gt;CLI&lt;/abbr&gt;
 &lt;/dt&gt;
 &lt;dd&gt;
  While most of the shell commands
  should work in the majority of Unix-like shells
  (&lt;code&gt;/bin/sh&lt;/code&gt;,
  &lt;code&gt;bash&lt;/code&gt;,
  &lt;code&gt;ksh&lt;/code&gt;,
  &lt;code&gt;zsh&lt;/code&gt;,
  or
  &lt;code&gt;csh&lt;/code&gt;/&lt;code&gt;tcsh&lt;/code&gt;)
  some have slight syntax differences.
  Such differences might include the
  &lt;code&gt;for&lt;/code&gt;
  loop syntax
  (&lt;code&gt;csh&lt;/code&gt;/&lt;code&gt;tcsh&lt;/code&gt;
  has a different syntax
  than the
  &lt;code&gt;sh&lt;/code&gt;
  style loops I use),
  and what variables the shell provides
  (such as
  &lt;code&gt;$COLUMNS&lt;/code&gt;
  that
  &lt;code&gt;bash&lt;/code&gt;
  &amp;amp;
  &lt;code&gt;ksh&lt;/code&gt;
  provide,
  which you might have to define
  or hard-code
  before using in other shells).
  If you build from source
  into a local
  &lt;code&gt;~/bin/&lt;/code&gt;
  directory,
  you will want to know about
  modifying your
  &lt;code&gt;$PATH&lt;/code&gt;
  as well.
  You don't need a
  &lt;abbr title="doctorate"&gt;Ph.D.&lt;/abbr&gt;
  in using the command-line
  to do any of these,
  but I do assume basic
  directory creation &amp;amp; navigation
  skills using
  &lt;code&gt;mkdir&lt;/code&gt;
  &amp;amp;
  &lt;code&gt;cd&lt;/code&gt;,
  as well as moving/renaming
  and copying with
  &lt;code&gt;mv&lt;/code&gt;/&lt;code&gt;cp&lt;/code&gt;
  and output redirection.
  I also have a section on
  &lt;a href="https://blog.thechases.com/posts/remind/#version-control"&gt;using
  &lt;code&gt;git&lt;/code&gt;
  or
  &lt;code&gt;rcs&lt;/code&gt;
  to version your reminders&lt;/a&gt;
  but that section aims
  to help you integrate whatever
  version-control software
  you already use
  rather than try to teach
  either as a requirement.
 &lt;/dd&gt;
 &lt;dt&gt;
  General competency with a text editor
 &lt;/dt&gt;
 &lt;dd&gt;
  Reminder files are pure plain-text.
  So you can edit them with your favorite
  &lt;code&gt;$EDITOR&lt;/code&gt;,
  whether
  &lt;code&gt;nano&lt;/code&gt;,
  &lt;code&gt;emacs&lt;/code&gt;/&lt;code&gt;mg&lt;/code&gt;,
  &lt;code&gt;vi&lt;/code&gt;/&lt;code&gt;vim&lt;/code&gt;/&lt;code&gt;neovim&lt;/code&gt;,
  &lt;a href="https://blog.thechases.com/categories/ed/"&gt;&lt;code&gt;ed&lt;/code&gt;&lt;/a&gt;,
  &lt;a href="https://xkcd.com/378/" title="XKCD about text-editors"&gt;&lt;code&gt;cat&lt;/code&gt;,
  a magnetized needle
  and a steady hand,
  or butterflies&lt;/a&gt;.
  But this guide assumes
  that you can edit text files
  and save them.
 &lt;/dd&gt;
 &lt;dt&gt;
  You have
  &lt;code&gt;remind&lt;/code&gt;
  already installed
  or can install it
 &lt;/dt&gt;
 &lt;dd&gt;
  If you administer your personal system,
  you should have
  root/&lt;code&gt;sudo&lt;/code&gt;/&lt;code&gt;doas&lt;/code&gt;
  access
  &lt;a href="https://blog.thechases.com/posts/remind/#installing"&gt;to install
  &lt;code&gt;remind&lt;/code&gt;
  from a binary package&lt;/a&gt;
  (available in most repositories)
  or
  possibly install a C compiler like
  &lt;code&gt;gcc&lt;/code&gt;/&lt;code&gt;clang&lt;/code&gt;
  to
  &lt;a href="https://blog.thechases.com/posts/remind/#install-source"&gt;install from source&lt;/a&gt;.
  If you do not administer your system,
  you can either talk to your system administrator
  and ask them to install
  &lt;code&gt;remind&lt;/code&gt;
  (try offering baked goods);
  or you can install from source,
  specifying a user-local
  directory such as
  &lt;code&gt;$HOME/bin/&lt;/code&gt;
  (as long as you include that in your
  &lt;code&gt;$PATH&lt;/code&gt;,
  you have a C compiler on the system,
  and your administrator didn't mark your
  &lt;code&gt;$HOME/&lt;/code&gt;
  directory's mount-point as
  &lt;code&gt;noexec&lt;/code&gt;).
 &lt;/dd&gt;
&lt;/dl&gt;


&lt;h2 id="installing"&gt;Installing&lt;/h2&gt;

 &lt;h3 id="install-package"&gt;As a package&lt;/h3&gt;

&lt;p&gt;
Initially released in 1990
via USENET on
&lt;code&gt;comp.sources.misc&lt;/code&gt;,
nearly every package repository
should provide
&lt;code&gt;remind&lt;/code&gt;,
making it as easy to install
as any other package:

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@freebsd# &lt;/label&gt;pkg install remind
&lt;/pre&gt;
&lt;figcaption&gt;FreeBSD&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user@openbsd$ &lt;/label&gt;doas pkg_add remind
&lt;/pre&gt;
&lt;figcaption&gt;OpenBSD&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user@debian$ &lt;/label&gt;sudo apt-get install remind
&lt;/pre&gt;
&lt;figcaption&gt;Debian/Ubuntu based&lt;/figcaption&gt;
&lt;/figure&gt;
On some systems,
this has no dependencies;
on other systems,
this brings in some
&lt;abbr title="Graphical User Interface"&gt;GUI&lt;/abbr&gt;
components
(&lt;abbr title="Tool Command Language"&gt;Tcl&lt;/abbr&gt;/&lt;abbr title="Toolkit"&gt;tk&lt;/abbr&gt;)
because the package might also include
&lt;a href="https://blog.thechases.com/posts/remind/#tkremind"&gt;the optional graphical tkRemind&lt;/a&gt;
component.
&lt;/p&gt;

 &lt;h3 id="install-source"&gt;From source&lt;/h3&gt;

&lt;p&gt;
Alternatively, download the source
directly from the
&lt;a href="https://dianne.skoll.ca/projects/remind/download/remind-03.03.00.tar.gz"&gt;latest tarball&lt;/a&gt;
(and hopefully verify it using
&lt;code&gt;gpg&lt;/code&gt;
with the
&lt;a href="https://dianne.skoll.ca/key.php"&gt;PGP key&lt;/a&gt;
which should have the fingerprint
&lt;code&gt;738E:4D95:4052:902C:147D:07B2:685A:5A5E:511D:30E2&lt;/code&gt;)
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;VER=0.3.03.00
&lt;label class="user"&gt;user$ &lt;/label&gt;wget &lt;a href="https://dianne.skoll.ca/projects/remind/download/remind-03.03.00.tar.gz"&gt;https://dianne.skoll.ca/projects/remind/download/remind-&lt;var&gt;${VER}&lt;/var&gt;.tar.gz&lt;/a&gt;{,.sig}
&lt;label class="user"&gt;user$ &lt;/label&gt;gpg --verify remind-&lt;var&gt;${VER}&lt;/var&gt;.tar.gz.sig  xvfz remind-&lt;var&gt;${VER}&lt;/var&gt;.tar.gz
&lt;label class="user"&gt;user$ &lt;/label&gt;tar xvfz &lt;var&gt;remind-${VER}.tar.gz&lt;/var&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;mv &lt;var&gt;remind-${VER}&lt;/var&gt; remind
&lt;/pre&gt;
&lt;/figure&gt;
or if you want to download from
&lt;code&gt;git&lt;/code&gt;:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;git clone &lt;a href="https://dianne.skoll.ca/projects/remind/git/Remind.git"&gt;https://dianne.skoll.ca/projects/remind/git/Remind.git&lt;/a&gt; remind
&lt;/pre&gt;
&lt;/figure&gt;
and then compile it yourself:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;cd remind
&lt;label class="user"&gt;user$ &lt;/label&gt;./configure
&lt;label class="user"&gt;user$ &lt;/label&gt;make
&lt;label class="user"&gt;user$ &lt;/label&gt;sudo make install
&lt;/pre&gt;
&lt;/figure&gt;
If you prefer not to
&lt;code&gt;make install&lt;/code&gt;
you can move/copy the resulting binaries from
&lt;code&gt;remind/src/{remind,rem2ps}&lt;/code&gt;
to a
&lt;code&gt;~/bin/&lt;/code&gt;
directory
(in your
&lt;code&gt;$PATH&lt;/code&gt;),
create a link from
&lt;code&gt;remind&lt;/code&gt;
to
&lt;code&gt;rem&lt;/code&gt;,
and put the
&lt;code&gt;man&lt;/code&gt;
pages from
&lt;code&gt;remind/man/*.1&lt;/code&gt;
in your
&lt;code&gt;$MANPATH&lt;/code&gt;.
&lt;/p&gt;

&lt;h2 id="getting-started"&gt;Getting started&lt;/h2&gt;

 &lt;h3 id="your-first-remind-file"&gt;Creating your first
  &lt;code&gt;remind&lt;/code&gt;
  file
  &lt;/h3&gt;

&lt;p&gt;
By default, invoking
&lt;code&gt;rem&lt;/code&gt;
expects data in
&lt;code&gt;~/.reminders&lt;/code&gt;
and you can just dump all your reminders in there
(though
&lt;code&gt;remind&lt;/code&gt;
also lets you
&lt;a href="https://blog.thechases.com/posts/remind/#organizing-reminders"&gt;split reminders up into
individual files&lt;/a&gt;).
When invoked as
&lt;code&gt;remind&lt;/code&gt;
rather than
&lt;code&gt;rem&lt;/code&gt;
you must specify the reminder file directly:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;remind &lt;mark&gt;path/to/reminders.rem&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
The most basic reminder
consists of
&lt;code&gt;REM&lt;/code&gt;
followed by a date of some sort,
followed by
&lt;code&gt;MSG&lt;/code&gt;
and the text of the reminder.
A few things to keep in mind
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;
  &lt;code&gt;remind&lt;/code&gt;
  ignores blank lines.
  and treats lines beginning with a
  &lt;code&gt;#&lt;/code&gt;
  as comments
 &lt;/li&gt;
 &lt;li&gt;
  to continue a reminder on the next line,
  end the previous line with a backslash
  ("&lt;code&gt;\&lt;/code&gt;")
 &lt;/li&gt;
 &lt;li&gt;
  If you only include part of the date
  &lt;code&gt;remind&lt;/code&gt;
  assumes the missing parts repeat.
 &lt;/li&gt;
 &lt;li&gt;
  You can specify either the numeric day of the month
  or the day of the week
  (in which case it will repeat on that day of the week).
 &lt;/li&gt;
 &lt;li&gt;
  For the month and week-day,
  &lt;code&gt;remind&lt;/code&gt;
  does not distinguish between upper/lower-case
  and thus treats "Jan", "jan", "JaN", "jAn", and "JAN"
  all as the same.
 &lt;/li&gt;
 &lt;li&gt;
  Additionally, while
  &lt;code&gt;remind&lt;/code&gt;
  requires the first three characters
  of months ("Jan")
  and weekday names ("Wed"),
  you may also fully spell out
  the month ("January")
  or weekday name ("Wednesday")
  if that makes it easier for you to read.
 &lt;/li&gt;
 &lt;li&gt;
  If you provide more than one weekday,
  &lt;code&gt;remind&lt;/code&gt;
  will match any of them.
 &lt;/li&gt;
 &lt;li&gt;
  Alternatively, you can write the date in
  &lt;code&gt;YYYY-MM-DD&lt;/code&gt;
  format
 &lt;/li&gt;
 &lt;li&gt;
  If you omit all of the values,
  omit the
  &lt;code&gt;REM&lt;/code&gt;
  too
 &lt;/li&gt;

For example if you use your
&lt;code&gt;$EDITOR&lt;/code&gt;
to create this
&lt;code&gt;~/.reminders&lt;/code&gt;
file
&lt;figure&gt;
&lt;pre&gt;
# this is a comment
# these and the next (blank) line are ignored

REM &lt;mark&gt;Jan 1 2020&lt;/mark&gt; MSG &lt;var&gt;The first day of the 2020&lt;/var&gt;
REM &lt;mark&gt;2020-1-1&lt;/mark&gt; MSG &lt;var&gt;Also the first day of the 2020&lt;/var&gt;
REM &lt;mark&gt;Jan 1&lt;/mark&gt; MSG &lt;var&gt;The first day of the year&lt;/var&gt;
REM &lt;mark&gt;1&lt;/mark&gt; MSG &lt;var&gt;The first day of every month&lt;/var&gt;
REM &lt;mark&gt;Jan&lt;/mark&gt; MSG &lt;var&gt;Every day in each January&lt;/var&gt;
REM &lt;mark&gt;January&lt;/mark&gt; MSG &lt;var&gt;Also every day in each January&lt;/var&gt;
REM &lt;mark&gt;Jan 1&lt;/mark&gt; MSG &lt;var&gt;Every January 1st&lt;/var&gt;
REM &lt;mark&gt;Jan 2020&lt;/mark&gt; MSG &lt;var&gt;Every day in January 2020&lt;/var&gt;
REM &lt;mark&gt;Wed&lt;/mark&gt; MSG &lt;var&gt;Every Wednesday&lt;/var&gt;
REM &lt;mark&gt;Wednesday&lt;/mark&gt; MSG &lt;var&gt;Also every Wednesday&lt;/var&gt;
REM &lt;mark&gt;Wed Jan&lt;/mark&gt; MSG &lt;var&gt;Every Wednesday in every January&lt;/var&gt;
REM &lt;mark&gt;Wed Jan 2020&lt;/mark&gt; MSG &lt;var&gt;Every Wednesday in January 2020&lt;/var&gt;
REM &lt;mark&gt;Mon Wed Fri&lt;/mark&gt; MSG &lt;var&gt;Every Monday, Wednesday, and Friday&lt;/var&gt;
REM &lt;mark&gt;Sat Sun Jan 2020&lt;/mark&gt; MSG &lt;var&gt;Every weekend in January of 2020&lt;/var&gt;
MSG &lt;var&gt;With no REM, this happens every single day&lt;/var&gt;
REM Jan 1 2020 &lt;mark&gt;\&lt;/mark&gt;
  MSG This line has a continuation
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
To see (most of) these reminders,
use either of these commands
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;remind ~/.reminders 2020-01-01
&lt;label class="user"&gt;user$ &lt;/label&gt;rem 2020-01-01
&lt;/pre&gt;
&lt;/figure&gt;
If you happen to run the command
on January 1&lt;sup&gt;st&lt;/sup&gt;, 2020,
you can omit the date
because
&lt;code&gt;remind&lt;/code&gt;
defaults to the current date.
I most frequently invoke just
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem
&lt;/pre&gt;
&lt;/figure&gt;
to get today's events.


&lt;p&gt;
By specifying a day of the week
and a numeric date,
&lt;code&gt;remind&lt;/code&gt;
will find the next date-match
and will then scan forward
until it finds the matching day of the week.
&lt;figure&gt;
&lt;pre&gt;
REM Mon 1 MSG First Monday of the month
REM Mon 1 Jan MSG First Monday in January
REM Mon 1 Jan 2020 MSG First Monday in January, 2020
REM Mon 8 MSG Second Monday of the month
REM Mon 15 MSG Third Monday of the month
REM Mon 22 MSG Fourth Monday of the month
REM Mon 29 MSG &lt;mark&gt;Infrequent Fifth Monday&lt;/mark&gt; of the month
REM Sat Sun 15 MSG Third Saturday or Sunday of the month
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;span class="warning"&gt;Beware&lt;/span&gt;
with that "Mon 29"
because the
"Monday on-or-after the 29&lt;sup&gt;th&lt;/sup&gt;"
might fall in the following month.
When we discuss the
&lt;a href="https://blog.thechases.com/posts/remind/#satisfy"&gt;&lt;code&gt;SATISFY&lt;/code&gt;&lt;/a&gt;
clause later,
we can limit this
so it only notifies
on the fifth day
in the
&lt;em&gt;same&lt;/em&gt;
month.
&lt;/p&gt;

&lt;p&gt;
&lt;span class="warning"&gt;Also note&lt;/span&gt;
that this can have interesting side-effects
such as
&lt;a href="https://blog.thechases.com/posts/remind/#scanfrom"&gt;trying to find Labor Day&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Similarly, our city offers a
program for recycling items
that we shouldn't put in our recycle bin
(hazardous household waste,
electronics, etc).
These events occur quarterly
on the Saturday
after the first Friday
of the month.
We can express the date as
&lt;figure&gt;
&lt;pre&gt;
REM &lt;mark&gt;Sat 2&lt;/mark&gt; MSG Recycling center drop-off day
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
to ensure that it falls on the Saturday after
the first Friday in the month.
We can then add the
&lt;a href="https://blog.thechases.com/posts/remind/#satisfy"&gt;&lt;code&gt;SATISFY&lt;/code&gt;&lt;/a&gt;
(discussed later)
to ensure it only falls on one of the quarterly dates:
&lt;figure&gt;
&lt;pre&gt;
REM Sat 2 &lt;mark&gt;SATISFY [$Tm % 3 == 2]&lt;/mark&gt; \
  MSG Recycling center drop-off day
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="times"&gt;Adding time information&lt;/h3&gt;

&lt;p&gt;
To indicate the time and length of an event use
&lt;code&gt;AT&lt;/code&gt;
(in military/24-hr time format;
I wish the parser allowed
for 12-hr AM/PM notation, but alas)
and optional
&lt;code&gt;DURATION&lt;/code&gt;
(in hours and minutes).
Here I have a doctor's appointment at 2:30pm
but don't know how long it will run.
And I go to the gym
on Tuesday/Thursday
at 5:00am
for an hour and fifteen minutes:
&lt;figure&gt;
&lt;pre&gt;
REM Jan 30 2020 &lt;mark&gt;AT 14:30&lt;/mark&gt; MSG Doctor's appointment
REM Tue Thu &lt;mark&gt;AT 5:00 DURATION 1:15&lt;/mark&gt; MSG Gym
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
At the moment,
adding time information doesn't
net you any big gains in the agenda view,
but they will appear in the
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;week/month view&lt;/a&gt;
and when we discuss
&lt;a href="https://blog.thechases.com/posts/remind/#substitution"&gt;substitution filters&lt;/a&gt;
they let you include time information in the
&lt;code&gt;MSG&lt;/code&gt;.
&lt;/p&gt;

 &lt;h3 id="notice"&gt;Getting advance notice&lt;/h3&gt;

&lt;p&gt;
Sometimes you want a bit of advanced warning
like knowing about your doctor's appointment tomorrow.
Perhaps you want a week of notice
before your mother's birthday
so you can get something in the mail.
Or you want a 60 days of notice
before you have to pay taxes
so you can get all your paperwork in order.
&lt;code&gt;remind&lt;/code&gt;
uses
&lt;code&gt;+&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
and
&lt;code&gt;++&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
to give you
&lt;var&gt;n&lt;/var&gt;
days of advanced warning.
The single
&lt;code&gt;+&lt;/code&gt;
skips over
&lt;a href="https://blog.thechases.com/posts/remind/#omit-notice"&gt;&lt;code&gt;OMIT&lt;/code&gt;
dates&lt;/a&gt;,
while
&lt;code&gt;++&lt;/code&gt;
ignores
&lt;code&gt;OMIT&lt;/code&gt;
dates.
For now, use the
&lt;code&gt;++&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
form for simplicity.
&lt;figure&gt;
&lt;pre&gt;
REM Jan 1 2020 &lt;mark&gt;++1&lt;/mark&gt; AT 14:00 MSG &lt;var&gt;Doctor's appointment&lt;/var&gt;
REM May 12 &lt;mark&gt;++7&lt;/mark&gt; MSG &lt;var&gt;Mom's birthday&lt;/var&gt;
REM Apr 15 &lt;mark&gt;++60&lt;/mark&gt; MSG &lt;var&gt;Taxes&lt;/var&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;code&gt;remind&lt;/code&gt;
recognizes that you likely want this advanced notice
in the agenda view
but smartly prevents the advanced noticed from cluttering up
in the ASCII
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;week or month calendar views&lt;/a&gt;
(unless you explicitly request them with
&lt;code&gt;-p&lt;mark&gt;a&lt;/mark&gt;&lt;/code&gt;
or
&lt;code&gt;-s&lt;mark&gt;a&lt;/mark&gt;&lt;/code&gt;).
While less useful to get the same reminder several days out,
&lt;code&gt;remind&lt;/code&gt;
lets you
&lt;a href="https://blog.thechases.com/posts/remind/#substitution"&gt;adjust the reminder text
based on the number of days out&lt;/a&gt;
to make these more helpful.
&lt;/p&gt;

 &lt;h3 id="adjusting"&gt;Adjusting dates&lt;/h3&gt;

&lt;p&gt;
To adjust a date backwards, use
&lt;code&gt;-&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
or
&lt;code&gt;--&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
to specify the number of days.
At first glance,
this provides little benefit
over just doing the math
and hard-coding it
for normal reminders:
&lt;figure&gt;
&lt;pre&gt;
REM 15 --3 MSG Really the 12th
REM 12 MSG Just use the 12th instead
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
However, this lets you schedule reminders
relative to the last day of the month:
&lt;figure&gt;
&lt;pre&gt;
REM 1 &lt;mark&gt;--1&lt;/mark&gt; MSG Last day of the month
REM 1 &lt;mark&gt;--7&lt;/mark&gt; MSG One week left in the month
REM 1 Wed &lt;mark&gt;--7&lt;/mark&gt; MSG Last Wednesday of the month
REM Feb 1 Wed &lt;mark&gt;--7&lt;/mark&gt; MSG Last Wednesday in January
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
The first one
finds the first day of the month
and then goes back 1 day.
The second entry
goes back 7 days from the first of each month.
The third entry
finds the first Wednesday in the month
and then goes back 7 days
(to the last Wednesday of the previous month).
The last entry does similarly,
finding the first Wednesday in February,
then going back 7 days
to find the last Wednesday in January.
&lt;/p&gt;

&lt;p&gt;
As with
&lt;code&gt;+&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
vs.
&lt;code&gt;++&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;,
the single
"&lt;code&gt;-&lt;/code&gt;"
jumps over
&lt;a href="https://blog.thechases.com/posts/remind/#omit-notice"&gt;&lt;code&gt;OMIT&lt;/code&gt;
dates&lt;/a&gt;,
(so they can end up going back
more than &lt;var&gt;n&lt;/var&gt; days
if they encounter an intervening holiday);
while the double
"&lt;code&gt;--&lt;/code&gt;"
ignores
&lt;code&gt;OMIT&lt;/code&gt;
dates.
These adjustments can combine
&lt;figure&gt;
&lt;pre&gt;
REM 1 &lt;mark&gt;--1 ++3&lt;/mark&gt; MSG Last day of the month
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
This will put the event on the last day of the month
but also remind you 3 days in advance
that the end of the month will arrive soon.
&lt;/p&gt;

 &lt;h3 id="repeating-reminders"&gt;Repeating reminders&lt;/h3&gt;

&lt;p&gt;
As shown above,
if you omit portions of a
&lt;code&gt;REM&lt;/code&gt;
statement,
&lt;code&gt;remind&lt;/code&gt;
treats the missing date part
as a repeat.
This lets you create reminders
such as
birthdays and anniversaries,
holidays,
weekly events like church,
or invoicing customers
on a given day of each month:
&lt;figure&gt;
&lt;pre&gt;
REM &lt;mark&gt;Jan 13&lt;/mark&gt; MSG Steve's birthday
REM &lt;mark&gt;Feb 18 ++7&lt;/mark&gt; MSG Mom &amp;amp; Dad's anniversary
REM &lt;mark&gt;Apr 1 ++1&lt;/mark&gt; MSG April Fool's Day
REM &lt;mark&gt;Sun&lt;/mark&gt; AT 9:00 MSG Church
REM &lt;mark&gt;15&lt;/mark&gt; MSG Invoice customers
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Note the "++7" style notation
to get a week of notice before my parents' anniversary
so I get something in the mail for them.
I also get a warning one day before April Fool's Day
so I don't get surprised by pranks.
&lt;/p&gt;

&lt;p&gt;
On occasion I need a reminder every N days
like picking up a 90-day prescription
that started on January 27&lt;sup&gt;th&lt;/sup&gt; of 2019.
To do this,
use an asterisk followed by the number of days to repeat.
&lt;figure&gt;
&lt;pre&gt;
REM Jan 27 2019 &lt;mark&gt;*90&lt;/mark&gt; MSG Pick up medication
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
I'll cover a more complex
&lt;a href="https://blog.thechases.com/posts/remind/#medication"&gt;example of a medication schedule&lt;/a&gt;
towards the end.
&lt;/p&gt;

&lt;p&gt;
For repeating events,
&lt;code&gt;remind&lt;/code&gt;
lets you specify date ranges
using
&lt;code&gt;FROM&lt;/code&gt;,
&lt;code&gt;UNTIL&lt;/code&gt;,
and
&lt;code&gt;THROUGH&lt;/code&gt;
keywords.
For example, if your vacation starts on December 21&lt;sup&gt;st&lt;/sup&gt;
and repeats daily
(&lt;code&gt;*1&lt;/code&gt;)
until January 1st
you can use:
&lt;figure&gt;
&lt;pre&gt;
REM Dec 21 2019 &lt;mark&gt;*1 UNTIL Jan 1 2020&lt;/mark&gt; MSG Vacation
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
The
&lt;code&gt;THROUGH&lt;/code&gt;
keyword simplifies the common
"&lt;code&gt;*1&lt;/code&gt;"
use case,
so this works exactly the same:
&lt;figure&gt;
&lt;pre&gt;
REM Dec 21 2019 &lt;mark&gt;THROUGH Jan 1 2020&lt;/mark&gt; MSG Vacation
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
You might also have to take a medication
every other day
(&lt;code&gt;*2&lt;/code&gt;)
starting on January 1&lt;sup&gt;st&lt;/sup&gt;
through the end of the month:
&lt;figure&gt;
&lt;pre&gt;
REM Jan 1 2019 &lt;mark&gt;*2 UNTIL Jan 31 2020&lt;/mark&gt; MSG Take medication
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Alternatively, you might have a
Wednesday game night during the school year,
or a meeting on the 12&lt;sup&gt;th&lt;/sup&gt;
of each month during the school year:
&lt;figure&gt;
&lt;pre&gt;
REM Wed &lt;mark&gt;FROM Aug 21 2019 UNTIL Jun 1 2020&lt;/mark&gt; MSG Game Night
REM 12 &lt;mark&gt;FROM Aug 21 2019 UNTIL Jun 1 2020&lt;/mark&gt; MSG School year meeting
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2 id="output"&gt;Modifying the output&lt;/h2&gt;

 &lt;h3 id="views"&gt;Other views&lt;/h3&gt;

&lt;p&gt;
If you have no reminders for a given day,
&lt;code&gt;remind&lt;/code&gt;
prints "No reminders."
Otherwise,
&lt;code&gt;remind&lt;/code&gt;
&lt;a href="https://blog.thechases.com/posts/remind/#banner"&gt;prints a banner&lt;/a&gt;
of the form
"Reminders for Wednesday, 1st January, 2020 (today)"
followed by an agenda-view of the reminders for that day.
If you want more than one day worth,
specify the number of days with
"&lt;code&gt;*&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;".
Note that you should quote the "*"
either by escaping it with a backslash
or by wrapping it in quotes.
Otherwise the shell might expand it
to a filename in your current directory.
To show four days of reminders:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem \*4
&lt;label class="user"&gt;user$ &lt;/label&gt;rem '*4'
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;code&gt;remind&lt;/code&gt;
also offers an ASCII calendar view
for either the month or week:
&lt;!--
below Nikola mungs the text
and removes the space between the closing of the
mark-tag and the opening of the span-tag
--&gt;
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem &lt;mark&gt;-c&lt;/mark&gt; &lt;span class="comment"&gt;# a month view&lt;/span&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem &lt;mark&gt;-c2&lt;/mark&gt; &lt;span class="comment"&gt;# two month view&lt;/span&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem &lt;mark&gt;-c&lt;/mark&gt; 2019-8-1 &lt;span class="comment"&gt;# one-month-view of 2019-08-01&lt;/span&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem &lt;mark&gt;-c+&lt;/mark&gt; &lt;span class="comment"&gt;# a week view&lt;/span&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem &lt;mark&gt;-c+2&lt;/mark&gt; &lt;span class="comment"&gt;# two week view&lt;/span&gt;
&lt;/pre&gt;
&lt;/figure&gt;
You can change the default width
for either the week or month view
by adding
&lt;code&gt;-w&lt;var&gt;$COLUMNS&lt;/var&gt;&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem -c+2 &lt;mark&gt;-w&lt;var&gt;$COLUMNS&lt;/var&gt;&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
As of version 3.3.0,
&lt;code&gt;remind&lt;/code&gt;
automatically detects the screen width
if you output to a
&lt;abbr title="Teletype—a terminal"&gt;TTY&lt;/abbr&gt;
so you no longer need to specify this explicitly
unless you want something other than the current screen width.
&lt;/p&gt;

 &lt;h3 id="other-msg"&gt;Other message-types&lt;/h3&gt;

&lt;p&gt;
Up to this point,
we've only discussed the
&lt;code&gt;MSG&lt;/code&gt;
message-type.
&lt;code&gt;remind&lt;/code&gt;
also provides
&lt;code&gt;CAL&lt;/code&gt;
for events that should only show up
in the
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;week/month view&lt;/a&gt;
but not the agenda view, and
&lt;code&gt;MSF&lt;/code&gt;
which behaves the same as the
&lt;code&gt;MSG&lt;/code&gt;
except it wraps long text
in the agenda view.
&lt;figure&gt;
&lt;pre&gt;
REM Jan 1 2020 CAL This only appears on the calendar view
REM Jan 1 2020 MSF This is a very long reminder \
  and includes several lines \
  of text all with the escaping backslash \
  but when you view it \
  in the agenda view \
  it will be wrapped \
  so that it formats nicely.
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
(&lt;code&gt;remind&lt;/code&gt;
provides a couple other reminder-types:
&lt;a href="https://blog.thechases.com/posts/remind/#run"&gt;&lt;code&gt;RUN&lt;/code&gt;&lt;/a&gt;
for running an external command,
&lt;a href="https://blog.thechases.com/posts/remind/#special"&gt;&lt;code&gt;SPECIAL&lt;/code&gt;&lt;/a&gt;,
for conveying out-of-band information,
and
&lt;code&gt;PS&lt;/code&gt;/&lt;code&gt;PSFILE&lt;/code&gt;
for passing PostScript information
to a PostScript-processing back-end
which I'll skip for now).
&lt;/p&gt;

 &lt;h3 id="blank-lines"&gt;Getting rid of blank lines&lt;/h3&gt;

&lt;p&gt;
I find it annoying
to get a blank line after each reminder.
You can prevent this
by ending reminder text with a
&lt;code&gt;%&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
REM Jan 30 2020 +3 MSG Doctor's appointment&lt;mark&gt;%&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
For simplicity,
I've omitted the trailing
&lt;code&gt;%&lt;/code&gt;
from all of the other examples in this post,
but I put it on
&lt;strong&gt;every single reminder&lt;/strong&gt;.
&lt;/p&gt;

 &lt;h3 id="percent-quote"&gt;Selectively showing text&lt;/h3&gt;

&lt;p&gt;
You might want
certain text to appear
on the daily agenda calendar
but you don't need that level of detail
on the
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;week/month calendar view&lt;/a&gt;.
You can wrap the text you want
on the week/month calendar view with
&lt;code&gt;%"&lt;/code&gt;…&lt;code&gt;%"&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
REM Jan 30 2020 +3 MSF &lt;mark&gt;%"&lt;/mark&gt;Doctor&lt;mark&gt;%"&lt;/mark&gt;'s appointment \
  123 Main St., Anytown, NY\
  800-555-1212
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
With this in place,
it will display
the full text of the reminder
(without the
&lt;code&gt;%"&lt;/code&gt;
markers)
on the agenda view
but only show
"Doctor"
on the visual calendars.
&lt;/p&gt;

 &lt;h3 id="substitution"&gt;Substitution filter&lt;/h3&gt;

&lt;p&gt;
Your reminder-message can include escape sequences
(beginning with a
&lt;code&gt;%&lt;/code&gt;)
that adjust based on the context.
So instead of a reminder that just reads
"Mom's birthday"
you can use
&lt;figure&gt;
&lt;pre&gt;
REM May 12 ++7 MSG &lt;var&gt;Mom's birthday&lt;/var&gt; &lt;mark&gt;%b&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
and
&lt;code&gt;remind&lt;/code&gt;
will display
"Mom's birthday
&lt;mark&gt;in 7 days' time&lt;/mark&gt;",
…,
"Mom's birthday
&lt;mark&gt;in 2 days' time&lt;/mark&gt;",
"Mom's birthday
&lt;mark&gt;tomorrow&lt;/mark&gt;",
and finally
"Mom's birthday
&lt;mark&gt;today&lt;/mark&gt;".
The
&lt;code&gt;man&lt;/code&gt;
page for
&lt;code&gt;remind&lt;/code&gt;
has a whole collection of these
under the
"&lt;cite&gt;SUBSTITUTION FILTER&lt;/cite&gt;"
section.
I most frequently use
&lt;/p&gt;&lt;dl&gt;
&lt;dt&gt;&lt;code&gt;%b&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
described above, producing
"in &lt;var&gt;N&lt;/var&gt; days' time",
"tomorrow",
and "today"
&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;%l&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
replaced with
"on YYYY-MM-DD",
"tomorrow",
and "today"
&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;%c&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
replaced with the day-of-the-week of the event
such as "on Wednesday",
then "tomorrow",
and "today"
&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;%2&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;
replaced with the
&lt;code&gt;AT&lt;/code&gt;
time in 12-hour am/pm time,
e.g. "at 2:30pm"
&lt;/dd&gt;
&lt;/dl&gt;
These combine to allow things like
&lt;figure&gt;
&lt;pre&gt;
REM Jan 1 2020 +1 AT 14:30 MSG &lt;var&gt;Doctor appointment&lt;/var&gt; &lt;mark&gt;%b %2&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
to produce a reminder like
"Doctor appointment tomorrow at 2:30pm".


 &lt;h3 id="banner"&gt;Tweaking the banner&lt;/h3&gt;

&lt;p&gt;
&lt;code&gt;remind&lt;/code&gt;
prints a "banner"
before each day's agenda.
The default
&lt;code&gt;BANNER&lt;/code&gt;
contains
"&lt;code&gt;Reminders for %w, %d%s %m, %y%o:&lt;/code&gt;"
but you can use any of the
&lt;a href="https://blog.thechases.com/posts/remind/#substitution"&gt;substitution filter&lt;/a&gt;
formatting sequences that fit your wants.
The extra blank line after the
&lt;code&gt;BANNER&lt;/code&gt;
bothers me so let's get rid of that
by putting a
&lt;code&gt;%&lt;/code&gt;
at the end:
&lt;figure&gt;
&lt;pre&gt;
BANNER Reminders for %w, %d%s %m, %y%o:&lt;mark&gt;%&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
and I prefer the month presented before the day:
&lt;figure&gt;
&lt;pre&gt;
BANNER Reminders for %w, &lt;mark&gt;%m %d%s&lt;/mark&gt;, %y%o:%
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
When I use
&lt;code&gt;rem '*3'&lt;/code&gt;
to
&lt;a href="https://blog.thechases.com/posts/remind/#repeating-reminders"&gt;get 3 days worth of reminders&lt;/a&gt;
I dislike how it formats the dividers
because they all flow together visually,
making it hard for me to read.
Fortunately,
&lt;code&gt;remind&lt;/code&gt;
lets you use an
&lt;code&gt;IF&lt;/code&gt;/&lt;code&gt;ELSE&lt;/code&gt;/&lt;code&gt;ENDIF&lt;/code&gt;
block to conditionally
tweak this as well:
&lt;figure&gt;
&lt;pre&gt;
&lt;mark&gt;IF&lt;/mark&gt; defined("subsequent_iteration")
  BANNER ---------------------%_%w, %m %d%s, %y%o:%
&lt;mark&gt;ELSE&lt;/mark&gt;
  BANNER %w, %m %d%s, %y%o:%
  SET subsequent_iteration 1
  PRESERVE subsequent_iteration
&lt;mark&gt;ENDIF&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
On the first pass,
&lt;code&gt;subsequent_iteration&lt;/code&gt;
is not defined,
so we set the default
&lt;code&gt;BANNER&lt;/code&gt;
and then set/define
&lt;code&gt;subsequent_iteration&lt;/code&gt;.
The
&lt;code&gt;PRESERVE&lt;/code&gt;
keyword then keeps the value of
&lt;code&gt;subsequent_iteration&lt;/code&gt;
defined through subsequent iterations.
Now defined,
&lt;code&gt;remind&lt;/code&gt;
takes the
&lt;code&gt;ELSE&lt;/code&gt;
block,
prefixing the banner with a row of dashes,
a new-line,
and then the normal banner text.
&lt;/p&gt;

 &lt;h3 id="sorting"&gt;Sorting&lt;/h3&gt;

&lt;p&gt;
By default,
&lt;code&gt;remind&lt;/code&gt;
prints agenda items in the order in which
they appear in the reminder files.
Using the
&lt;code&gt;-g&lt;/code&gt;
flag tells
&lt;code&gt;remind&lt;/code&gt;
to sort the output.
Following the
&lt;code&gt;-g&lt;/code&gt;
comes up to four
"a" or "d"
characters to indicate
&lt;mark&gt;a&lt;/mark&gt;scending
or
&lt;mark&gt;d&lt;/mark&gt;escending
order.
Positionally, these indicate
&lt;/p&gt;&lt;ol&gt;
 &lt;li&gt;the trigger date of the event&lt;/li&gt;
 &lt;li&gt;the trigger time of the event&lt;/li&gt;
 &lt;li&gt;
  the
  &lt;a href="https://blog.thechases.com/posts/remind/#priority"&gt;priority&lt;/a&gt;
  of the event
 &lt;/li&gt;
 &lt;li&gt;whether to sort timed events before untimed events&lt;/li&gt;
&lt;/ol&gt;
So
&lt;code&gt;rem -gaad&lt;/code&gt;
sorts first by trigger date (ascending),
then by trigger time (ascending),
and finally by priority (descending),
while not specifying whether
timed or untimed events should come first.


 &lt;h3 id="sort-banner"&gt;Sub-BANNERs for sorting&lt;/h3&gt;

&lt;p&gt;
When sorting reminders,
I like to have a visual indication
between where today's reminders stop
and subsequent days'
&lt;a href="https://blog.thechases.com/posts/remind/#notice"&gt;advanced notices&lt;/a&gt;
so I override the special
&lt;code&gt;sortbanner()&lt;/code&gt;
&lt;a href="https://blog.thechases.com/posts/remind/#functions"&gt;function&lt;/a&gt;.
Much like the
&lt;a href="https://blog.thechases.com/posts/remind/#banner"&gt;&lt;code&gt;BANNER&lt;/code&gt;&lt;/a&gt;,
this lets you define
a separator that comes
between these events.
I use the
&lt;code&gt;$SortByDate&lt;/code&gt;,
&lt;code&gt;$SortByPrio&lt;/code&gt;,
and
&lt;code&gt;$SortByTime&lt;/code&gt;
variables to determine if any
&lt;a href="https://blog.thechases.com/posts/remind/#sorting"&gt;sort-order&lt;/a&gt;
creates these logical breaks,
and then use a
&lt;a href="https://blog.thechases.com/posts/remind/#substitution"&gt;substitution filter&lt;/a&gt;
in my function-result:
&lt;figure&gt;
&lt;pre&gt;
SET banner_str "…"
IF &lt;mark&gt;$SortByDate + $SortByPrio + $SortByTime&lt;/mark&gt; &amp;gt; 0
  BANNER %
  FSET &lt;mark&gt;sortbanner(x)&lt;/mark&gt; iif(x == $U, \
    banner_str, \
    "----%B----%" \
    )
ELSE
  BANNER [banner_str]
ENDIF
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
I use the
&lt;code&gt;BANNER %&lt;/code&gt;
to suppress the regular banner,
letting the
&lt;code&gt;sortbanner()&lt;/code&gt;
assume those duties.
Also, by capitalizing the substitution-filter
(&lt;code&gt;%B&lt;/code&gt; instead of &lt;code&gt;%b&lt;/code&gt;)
it capitalizes the resulting output.
&lt;/p&gt;

 &lt;h3 id="next-reminder"&gt;Next instance of reminders&lt;/h3&gt;

&lt;p&gt;
If you want to know the next time
a reminder will occur,
&lt;code&gt;remind&lt;/code&gt;
offers the "next"
(&lt;code&gt;-n&lt;/code&gt;)
flag.
Beware as this produces ~900 reminders for me
(one for each of my 1600+
reminders yet to happen in the future),
but you can
&lt;code&gt;grep&lt;/code&gt;
this output or pipe it to
&lt;code&gt;$PAGER&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem &lt;mark&gt;-n&lt;/mark&gt; |
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;grep Alice |
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;sort |
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;less
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2 id="file-management"&gt;File management&lt;/h2&gt;

 &lt;h3 id="organizing-reminders"&gt;Organizing reminders&lt;/h3&gt;

&lt;p&gt;
While
&lt;code&gt;remind&lt;/code&gt;
will happily let you dump all your reminders in
&lt;code&gt;~/.reminders&lt;/code&gt;,
it also provides functionality
to split and combine files using the
&lt;code&gt;INCLUDE&lt;/code&gt;
directive.
I like to organize reminders
into files based on reminder-type,
so I created a directory to house them all
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;cd
&lt;label class="user"&gt;user$ &lt;/label&gt;&lt;span class="comment"&gt;# don't tromp over existing reminders if we have them&lt;/span&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;mv .reminders original_reminders.rem
&lt;label class="user"&gt;user$ &lt;/label&gt;mkdir -p ~/.config/remind
&lt;label class="user"&gt;user$ &lt;/label&gt;echo 'SET remind_dir getenv("HOME") + "/.config/remind/"' &amp;gt; \
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;~/.config/remind/reminders.rem
&lt;/pre&gt;
&lt;/figure&gt;
and link the
&lt;code&gt;~/.reminders&lt;/code&gt;
to point to my main reminder file:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;ln -s .config/remind/reminders.rem ~/.reminders
&lt;label class="user"&gt;user$ &lt;/label&gt;cd ~/.config/remind
&lt;/pre&gt;
&lt;/figure&gt;
This uses a little
&lt;code&gt;remind&lt;/code&gt;
magic
(see the section on
&lt;a href="https://blog.thechases.com/posts/remind/#expressions"&gt;expressions&lt;/a&gt;
below)
to dynamically find your reminder directory
for later use in the
&lt;code&gt;INCLUDE&lt;/code&gt;
statements.
This lets me move
or
&lt;code&gt;rsync&lt;/code&gt;
my reminders to another machine
under a different
&lt;code&gt;$HOME&lt;/code&gt;
and everything will continue to work.
&lt;/p&gt;

&lt;p&gt;
Alternatively,
you can create a link
to the containing folder
instead of a reminder file
full of
&lt;code&gt;INCLUDE&lt;/code&gt;
lines:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;ln -s .config/remind/ ~/.reminders
&lt;/pre&gt;
&lt;/figure&gt;
and
&lt;code&gt;remind&lt;/code&gt;
will process all of the
&lt;code&gt;*.rem&lt;/code&gt;
files in the target folder
in glob order.
If you want a particular order,
you can name the files
with a prefix like
&lt;code&gt;010-birthdays.rem&lt;/code&gt;,
&lt;code&gt;015-anniversaries.rem&lt;/code&gt;,
&lt;code&gt;020-work.rem&lt;/code&gt;,
etc.
&lt;/p&gt;

 &lt;h3 id="initial-file-types"&gt;Initial file types&lt;/h3&gt;

&lt;p&gt;
I start by creating a
&lt;code&gt;helpers.rem&lt;/code&gt;
file to store a variety of
helper functions and constants.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;touch ~/.config/remind/helpers.rem
&lt;/pre&gt;
&lt;/figure&gt;
I then create files for various event types and
&lt;code&gt;INCLUDE&lt;/code&gt;
them into the master
&lt;code&gt;reminders.rem&lt;/code&gt;
file:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;for f in ushol birthdays anniversaries work bills &lt;label&gt;…&lt;/label&gt;
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;do
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;echo "INCLUDE [filedir()]/helpers.rem" &amp;gt; ~/.config/remind/${f}.rem
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;echo "INCLUDE [remind_dir]/${f}.rem" &amp;gt;&amp;gt; ~/.config/remind/reminders.rem
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;done
&lt;/pre&gt;
&lt;/figure&gt;
While I've simplified my list of calendars above
for the sake of brevity,
I have nearly 30 different calendar files
to keep things organized:
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;an individual file for myself&lt;/li&gt;
 &lt;li&gt;one for my wife&lt;/li&gt;
 &lt;li&gt;one for each kid&lt;/li&gt;
 &lt;li&gt;one for each kid's school calendar&lt;/li&gt;
 &lt;li&gt;birthdays&lt;/li&gt;
 &lt;li&gt;anniversaries&lt;/li&gt;
 &lt;li&gt;family events&lt;/li&gt;
 &lt;li&gt;household chores&lt;/li&gt;
 &lt;li&gt;finances&lt;/li&gt;
 &lt;li&gt;church events&lt;/li&gt;
 &lt;li&gt;one for each place I volunteer&lt;/li&gt;
 &lt;li&gt;US holidays&lt;/li&gt;
 &lt;li&gt;events at our local library&lt;/li&gt;
 &lt;li&gt;work&lt;/li&gt;
&lt;/ul&gt;
as well as a couple odd-balls like a
&lt;a href="https://blog.thechases.com/posts/remind/#manpages"&gt;&lt;code&gt;man&lt;/code&gt;-page-per-day&lt;/a&gt; and
a schedule of when the Cowboys play
(I don't care much about football,
but Dunkin offers a free medium coffee
on the day following a Cowboys win
so my calendar consists of reminders for the day
&lt;em&gt;following&lt;/em&gt;
a game to check the score).
I also have a couple calendars
consisting only of
&lt;code&gt;INCLUDE&lt;/code&gt;
statements to combine other calendars:
&lt;ul&gt;
 &lt;li&gt;
   one for the kids
  (combining both kids' personal calendars
  and their school calendars)
 &lt;/li&gt;
 &lt;li&gt;
  one for the whole family
  (combining my calendar
  and my wife's calendar
  with the combined kids' calendar)
 &lt;/li&gt;
 &lt;li&gt;
  and of course the master
  &lt;code&gt;reminders.rem&lt;/code&gt;
  that contains everything
 &lt;/li&gt;
&lt;/ul&gt;


&lt;aside&gt;
To save a bit of processing time,
I wrap my
&lt;code&gt;helpers.rem&lt;/code&gt;
in a guard so that it only gets processed once:
&lt;figure&gt;
&lt;pre&gt;
IF !defined("HaveHelpers")
SET HaveHelpers 1
# make sure this IF guard gets closed at the bottom
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;span class="comment"&gt;# all my helpers go here&lt;/span&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
ENDIF # HaveHelpers
&lt;/pre&gt;
&lt;figcaption&gt;helpers.rem&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/aside&gt;

&lt;p&gt;
At first glance,
splitting out each file doesn't seem to offer any advantages.
However, this lets you do things like
show a calendar of birthdays only
without all the other reminders,
or show just a school calendar.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;remind -c ~/.config/remind/birthdays.rem
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
The majority of my
&lt;code&gt;ushol.rem&lt;/code&gt;
file comes from the
&lt;a href="https://www.roaringpenguin.com/wiki/index.php/Defs.rem"&gt;examples on the
&lt;code&gt;remind&lt;/code&gt;
website&lt;/a&gt;.
It creates a bunch of
&lt;a href="https://blog.thechases.com/posts/remind/#omit"&gt;&lt;code&gt;OMIT&lt;/code&gt;&lt;/a&gt;
entries useful
for any reminders
that get bumped because of US holidays.
&lt;/p&gt;

&lt;h2 id="advanced"&gt;Advanced features&lt;/h2&gt;

&lt;p&gt;
While all that is useful,
other calendar programs like
&lt;a href="https://calendar.google.com"&gt;Google Calendar&lt;/a&gt;,
&lt;a href="https://outlook.com"&gt;Microsoft Outlook&lt;/a&gt;,
or even the venerable
&lt;a href="https://man.openbsd.org/calendar.1"&gt;&lt;code&gt;calendar(1)&lt;/code&gt;&lt;/a&gt;
CLI utility
can do many of those things.
However
&lt;code&gt;remind&lt;/code&gt;
really shines when you want to do things
that these others simply can't.
&lt;/p&gt;

 &lt;h3 id="expressions"&gt;Expression evaluation&lt;/h3&gt;

&lt;p&gt;
For even more power &amp;amp; flexibility,
&lt;code&gt;remind&lt;/code&gt;
lets you
define variables &amp;amp; functions
as well as
evaluate expressions
(documented in the man-pages as
"&lt;cite&gt;EXPRESSION PASTING&lt;/cite&gt;").
&lt;/p&gt;

&lt;p&gt;
To set a variable, use
&lt;code&gt;SET&lt;/code&gt;
and
&lt;code&gt;remind&lt;/code&gt;
will evaluate the right-hand side
(the
"&lt;code&gt;4 - 3&lt;/code&gt;"
in the example),
assigning the result
to the variable on the left-hand side
(the
&lt;code&gt;myvar&lt;/code&gt;
in the example):
&lt;figure&gt;
&lt;pre&gt;
&lt;mark&gt;SET&lt;/mark&gt; myvar 4 - 3
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
To create a function, use
&lt;code&gt;FSET&lt;/code&gt;.
The example below creates a function named
&lt;code&gt;add_three&lt;/code&gt;
that takes one parameter named
&lt;code&gt;x&lt;/code&gt;.
When called,
it adds three to the number passed to it
and returns the result:
&lt;figure&gt;
&lt;pre&gt;
&lt;mark&gt;FSET&lt;/mark&gt; add_three(x) 3 + x
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
To evaluate an expression
put it in square brackets
(all of these produce the same output)
&lt;figure&gt;
&lt;pre&gt;
REM Jan 1 2020 +1 MSG 4 - 3 = &lt;mark&gt;[4 - 3]&lt;/mark&gt;
REM Jan 1 2020 +1 MSG 4 - 3 = &lt;mark&gt;[myvar]&lt;/mark&gt;
REM Jan 1 2020 +1 MSG 4 - 3 = &lt;mark&gt;[add_three(-2)]&lt;/mark&gt;
REM Jan &lt;mark&gt;[4 - 3]&lt;/mark&gt; 2020 +1 MSG 4 - 3 = 1
REM Jan 1 &lt;mark&gt;[add_three(2017)]&lt;/mark&gt; +1 MSG 4 - 3 = 1
REM Jan 1 2020 +&lt;mark&gt;[myvar]&lt;/mark&gt; MSG 4 - 3 = 1
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Note that you can use
an expression anywhere that a
number,
string,
or date/time
can appear.
This lets you do things like
&lt;figure&gt;
&lt;pre&gt;
&lt;mark&gt;SET VacationStart date(2020, 7, 15)
SET VacationEnd VacationStart + 10&lt;/mark&gt;
REM &lt;mark&gt;[VacationStart - 60]&lt;/mark&gt; OMIT Sat Sun BEFORE \
  MSG Submit time-off paperwork
REM &lt;mark&gt;[VacationStart - 50]&lt;/mark&gt; MSG Buy tickets
REM &lt;mark&gt;[VacationStart - 3]&lt;/mark&gt; OMIT Sun BEFORE MSG Stop mail delivery
REM &lt;mark&gt;[VacationStart - 1]&lt;/mark&gt; MSG Pack bags
REM &lt;mark&gt;[VacationStart]&lt;/mark&gt; THROUGH &lt;mark&gt;[VacationEnd]&lt;/mark&gt; MSG Vacation
REM &lt;mark&gt;[VacationEnd + 1]&lt;/mark&gt; OMIT Sat Sun AFTER MSG Back to work
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Which lets you easily change
the vacation start-date
and the length of the vacation,
automatically adjusting the items
relative to those dates
(even moving relevant messages
off of weekends thanks to
&lt;a href="https://blog.thechases.com/posts/remind/#omit"&gt;&lt;code&gt;OMIT&lt;/code&gt;&lt;/a&gt;
functionality).
&lt;/p&gt;

 &lt;h3 id="functions"&gt;Using functions in reminder text&lt;/h3&gt;

&lt;p&gt;
If I know the birthday or anniversary of a friend,
I like to include that in the reminder text.
So in my
&lt;code&gt;helpers.rem&lt;/code&gt;
I have
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
FSET &lt;mark&gt;born&lt;/mark&gt;(y) "(" + ($Uy-y) + "yo)"
FSET &lt;mark&gt;married&lt;/mark&gt;(y) (ord($Uy-y)) + " anniversary"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;helpers.rem&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside&gt;
The
&lt;code&gt;$Uy&lt;/code&gt;
holds the year of the currently-rendering event
whether today,
the date passed on the command line,
one of the subsequent days
in a multi-day agenda view,
or on a
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;week/month calendar view&lt;/a&gt;.
So by subtracting the
birthday/anniversary year
from the current-event's date,
you get which birthday/anniversary.
The
&lt;code&gt;ord()&lt;/code&gt;
function turns a number
into its ordinal representation
("1" → "1st",
 "2" → "2nd",
 "3" → "3rd",
 "4" → "4th",
 …)
&lt;/aside&gt;
This lets me cleanly annotate things like
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
REM Apr 1 +7 MSG %"Bob &amp;amp; Alice &lt;mark&gt;[married(1999)]&lt;/mark&gt;%" %b
&lt;label class="visual"&gt;⋮&lt;/label&gt;
REM Jan 14 +7 MSG %"Bob &lt;mark&gt;[born(1980)]&lt;/mark&gt;%" %b
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
which produces output like
&lt;figure&gt;
&lt;pre class="output"&gt;
Bob &amp;amp; Alice &lt;mark&gt;20th anniversary&lt;/mark&gt; tomorrow
&lt;label class="visual"&gt;⋮&lt;/label&gt;
Bob &lt;mark&gt;(40yo)&lt;/mark&gt; in 3 days' time
&lt;/pre&gt;
&lt;/figure&gt;
(ignore the date discrepancies)
&lt;/p&gt;

 &lt;h3 id="days-until"&gt;Days until an annual event&lt;/h3&gt;

&lt;p&gt;
As an example of function use,
say you want to determine the number of days
until an annual event.
If it has already passed this year,
you want to refer to the event next year;
if it hasn't already passed this year,
you want to refer to the event this year.
A function wraps up this functionality nicely:
&lt;figure&gt;
&lt;pre&gt;
FSET DaysUntil(dt, m, d) iif(\
    date(year(dt), m, d) &amp;lt; dt,\
    date(year(dt)+1, m, d),\
    date(year(dt), m, d)\
    ) - dt
FSET DaysTillStr(dt, m, d) \
    DaysUntil(dt, m, d) + \
    plural(DaysUntil(dt, m, d), " day")
FSET ChristmasDays(dt) DaysTillStr(dt, 12, 25)
MSG %"[ChristmasDays($T)] until Christmas%"%
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
When run, this will produce output
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem 2020-12-1
Reminders for Tuesday, 1st December, 2020:

24 days until Christmas
&lt;label class="user"&gt;user$ &lt;/label&gt;rem 2020-12-28
Reminders for Monday, 28th December, 2020:

362 days until Christmas
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="priority"&gt;Specifying the priority of a reminder&lt;/h3&gt;

&lt;p&gt;
While I don't currently use task priority much,
&lt;code&gt;remind&lt;/code&gt;
allows you to provide a
&lt;code&gt;PRIORITY&lt;/code&gt;
modifier (an integer between 0 and 9999)
for an event.
If unspecified,
&lt;code&gt;remind&lt;/code&gt;
sets the priority to
&lt;code&gt;$DefaultPrio&lt;/code&gt;
(5000 by default).
As far as I can tell,
there's no inherent meaning to the priority,
so if it makes more sense to you that
9999 means "high priority" and
0 means "low priority",
then use them as such;
if it makes more sense to you that
0 means "high priority" and
9999 means "low priority",
use that.
I tend to declare a couple constants using
&lt;code&gt;SET&lt;/code&gt;
and then use those,
making it easier to reverse the meaning
should I need to.
&lt;figure&gt;
&lt;pre&gt;
SET &lt;mark&gt;PRIORITY_HIGH&lt;/mark&gt; 9999
SET &lt;mark&gt;PRIORITY_LOW&lt;/mark&gt; 0
REM Jan 1 2020 \
    AT 10:00 DURATION 1:00 \
    &lt;mark&gt;PRIORITY [PRIORITY_HIGH]&lt;/mark&gt; \
    MSG Very important meeting
REM Jan 1 2020 \
    AT 14:00 DURATION 1:00 \
    &lt;mark&gt;PRIORITY [PRIORITY_LOW]&lt;/mark&gt; \
    MSG Unimportant thing
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="msgprefix"&gt;Adding a prefix/suffix to agenda items&lt;/h3&gt;

&lt;p&gt;
&lt;code&gt;remind&lt;/code&gt;
provides a pair of special functions,
&lt;code&gt;msgprefix(p)&lt;/code&gt;
and
&lt;code&gt;msgsuffix(p)&lt;/code&gt;,
that emit a prefix &amp;amp; suffix
around each agenda item.
These functions take an explicit
&lt;a href="https://blog.thechases.com/posts/remind/#priority"&gt;priority&lt;/a&gt;
parameter
(although you can reference other variables and functions)
allowing you to do things like
&lt;figure&gt;
&lt;pre&gt;
# Annotate high-vs-low priority
FSET &lt;mark&gt;is_low_prio&lt;/mark&gt;(p) p &amp;lt; $DefaultPrio
FSET &lt;mark&gt;is_high_prio&lt;/mark&gt;(p) p &amp;gt; $DefaultPrio

FSET &lt;mark&gt;msgprefix&lt;/mark&gt;(p) \
    iif(&lt;mark&gt;is_high_prio&lt;/mark&gt;(p), "+ ", \
    iif(&lt;mark&gt;is_low_prio&lt;/mark&gt;(p), "- ", \
    "  "))

# add some asterisks after high-priority tasks
FSET &lt;mark&gt;msgsuffix&lt;/mark&gt;(p) \
    iif(&lt;mark&gt;is_high_prio&lt;/mark&gt;(p), " *****", "")
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
(assuming higher numbers mean higher priority;
otherwise, adjust the
&lt;code&gt;is_low_prio&lt;/code&gt;
and
&lt;code&gt;is_high_prio&lt;/code&gt;
functions accordingly)
or, using some of the
&lt;a href="https://blog.thechases.com/posts/remind/#ansi"&gt;tricks from the section on color&lt;/a&gt;
&lt;figure&gt;
&lt;pre&gt;
FSET msgprefix(p) \
    iif(is_high_prio(p), &lt;mark&gt;Red&lt;/mark&gt;, \
    iif(is_low_prio(p), &lt;mark&gt;Gry&lt;/mark&gt;, \
    &lt;mark&gt;Nrm&lt;/mark&gt;))
FSET msgsuffix(p) &lt;mark&gt;Nrm&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="omit"&gt;Omitting and shifting dates&lt;/h3&gt;

&lt;p&gt;
Some events shift around
or get cancelled
based on whether they fall
on particular days like holidays,
weekends,
or even just for arbitrary reasons
("somebody else booked the conference room
so we need to move it to next week").
The
&lt;code&gt;OMIT&lt;/code&gt;
keyword tells
&lt;code&gt;remind&lt;/code&gt;
how to identify these cases.
&lt;/p&gt;

  &lt;h4 id="omit-single"&gt;Omitting/shifting for a single event&lt;/h4&gt;

&lt;p&gt;
You might have a project at work
that runs for a couple weeks
but you don't want your agenda
to display it on weekends
because you have work/life boundaries.
Use the
&lt;code&gt;OMIT&lt;/code&gt;
keyword to tell which days, and
&lt;code&gt;SKIP&lt;/code&gt;
to tell
&lt;code&gt;remind&lt;/code&gt;
to ignore events that fall on these days.
Put them in your
&lt;code&gt;REM&lt;/code&gt;
command:
&lt;figure&gt;
&lt;pre&gt;
REM Jan 2 2020 THROUGH Jan 15 2020 \
    &lt;mark&gt;OMIT Sat Sun SKIP&lt;/mark&gt; \
    MSG Work project
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
In another case,
your employer might pay you
on the 15&lt;sup&gt;th&lt;/sup&gt;
of each month,
but if pay-day falls on a weekend,
payment moves
&lt;code&gt;AFTER&lt;/code&gt;
the weekend
to the following Monday:
&lt;figure&gt;
&lt;pre&gt;
REM 15 OMIT Sat Sun &lt;mark&gt;AFTER&lt;/mark&gt; MSG Payday
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Or you might have a bill
that you need to pay by the 15&lt;sup&gt;th&lt;/sup&gt;
but if that falls on a weekend
you must pay it
&lt;code&gt;BEFORE&lt;/code&gt;
the weekend
so that payment arrives on time:
&lt;figure&gt;
&lt;pre&gt;
REM 15 OMIT Sat Sun &lt;mark&gt;BEFORE&lt;/mark&gt; MSG Pay water bill
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

  &lt;h4 id="omit-multiple"&gt;Omitting/shifting for multiple events&lt;/h4&gt;

&lt;p&gt;
Certain events such as federal holidays
can displace multiple reminders.
Rather than try and
&lt;code&gt;OMIT&lt;/code&gt;
every single holiday
in every single reminder,
&lt;code&gt;remind&lt;/code&gt;
provides bare
&lt;code&gt;OMIT&lt;/code&gt;
entries similar to
&lt;code&gt;REM&lt;/code&gt;
but they get added to a
&lt;a href="https://blog.thechases.com/posts/remind/#omit-push-pop"&gt;contextual&lt;/a&gt;
omit-list.
If you have important weekly meetings
on Monday and Friday
and an unimportant meeting on Wednesday,
the following will bump the important Monday meeting
back to Tuesday if Christmas fell on Monday,
will bump the important Friday meeting back to Thursday
if Christmas fell on a Friday,
and cancel the unimportant Wednesday meeting
if Christmas fell on a Wednesday,
&lt;figure&gt;
&lt;pre&gt;
&lt;mark&gt;OMIT Jul 4&lt;/mark&gt; MSG 4th of July
&lt;mark&gt;OMIT Dec 25&lt;/mark&gt; MSG Christmas
REM Mon &lt;mark&gt;AFTER&lt;/mark&gt; MSG Important Monday Meeting
REM Wed &lt;mark&gt;SKIP&lt;/mark&gt; MSG Unimportant Wednesday meeting
REM Fri &lt;mark&gt;BEFORE&lt;/mark&gt; MSG Important Friday Meeting
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
In 2015, Christmas fell on a Friday,
in 2017, Christmas fell on a Monday,
and in 2019, Christmas fell on a Wednesday.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem 2015-12-24 '*2' &lt;span&gt;# got moved to 24th&lt;/span&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem 2017-12-25 '*2' &lt;span&gt;# got moved to 26th&lt;/span&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem 2019-12-24 '*3' &lt;span&gt;# got cancelled&lt;/span&gt;
&lt;/pre&gt;
&lt;/figure&gt;
The first command shows
that the Friday meeting
got pushed back to Thursday,
while the second command shows
that the important Monday meeting
got pushed forward to Tuesday
and the unimportant Wednesday meeting
simply vanished.
The same thing happens
around July 4&lt;sup&gt;th&lt;/sup&gt;
since we have multiple
&lt;code&gt;OMIT&lt;/code&gt;
entries.
&lt;/p&gt;

  &lt;h4 id="omit-notice"&gt;Omit notice and adjusting backwards&lt;/h4&gt;

&lt;p&gt;
As discussed in the
&lt;a href="https://blog.thechases.com/posts/remind/#notice"&gt;advanced-notice&lt;/a&gt;
and
&lt;a href="https://blog.thechases.com/posts/remind/#adjusting"&gt;adjusting-dates&lt;/a&gt;
sections,
&lt;code&gt;remind&lt;/code&gt;
can give you advanced notice of reminders
and adjust dates backwards a certain number of days.
In that section,
I recommended using the double
&lt;code&gt;++&lt;/code&gt;/&lt;code&gt;--&lt;/code&gt;
because they ignore
&lt;code&gt;OMIT&lt;/code&gt;
dates, making them easier to understand.
However sometimes you want extra days' notice
if intervening
&lt;code&gt;OMIT&lt;/code&gt;
holidays happens
(such as closing the bank or post-office)
in which case
you should use the single
&lt;code&gt;+&lt;/code&gt;
form.
Likewise, use
&lt;code&gt;-&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
to find the last day of the month,
but you want it to skip over
&lt;code&gt;OMIT&lt;/code&gt;
dates.
&lt;figure&gt;
&lt;pre&gt;
OMIT Dec 25 MSG Christmas
REM Dec 26 &lt;mark&gt;+1&lt;/mark&gt; MSG shows on the 24th, 25th, and 26th
REM Dec 26 &lt;mark&gt;++1&lt;/mark&gt; MSG shows on the 25th and 26th but not 24th
REM Jan 1 &lt;mark&gt;-7&lt;/mark&gt; MSG shows on the 24th
REM Jan 1 &lt;mark&gt;--7&lt;/mark&gt; MSG shows on the 25th
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

  &lt;h4 id="omit-push-pop"&gt;Omit contexts&lt;/h4&gt;

&lt;p&gt;
Because not all places or calendars
use the same list of holidays
to determine
&lt;code&gt;OMIT&lt;/code&gt;
days,
&lt;code&gt;remind&lt;/code&gt;
lets you create custom
&lt;code&gt;OMIT&lt;/code&gt;
contexts with
&lt;code&gt;PUSH&lt;/code&gt;
(ending those temporary contexts with
&lt;code&gt;POP&lt;/code&gt;)
and optionally use
&lt;code&gt;CLEAR&lt;/code&gt;
to temporarily remove previous
&lt;code&gt;OMIT&lt;/code&gt;
dates.
As an example,
our kids' schools close for (most) federal holidays
but each has its own list of other days off
such as in-service days
and bad-weather make-up days.
&lt;figure&gt;
&lt;pre&gt;
# our son's school has certain days off
&lt;mark&gt;PUSH&lt;/mark&gt;
OMIT Apr 27 2020 MSG %"In service day%" \
  No school for son
OMIT May 22 2020 MSG %"Bad-weather make-up day%" \
  No school for son
REM Mon Tue Wed Thu Fri \
  FROM Aug 14 2019 \
  UNTIL Jun 1 2020 \
  SKIP \
  MSG %"Son's school%"
&lt;mark&gt;POP&lt;/mark&gt;
# our daughter's school has other days off
&lt;mark&gt;PUSH&lt;/mark&gt;
OMIT Apr 13 2020 MSG %"In service day%" \
  No school for daughter
OMIT May 8 2020 MSG %"Bad-weather make-up day%" \
  No school for daughter
REM Mon Tue Wed Thu Fri \
  FROM Aug 14 2019 \
  UNTIL Jun 1 2020 \
  SKIP \
  MSG %"Daughter's school%"
&lt;mark&gt;POP&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
For some calendars,
I want to totally ignore the global holiday list
and use custom
&lt;code&gt;OMIT&lt;/code&gt;
lists instead.
For example,
a group of guys get together for breakfast
on the first Saturday of each month.
But a couple times
someone else has booked our venue,
pushing it back a week.
&lt;figure&gt;
&lt;pre&gt;
PUSH
&lt;mark&gt;CLEAR&lt;/mark&gt;
OMIT 5 Oct 2019 MSG %"No breakfast get-together%" \
  (venue unavailable)
REM Sat 1 +1 AT 07:45 DURATION 1:30 \
  OMIT &lt;mark&gt;Sun Mon Tue Wed Thur Fri&lt;/mark&gt; AFTER \
  FROM Aug 31 2019 \
  MSG %"Breakfast get-together%" %b %2
POP
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
A couple things to notice:
&lt;/p&gt;&lt;ol&gt;
 &lt;li&gt;
  the
  &lt;code&gt;CLEAR&lt;/code&gt;
  inside the
  &lt;code&gt;PUSH&lt;/code&gt;/&lt;code&gt;POP&lt;/code&gt;
  block means that my global
  &lt;code&gt;OMIT&lt;/code&gt;
  list of holidays
  doesn't impact the breakfast dates
 &lt;/li&gt;
 &lt;li&gt;
  if future events bump the event
  I only need to add them to the
  &lt;code&gt;OMIT&lt;/code&gt;
  list at the top
 &lt;/li&gt;
 &lt;li&gt;
  by having a local
  &lt;code&gt;OMIT&lt;/code&gt;
  list as well
  (consisting of the non-Saturdays)
  the
  &lt;code&gt;AFTER&lt;/code&gt;
  postpones to the following Saturday
  rather than pushing it to the next day
  (Sunday)
 &lt;/li&gt;
&lt;/ol&gt;
&lt;aside&gt;
In reality, I also use
&lt;code&gt;CLEAR&lt;/code&gt;
with the kids' school calendars too
because not every federally-recognized holiday
closes the schools
so I find it easiest to
just hard-code all the days-off
for each school year.
&lt;/aside&gt;


&lt;p&gt;
Frankly, as a guideline,
I lean towards putting
&lt;em&gt;every&lt;/em&gt;
&lt;code&gt;OMIT&lt;/code&gt;
inside its own
&lt;code&gt;PUSH&lt;/code&gt;/&lt;code&gt;CLEAR&lt;/code&gt;/&lt;code&gt;POP&lt;/code&gt;
block.
&lt;/p&gt;

  &lt;h4 id="omit-function"&gt;Omit function&lt;/h4&gt;

&lt;p&gt;
On occasion you might find it easier
to describe the days you want to
&lt;code&gt;OMIT&lt;/code&gt;
with a function.
&lt;code&gt;remind&lt;/code&gt;
lets you create a such a function
that takes a date as the only argument,
and returns 0
if it should consider the date,
or returns non-0 if
it should treat the date as omitted.
Use the
&lt;code&gt;OMITFUNC&lt;/code&gt;
modifier to tell
&lt;code&gt;remind&lt;/code&gt;
to ignore the local
and global
&lt;code&gt;OMIT&lt;/code&gt;s
and use the
&lt;code&gt;OMITFUNC&lt;/code&gt;
instead.
&lt;figure&gt;
&lt;pre&gt;
FSET &lt;mark&gt;myomit&lt;/mark&gt;(d) …
REM &lt;mark&gt;OMITFUNC myomit&lt;/mark&gt; MSG Some event
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
If you want to take the global
&lt;code&gt;OMIT&lt;/code&gt;
list into consideration
or omit certain days of the week
like you might with a local
&lt;code&gt;OMIT&lt;/code&gt;,
use the
&lt;code&gt;isomitted()&lt;/code&gt;
function in your
&lt;code&gt;OMITFUNC&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
FSET near_a_new_moon(d) \
  moonphase(d) &amp;lt; 20 || \
  moonphase(d) &amp;gt; 340
FSET allmyomits(d) \
  &lt;mark&gt;wkdaynum(d) == 0&lt;/mark&gt; || \
  &lt;mark&gt;wkdaynum(d) == 6&lt;/mark&gt; || \
  &lt;mark&gt;isomitted(d)&lt;/mark&gt; || \
  near_a_new_moon(d)

REM OMITFUNC allmyomits AFTER \
  MSF This doesn't happen \
  on weekends, \
  OMIT dates, \
  or around a new moon.
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
(the
&lt;code&gt;moonphase()&lt;/code&gt;
function takes a date
and returns a number from 0 to 359
with 0="new moon"
and 180="full moon",
so the
&lt;code&gt;near_a_new_moon&lt;/code&gt;
function returns true
within ~20° on either side
of a new moon)
&lt;/p&gt;

  &lt;h4 id="omit-weekdays"&gt;Using
  &lt;code&gt;OMIT&lt;/code&gt;
  for more complex dates&lt;/h4&gt;

&lt;p&gt;
In certain cases
you might want to create an
&lt;code&gt;OMIT&lt;/code&gt;
that falls on certain days
that otherwise work with regular reminders.
However if you try to do something like
&lt;figure&gt;
&lt;pre&gt;
OMIT &lt;mark&gt;Mon&lt;/mark&gt; Feb 15 MSG President's Day
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;code&gt;remind&lt;/code&gt;
considers the "Mon" part of the
&lt;code&gt;OMIT&lt;/code&gt;
a syntax error.
To get around this,
create it as a regular reminder first,
then use the resulting
&lt;code&gt;trigdate()&lt;/code&gt;
to create the
&lt;code&gt;OMIT&lt;/code&gt;:
&lt;figure&gt;
&lt;pre&gt;
REM Mon Feb 15 MSG President's Day
OMIT [trigdate()]
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
However, note that
&lt;a href="https://blog.thechases.com/posts/remind/#scanfrom"&gt;this might have surprising side-effects
if you don't take care where you start your search&lt;/a&gt;.
&lt;/p&gt;

 &lt;h3 id="satisfy"&gt;Using
  &lt;code&gt;SATISFY&lt;/code&gt;
  to conditionally test events
  &lt;/h3&gt;

&lt;p&gt;
Sometimes you want
&lt;code&gt;remind&lt;/code&gt;
to perform additional tests on a date
to determine whether an event should occur,
and if not,
continue seeking the next matching date
and test that.
The
&lt;code&gt;SATISFY&lt;/code&gt;
clause lets you
&lt;a href="https://blog.thechases.com/posts/remind/#expressions"&gt;evaluate an expression&lt;/a&gt;
to do just that.
You might only want something to happen every N&lt;sup&gt;th&lt;/sup&gt; month
or test that something only occurs on certain days.
As previously suggested
&lt;a href="https://blog.thechases.com/posts/remind/#your-first-remind-file"&gt;when discussing the N&lt;sup&gt;th&lt;/sup&gt; weekday of a month&lt;/a&gt;
you might need to check that the date
falls in the same month.
&lt;figure&gt;
&lt;pre&gt;
REM 1 &lt;mark&gt;SATISFY [$Tm % 3 == 1]&lt;/mark&gt; \
  MSG The first of every 3rd month starting January
REM 1 &lt;mark&gt;SATISFY [$Tm % 3 == 2]&lt;/mark&gt; \
  MSG The first of every 3rd month starting February
REM 1 &lt;mark&gt;SATISFY [$Tm % 3 == 0]&lt;/mark&gt; \
  MSG The first of every 3rd month starting March

REM MAYBE-UNCOMPUTABLE \
  &lt;mark&gt;SATISFY [$U == realtoday()]&lt;/mark&gt; \
  MSG Only appears today
REM &lt;mark&gt;[realtoday()]&lt;/mark&gt; \
  MSG Also only appears today

REM 13 &lt;mark&gt;SATISFY [$Uw == 5]&lt;/mark&gt; MSG Friday the 13th

REM Mon 29 &lt;mark&gt;SATISFY [monnum($T - 7) = $Tm]&lt;/mark&gt; \
  MSG Infrequent Fifth Monday of the month

PUSH
CLEAR
&lt;label class="visual"&gt;⋮&lt;/label&gt;
OMIT Dec 23 2019 THROUGH Jan 6 2020 \
  &lt;mark&gt;SATISFY [$Uw == 1]&lt;/mark&gt; \
  MSG %"No club%"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
REM Mon \
  FROM Aug 1 2019 \
  UNTIL May 31 2020 \
  OMIT SKIP \
  MSG Club
POP
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;
  The first three reminders
  trigger on the first of each month.
  However
  &lt;code&gt;remind&lt;/code&gt;
  will test the month-number
  (&lt;code&gt;$Um&lt;/code&gt;),
  dividing it by 3
  and getting the remainder
  ("&lt;code&gt;%&lt;/code&gt;" does division
  and results in the modulus/remainder).
  If the remainder is 1,
  we have January/April/July/October;
  if the remainder is 2,
  we have February/May/August/November;
  if the remainder is 0,
  we have March/June/September/December.
 &lt;/li&gt;
 &lt;li&gt;
  The second two reminders test
  if the currently-considered date
  (&lt;code&gt;today()&lt;/code&gt;,
  the same as
  &lt;code&gt;$U&lt;/code&gt;)
  matches the real-world date
  (&lt;code&gt;realtoday()&lt;/code&gt;)
  and only displays the
  &lt;code&gt;MSG&lt;/code&gt;
  portion today.
 &lt;/li&gt;
 &lt;li&gt;
  To find Friday the 13&lt;sup&gt;th&lt;/sup&gt;,
  we can't use
  &lt;code&gt;REM Fri 13&lt;/code&gt;
  because this finds
  the first Friday on or after the 13&lt;sup&gt;th&lt;/sup&gt;
  of each month.
  That might or might not
  coincide with the 13&lt;sup&gt;th&lt;/sup&gt;.
  Instead, we find all the 13&lt;sup&gt;th&lt;/sup&gt;s
  and then use the
  &lt;code&gt;SATISFY&lt;/code&gt;
  clause to reject those
  that don't fall on a Friday
  (0=Sunday, …, 5=Friday, 6=Saturday)
 &lt;/li&gt;
 &lt;li&gt;
  To find the 5&lt;sup&gt;th&lt;/sup&gt; Monday
  we need to use the same procedure
  for finding the other N&lt;sup&gt;th&lt;/sup&gt;
  weekdays of the month
  (&lt;code&gt;Mon 29&lt;/code&gt;)
  but then check that the month
  of the resulting date
  (&lt;code&gt;$Um&lt;/code&gt;)
  falls in the same month
  (&lt;code&gt;monnum()&lt;/code&gt;)
  as the date seven days before
  (&lt;code&gt;-7&lt;/code&gt;)
  today's date
  (&lt;code&gt;$U&lt;/code&gt;,
  the date currently under consideration).
 &lt;/li&gt;
 &lt;li&gt;
  In the final pair of entries,
  I have a club meeting
  every Monday night
  from August through May.
  However, club takes off during Christmas break
  (and some other days I've not included here),
  so the
  &lt;code&gt;OMIT&lt;/code&gt;
  takes care of this.
  However, I don't want notifications
  on
  &lt;em&gt;every&lt;/em&gt;
  day during the break
  telling me not to attend club.
  I only want notifications
  on the Mondays during the break.
  Using the
  &lt;code&gt;SATISFY&lt;/code&gt;
  clause lets me limit these
  so they only fall on Mondays
  (&lt;code&gt;$Uw == 1&lt;/code&gt;).
 &lt;/li&gt;
&lt;/ul&gt;


 &lt;h3 id="warn-and-sched"&gt;Fine-grained scheduling&lt;/h3&gt;

&lt;p&gt;
Sometimes you want
&lt;a href="https://blog.thechases.com/posts/remind/#notice"&gt;advanced warning&lt;/a&gt;
of an event at certain intervals like
&lt;code&gt;++&lt;var&gt;n&lt;/var&gt;&lt;/code&gt;
provides,
but you don't want those warnings
on &lt;em&gt;every&lt;/em&gt; day during that interval.
The
&lt;code&gt;WARN&lt;/code&gt;
modifier lets you provide a function
that tells
&lt;code&gt;remind&lt;/code&gt;
which days you want reminders.
The function gets called repeatedly
first with 1,
then with 2,
etc.
The search stops if one of the following conditions occur:
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;
  &lt;code&gt;remind&lt;/code&gt;
  encounters the current number of days out
  for the next instance of this reminder
 &lt;/li&gt;
 &lt;li&gt;it encounters a zero (today)&lt;/li&gt;
 &lt;li&gt;
  the function results cease decreasing monotonically
  (they must continue decreasing,
  so "10, 5, 7, 0" will bail at 7
  because it went up from 5)
 &lt;/li&gt;
&lt;/ul&gt;
Most commonly, the
&lt;code&gt;choose()&lt;/code&gt;
function does this,
so to get reminders
30 days out,
10 days out,
5 days out,
and 2 days out:
&lt;figure&gt;
&lt;pre&gt;
FSET mywarnfunc(i) choose(i, 30, 10, 5, 2, 0)
REM Jan 1 2020 WARN mywarnfunc \
  MSG Get car inspected
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
When checking if
&lt;code&gt;remind&lt;/code&gt;
should produce a reminder for today,
it starts by calling this
&lt;code&gt;mywarnfunc&lt;/code&gt;
function with a parameter of 1.
The
&lt;code&gt;choose()&lt;/code&gt;
function returns 30,
so if January 1&lt;sup&gt;st&lt;/sup&gt;, 2020
is 30 days from the current date in consideration,
it will remind.
If not,
&lt;code&gt;remind&lt;/code&gt;
calls
&lt;code&gt;mywarnfunc&lt;/code&gt;
with a parameter of 2.
This time, the
&lt;code&gt;choose()&lt;/code&gt;
returns 10.
Again if January 1&lt;sup&gt;st&lt;/sup&gt;, 2020
is 10 days from the current date,
you will get the reminder.
Same with 5 and 2 days out.
Finally, if none of the results
match,
&lt;code&gt;remind&lt;/code&gt;
tests if the date in question
is January 1&lt;sup&gt;st&lt;/sup&gt;, 2020.
If you want to skip
&lt;code&gt;OMIT&lt;/code&gt;
days, return a negative number.
The check for monotonically decreasing numbers
only tests the absolute value so a function like
&lt;figure&gt;
&lt;pre&gt;
FSET &lt;mark&gt;mywarnfunc(i)&lt;/mark&gt; choose(i, 30, -10, 5, -2, 0)
REM Jan 1 2020 \
  &lt;mark&gt;WARN mywarnfunc&lt;/mark&gt; \
  MSG Get car inspected
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
works and will warn
30 days out,
10 days out
(skipping over
&lt;code&gt;OMIT&lt;/code&gt;
days),
5 days out,
and 2 days out
(also skipping over
&lt;code&gt;OMIT&lt;/code&gt;
days).
&lt;code&gt;remind&lt;/code&gt;
provides a similar
&lt;code&gt;SCHED&lt;/code&gt;
functionality for fine-grained
lead time for
&lt;code&gt;AT&lt;/code&gt;-style
reminders,
notifying at a series of times:
&lt;figure&gt;
&lt;pre&gt;
FSET &lt;mark&gt;mynotice(i)&lt;/mark&gt; choose(i, 4*60, 2*60, 30, 0)
REM January 1 2020 AT 10:00 \
  &lt;mark&gt;SCHED mynotice&lt;/mark&gt; \
  MSG Doctor
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
to remind you at
4 hours out,
2 hours out,
30 minutes out,
and at the time of your appointment.
You can read more under
&lt;cite&gt;PRECISE SCHEDULING&lt;/cite&gt;
in the
&lt;code&gt;man&lt;/code&gt;-pages.


&lt;h2 id="tricks"&gt;Miscellaneous tricks&lt;/h2&gt;

&lt;p&gt;
As I've built up my
&lt;code&gt;remind&lt;/code&gt;
files, I've developed a collection
of tricks and tips
that I use for various scenarios.
&lt;/p&gt;

 &lt;h3 id="labels"&gt;Labels&lt;/h3&gt;

&lt;p&gt;
Because I have
&lt;a href="https://blog.thechases.com/posts/remind/#organizing-reminders"&gt;a file for each type of reminder&lt;/a&gt;,
I find it handy to prefix each item with its filename
using the special
&lt;code&gt;msgprefix(x)&lt;/code&gt;
function
(which I also
&lt;a href="https://blog.thechases.com/posts/remind/#ansi"&gt;use for colorizing agenda reminders&lt;/a&gt;):
&lt;figure&gt;
&lt;pre&gt;
# fileprefix() reduces the current
# path+filename+extension
# to just the filename
# /path/to/file.rem -&amp;gt; "FILE"
FSET fileprefix() upper( \
  substr( \
    filename(), \
    strlen(remind_dir)+1, \
      strlen(filename())-4 \
      ) \
  )
FSET msgprefix(x) fileprefix() + ": "
&lt;/pre&gt;
&lt;figcaption&gt;reminders.rem&lt;/figcaption&gt;
&lt;/figure&gt;
This produces
&lt;figure&gt;
&lt;pre class="output"&gt;
Friday, 27th December, 2019 (today):
&lt;mark&gt;USHOL:&lt;/mark&gt; Chanukah 5
&lt;mark&gt;BIRTHDAYS:&lt;/mark&gt; David (10yo) in 3 days' time
&lt;mark&gt;BIRTHDAYS:&lt;/mark&gt; Annabelle (12yo)
&lt;mark&gt;BIRTHDAYS:&lt;/mark&gt; Nate today
&lt;mark&gt;MANPAGES:&lt;/mark&gt; man 1 ed
&lt;mark&gt;SCHOOL:&lt;/mark&gt; Christmas: No School
&lt;mark&gt;FINANCES:&lt;/mark&gt; Pay gas bill (today)
&lt;mark&gt;HOUSEHOLD:&lt;/mark&gt; Garbage day (Delayed) today
&lt;/pre&gt;
&lt;/figure&gt;
If you don't put your reminders
in separate files,
you could achieve the same effect
in a single file
with a variable and a function
that references it
as part of the
&lt;code&gt;msgprefix(x)&lt;/code&gt;
function:
&lt;figure&gt;
&lt;pre&gt;
SET topic ""
&lt;mark&gt;FSET msgprefix(x) iif(strlen(topic) &amp;gt; 1, topic + ": ", "")&lt;/mark&gt;
FSET born(y) "(" + ($Uy-y) + "yo)"
FSET married(y) (ord($Uy-y)) + " anniversary"
&lt;mark&gt;SET topic "Birthdays"&lt;/mark&gt;
REM Jan 1 MSG %"Mom%" [born(1950)]
REM Jan 6 MSG %"Dianne%" [born(1970)]
&lt;mark&gt;SET topic "Anniv"&lt;/mark&gt;
REM Apr 1 +7 MSG %"Bob &amp;amp; Alice [married(1999)]%" %b
&lt;mark&gt;SET topic "Work"&lt;/mark&gt;
REM Fri AT 7:30 MSG %"Meeting%" %2
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="color"&gt;Color&lt;/h3&gt;

&lt;p&gt;
As a rule,
I generally want plain-text reminders,
whether emailing them to myself
or displaying a calendar with
&lt;code&gt;rem -c&lt;/code&gt;.
But when at the command-line,
I like a bit of color.
&lt;/p&gt;

  &lt;h4 id="special-color"&gt;General per-reminder foreground color&lt;/h4&gt;

&lt;p&gt;
As one of the
&lt;code&gt;SPECIAL&lt;/code&gt;
codes,
&lt;code&gt;remind&lt;/code&gt;
offers
&lt;code&gt;COLOR&lt;/code&gt;
(or it will happily accept
"&lt;code&gt;COLOUR&lt;/code&gt;")
for reminders
that alters the color used
when generating a
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;week/month calendar with
&lt;code&gt;-c&lt;mark&gt;c&lt;/mark&gt;+&lt;/code&gt;
or
&lt;code&gt;-c&lt;mark&gt;c&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;.
The entry takes three numbers as arguments,
the
&lt;a href="https://www.google.com/search?q=color+picker"&gt;Red,
Green, and Blue values&lt;/a&gt;,
each ranging from 0 to 255,
followed directly by the
&lt;code&gt;MSG&lt;/code&gt;-style text
(without the
&lt;code&gt;MSG&lt;/code&gt;
keyword):
&lt;figure&gt;
&lt;pre&gt;
REM Jan 1 &lt;mark&gt;SPECIAL COLOR 255 0 255&lt;/mark&gt; \
  Happy New Year in bright magenta
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
When displayed on a traditional ANSI terminal
those get quantized into sixteen colors
(eight basic colors plus eight bold/bright versions of each).
&lt;/p&gt;

&lt;p&gt;
This makes it easy to colorize
a single reminder,
but it only works in week/month view
(or external apps that support it
via the
&lt;code&gt;-p&lt;/code&gt;
option),
not the agenda view
(unless you have version 3.3.0 or later
where the
&lt;code&gt;-@&lt;/code&gt;
option enables color in the agenda view).
However sometimes I want to colorize
whole swaths of reminders
and do it on the agenda view.
I want all my birthday
and anniversary reminders
in green,
my personal reminders in blue,
my wife's reminders in bright magenta,
the kids' reminders in cyan,
church/volunteering reminders in magenta,
work in yellow,
and some low-priority entries in gray.
I don't want to have to specify
the color for each and every one
of my 1,600+ reminders.
&lt;/p&gt;

  &lt;h4 id="ansi"&gt;Prior to the addition of
  &lt;code&gt;$DefaultColor&lt;/code&gt;
  in version 3.3.0
  &lt;/h4&gt;

&lt;p&gt;
To do this,
I first add a number of
&lt;a href="https://en.wikipedia.org/wiki/ANSI_escape_code" title="American National Standards Institute escape codes"&gt;ANSI escape codes&lt;/a&gt;
as constants to my
&lt;code&gt;helpers.rem&lt;/code&gt;
that I can then use elsewhere.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
# ANSI colors
SET Esc CHAR(27)
SET Nrm Esc + "[0m"
SET Blk Esc + "[0;30m"
SET Red Esc + "[0;31m"
SET Grn Esc + "[0;32m"
SET Ylw Esc + "[0;33m"
SET Blu Esc + "[0;34m"
SET Mag Esc + "[0;35m"
SET Cyn Esc + "[0;36m"
SET Wht Esc + "[0;37m"
SET Gry Esc + "[30;1m"
SET BrRed Esc + "[31;1m"
SET BrGrn Esc + "[32;1m"
SET BrYlw Esc + "[33;1m"
SET BrBlu Esc + "[34;1m"
SET BrMag Esc + "[35;1m"
SET BrCyn Esc + "[36;1m"
SET BrWht Esc + "[37;1m"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;helpers.rem&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;aside&gt;
If you wanted to get fancy,
you could even include the
&lt;abbr title="American National Standards Institute"&gt;ANSI&lt;/abbr&gt;
codes for background colors
or other more exotic 24-bit color escape codes.
&lt;/aside&gt;

&lt;p&gt;
Then, in my base
&lt;code&gt;reminders.rem&lt;/code&gt;
file,
I have the following:
&lt;figure&gt;
&lt;pre&gt;
IF defined("COLOR")
  SET bannercolor BrWht
  SET calcolor Nrm
  SET labelcolor Nrm
  # if desired, use the priority "x"
  # to change the prefix color
  FSET prefixcolor(x) Nrm
  FSET color() calcolor
  FSET &lt;mark&gt;msgsuffix(x)&lt;/mark&gt; Nrm
ELSE
  SET bannercolor ""
  SET calcolor ""
  SET labelcolor ""
  FSET prefixcolor(x) ""
  FSET color() ""
ENDIF

FSET &lt;mark&gt;msgprefix(x)&lt;/mark&gt; \
  prefixcolor(x) + \
  fileprefix(x) + \
  ": " + \
  color()

IF defined("subsequent_iteration")
  BANNER [labelcolor]=====================[bannercolor]%_%w, %d%s %m, %y%o:%
ELSE
  BANNER [bannercolor]%w, %d%s %m, %y%o:%
  SET subsequent_iteration 1
  PRESERVE subsequent_iteration
ENDIF
&lt;/pre&gt;
&lt;figcaption&gt;reminders.rem&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside&gt;
(the
&lt;a href="https://blog.thechases.com/posts/remind/#labels"&gt;section on labels&lt;/a&gt;
provides the
&lt;code&gt;fileprefix&lt;/code&gt;
function)
&lt;/aside&gt;
This checks if I've defined
&lt;code&gt;COLOR&lt;/code&gt;
on the command-line
and sets up functions
&amp;amp; constants
for default colors.
&lt;/p&gt;

&lt;p&gt;
I then set a color at the top of each of my files
after including the
&lt;code&gt;helpers.rem&lt;/code&gt;
and it changes the color of everything following,
allowing me to have multiple color blocks
in the same file
&lt;figure&gt;
&lt;pre&gt;
INCLUDE [filedir()]/helpers.rem
&lt;mark&gt;SET calcolor Ylw&lt;/mark&gt;
REM 1 MSG %"Send invoice%"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;work.rem&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;pre&gt;
INCLUDE [filedir()]/helpers.rem
&lt;mark&gt;SET calcolor BrBlu&lt;/mark&gt;
REM Jan 10 MSG %"Alice%"
REM Jan 14 +7 MSG %"Bob [born(1980)]%" %b
&lt;mark&gt;SET calcolor Blu&lt;/mark&gt;
REM Jan 6 MSG %"Dianne's birthday%"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;birthdays.rem&lt;/figcaption&gt;
&lt;/figure&gt;
The
&lt;code&gt;msgprefix()&lt;/code&gt;
and
&lt;code&gt;msgsuffix()&lt;/code&gt;
functions have special meaning to
&lt;code&gt;remind&lt;/code&gt;
allowing the above to
&lt;/p&gt;&lt;ol&gt;
 &lt;li&gt;set the prefix color for the labels&lt;/li&gt;
 &lt;li&gt;determine and display the file-name-based prefix&lt;/li&gt;
 &lt;li&gt;
  switch to whatever color
  &lt;code&gt;calcolor&lt;/code&gt;
  currently contains
 &lt;/li&gt;
 &lt;li&gt;display the reminder text&lt;/li&gt;
 &lt;li&gt;reset back to a normal color&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;
I can then use
&lt;code&gt;rem -iCOLOR=1&lt;/code&gt;
to get my results in color.
(ignore the fact that the events actually fall on different dates)
&lt;figure&gt;
&lt;pre class="output"&gt;
&lt;span class="prefixcolor"&gt;BIRTHDAYS:&lt;/span&gt; &lt;span class="rem_brblu"&gt;Alice&lt;/span&gt;
&lt;span class="prefixcolor"&gt;BIRTHDAYS:&lt;/span&gt; &lt;span class="rem_brblu"&gt;Bob (39yo)&lt;/span&gt;
&lt;span class="prefixcolor"&gt;BIRTHDAYS:&lt;/span&gt; &lt;span class="rem_blu"&gt;Dianne&lt;/span&gt;
&lt;span class="prefixcolor"&gt;WORK:&lt;/span&gt; &lt;span class="rem_ylw"&gt;Send invoice&lt;/span&gt;
&lt;/pre&gt;
&lt;/figure&gt;
These work wonderfully for the agenda view,
but unfortunately don't work for the week/month view
(attempting to hack it with the special
&lt;code&gt;calprefix()&lt;/code&gt;
and
&lt;code&gt;calsuffix()&lt;/code&gt;
functions doesn't work either)
&lt;/p&gt;

  &lt;h4 id="DefaultColor"&gt;After the addition of
  &lt;code&gt;$DefaultColor&lt;/code&gt;
  in version 3.3.0
  &lt;/h4&gt;

&lt;p&gt;
I submitted a patch that has
(with modifications)
made it into the mainline codebase
in version 3.3.0,
released &lt;time datetime="2020-01-31"&gt;January 31&lt;sup&gt;st&lt;/sup&gt;, 2020&lt;/time&gt;.
This patch allows you to set a
&lt;code&gt;$DefaultColor&lt;/code&gt;
variable combining the benefits of the
&lt;code&gt;COLOR&lt;/code&gt;
keyword
(appearing on the week/month calendars
and getting passed to external utilities)
with the benefits of my
&lt;code&gt;calcolor&lt;/code&gt;/&lt;code&gt;msgprefix()&lt;/code&gt;/&lt;code&gt;msgsuffix()&lt;/code&gt;
trick above
(colorizing the agenda view,
without needing to specify the color
for every single event).
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;mark&gt;SET $DefaultColor "0 255 0"&lt;/mark&gt;
# birthdays for which I take some sort of action
REM Jan 10 MSG %"Alice%"
REM Jan 14 +7 MSG %"Bob [born(1980)]%" %b
&lt;mark&gt;SET $DefaultColor "0 128 0"&lt;/mark&gt;
# birthdays I want to know about
REM Jan 1 MSG %"Eric's Mom's birthday%"
&lt;mark&gt;SET $DefaultColor "-1 -1 -1"&lt;/mark&gt;
# Other misc. birthdays in the original default color
REM Jan 6 MSG %"Dianne's birthday%"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;birthdays.rem&lt;/figcaption&gt;
&lt;/figure&gt;
As an added bonus in 3.3.0,
if you invoke
&lt;code&gt;remind&lt;/code&gt;
with the
&lt;code&gt;-@&lt;/code&gt;
parameter,
it will colorize agenda items for you
in addition to the week/month calendar views.
&lt;/p&gt;

 &lt;h3 id="raw-output"&gt;Raw output&lt;/h3&gt;

&lt;p&gt;
While pretty output works well for humans,
sometimes I desire to pipe output to another program.
I have no interest in trying to parse the
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;week or month calendar views&lt;/a&gt;.
The agenda view can serve
as a primitive &amp;amp; fragile output format
for post-processing.
However,
&lt;code&gt;remind&lt;/code&gt;
provides the
&lt;code&gt;-p&lt;/code&gt;
and
&lt;code&gt;-s&lt;/code&gt;
options to produce machine-readable output.
This output format works much better
with tools such as
&lt;code&gt;grep&lt;/code&gt;,
&lt;code&gt;sed&lt;/code&gt;,
&lt;code&gt;awk&lt;/code&gt;,
and
&lt;code&gt;python&lt;/code&gt;/&lt;code&gt;perl&lt;/code&gt;/&lt;code&gt;ruby&lt;/code&gt;,
as well as the
&lt;code&gt;tkremind&lt;/code&gt;
program.
&lt;code&gt;remind&lt;/code&gt;
also allows your reminders
to convey additional metadata
to these back-ends.
Given this reminder:
&lt;figure&gt;
&lt;pre&gt;
REM 2020-01-29 \
  AT 8:30 DURATION 3:15 \
  TAG work \
  TAG home \
  SPECIAL COLOR 255 128 0 \
  my message
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Remind will output as follows:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem -s 2020-01-29
2020/01/29 COLOR work,home 195 510 255 128 0 8:30-11:45am  my message
&lt;label class="user"&gt;user$ &lt;/label&gt;rem -p 2020-01-29
# rem2ps begin
January 2020 31 3 0
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
December 31
February 29
2020/01/29 COLOR work,home 195 510 255 128 0 8:30-11:45am  my message
# rem2ps end
&lt;/pre&gt;
&lt;/figure&gt;
The
&lt;code&gt;-s&lt;/code&gt;
offers a simplified format
of only the calendar data:

&lt;/p&gt;&lt;ol&gt;
 &lt;li&gt;The date in YYYY/MM/DD format&lt;/li&gt;
 &lt;li&gt;
  The
  &lt;code&gt;SPECIAL&lt;/code&gt;
  type (in this case
  &lt;code&gt;COLOR&lt;/code&gt;)
 &lt;/li&gt;
 &lt;li&gt;
  a comma-separated list of
  &lt;a href="https://blog.thechases.com/posts/remind/#tags"&gt;&lt;code&gt;TAG&lt;/code&gt;s&lt;/a&gt;
  associated with the reminder
 &lt;/li&gt;
 &lt;li&gt;the duration in minutes&lt;/li&gt;
 &lt;li&gt;
  the start-time in minutes-from-midnight
  (510 = 60 minutes * 8 hours)
 &lt;/li&gt;
 &lt;li&gt;
  the
  &lt;code&gt;&lt;var&gt;$RED&lt;/var&gt;&lt;/code&gt;,
  &lt;code&gt;&lt;var&gt;$GREEN&lt;/var&gt;&lt;/code&gt;,
  and
  &lt;code&gt;&lt;var&gt;$BLUE&lt;/var&gt;&lt;/code&gt;
  values from the
  &lt;code&gt;COLOR&lt;/code&gt;
  (255, 128, and 0 here)
 &lt;/li&gt;
 &lt;li&gt;
  the body of the message,
  prepended with the time
  (if present)
 &lt;/li&gt;
&lt;/ol&gt;
while the
&lt;code&gt;-p&lt;/code&gt;
option wraps it in the following meta-data:
&lt;ol&gt;
 &lt;li&gt;
  a literal
  &lt;code&gt;# rem2ps begin&lt;/code&gt;
 &lt;/li&gt;
 &lt;li&gt;
  a line showing the month currently rendering,
  the current year,
  the current month,
  the number of days in the month,
  the week-day
  (0=Sunday, 1=Monday, … 6=Saturday)
  of the first day of the month,
  and a boolean 0/1
  based on whether the calendar
  should display Monday first
  (based on the
  "&lt;code&gt;-m&lt;/code&gt;"
  option passed to
  &lt;code&gt;remind&lt;/code&gt;)
 &lt;/li&gt;
 &lt;li&gt;the days of the week in the local language&lt;/li&gt;
 &lt;li&gt;the next month and its number of days&lt;/li&gt;
 &lt;li&gt;the previous month and its number of days&lt;/li&gt;
 &lt;li&gt;
  the list of reminders like
  &lt;code&gt;-s&lt;/code&gt;
  produces
 &lt;/li&gt;
 &lt;li&gt;
  a literal
  &lt;code&gt;# rem2ps end&lt;/code&gt;
 &lt;/li&gt;
&lt;/ol&gt;
If producing multiple months of data
the
&lt;code&gt;# rem2ps end&lt;/code&gt;
indicates the end of one month
but the receiving-program
should handle the case
that more months' data could follow.
The date
(first column)
should always appear,
but the other fields might display an asterisk
("&lt;code&gt;*&lt;/code&gt;")
if not present in the reminder.


  &lt;h4 id="special"&gt;&lt;code&gt;SPECIAL&lt;/code&gt; reminders&lt;/h4&gt;

&lt;p&gt;
While I don't give much space to them,
&lt;code&gt;remind&lt;/code&gt;'s
&lt;code&gt;SPECIAL&lt;/code&gt;
token lets you pass out-of-band information
to external back-ends.
This can include things like
&lt;a href="https://blog.thechases.com/posts/remind/#special-color"&gt;changing the color of an entry&lt;/a&gt;
(with
&lt;code&gt;SPECIAL COLOR &lt;var&gt;$RED&lt;/var&gt; &lt;var&gt;$GREEN&lt;/var&gt; &lt;var&gt;$BLUE&lt;/var&gt;&lt;/code&gt;),
shading the background of a calendar square
(with
&lt;code&gt;SPECIAL SHADE &lt;var&gt;$GRAYLEVEL&lt;/var&gt;&lt;/code&gt;
or
&lt;code&gt;SPECIAL SHADE &lt;var&gt;$RED&lt;/var&gt; &lt;var&gt;$GREEN&lt;/var&gt; &lt;var&gt;$BLUE&lt;/var&gt;&lt;/code&gt;),
drawing the phase-of-the-moon
(with
&lt;code&gt;SPECIAL MOON &lt;var&gt;$PHASE&lt;/var&gt;&lt;/code&gt;),
or adding
per-week
annotations
(with
&lt;code&gt;SPECIAL WEEK &lt;var&gt;$TEXT&lt;/var&gt;&lt;/code&gt;).
I use
&lt;code&gt;rem2ps&lt;/code&gt;
in the example regarding
&lt;a href="https://blog.thechases.com/posts/remind/#relative-dates"&gt;relative dates&lt;/a&gt;
but don't employ any of the
&lt;code&gt;SPECIAL&lt;/code&gt;
commands therein.
&lt;/p&gt;

  &lt;h4 id="run"&gt;&lt;code&gt;RUN&lt;/code&gt; reminders&lt;/h4&gt;

&lt;p&gt;
The
&lt;code&gt;RUN&lt;/code&gt;
message-type acts much like a
&lt;code&gt;MSG&lt;/code&gt;
message-type except that
after passing the body through the
&lt;a href="https://blog.thechases.com/posts/remind/#substitution"&gt;substitution filter&lt;/a&gt;
it executes the resulting command
instead of displaying the result.
Beware of quoting and escaping issues.
While you can do things like
&lt;figure&gt;
&lt;pre&gt;
REM Jan 31 +3 &lt;mark&gt;RUN echo Hello %b,&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
the shell will complain on certain days because
it lacks a closing single-quote trying to run
&lt;figure&gt;
&lt;pre&gt;
echo Hello in 2 days&lt;mark&gt;'&lt;/mark&gt; time
&lt;/pre&gt;
&lt;/figure&gt;
However, you could do something like
&lt;figure&gt;
&lt;pre&gt;
REM 1 ONCE RUN backup.sh [$U]
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
which would run
&lt;figure&gt;
&lt;pre&gt;
backup.sh &lt;mark&gt;2020-01-01&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
The
&lt;code&gt;ONCE&lt;/code&gt;
keyword will only run the reminder once,
even if you invoke
&lt;code&gt;remind&lt;/code&gt;
more than once in a given day.
However, it relies on the
&lt;code&gt;atime&lt;/code&gt;
(most recent access-time)
of the reminder file.
Since my
&lt;code&gt;/etc/fstab&lt;/code&gt;
sets
&lt;code&gt;noatime&lt;/code&gt;
on all of my mount-points,
any reminders marked with
&lt;code&gt;ONCE&lt;/code&gt;
run &lt;em&gt;every&lt;/em&gt; time.
&lt;/p&gt;

&lt;p&gt;
I keep a list of "to-do" items
so I tried including those
in my reminders:
&lt;figure&gt;
&lt;pre&gt;
REM SATISFY [$U == realtoday()] RUN head -5 ~/todo.txt
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
to output the top 5 things
on my to-do list.
&lt;/p&gt;

&lt;p&gt;
I also experimented
with keeping larger stretches of prose
in an external file like
&lt;figure&gt;
&lt;pre&gt;
BEGIN: gifts
&lt;label class="visual"&gt;⋮&lt;/label&gt;
END: gifts
BEGIN: FakeCON
Flight:
 Southwest Flight 3141 DAL → BWI
 Departs: 1:41pm
 Arrives: 3:14pm
 Confirmation number: 1414
Car:
 Thrifty reservation# 15278-3955
Hotel:
 Marriott
 753 Old Oak
 Reservation: 20200115732
 Member#: 1783613807
END: FakeCON
BEGIN: other
&lt;label class="visual"&gt;⋮&lt;/label&gt;
END: other
&lt;/pre&gt;
&lt;figcaption&gt;annotations.txt&lt;/figcaption&gt;
&lt;/figure&gt;
and then using
&lt;code&gt;RUN&lt;/code&gt;
to emit the content
&lt;figure&gt;
&lt;pre&gt;
FSET external_annotation(marker) \
    "sed -n '/^BEGIN: *" + marker + "$/,/^END: *" + marker + "$/p' " + \
    filedir() + "/annotations.txt"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
REM Feb 18 2020 RUN [external_annotation("FakeCON")]
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
However, I found these of limited use,
so I don't really use
&lt;code&gt;RUN&lt;/code&gt;
in my day-to-day.
&lt;/p&gt;

  &lt;h4 id="tags"&gt;Tagging&lt;/h4&gt;

&lt;p&gt;
Similar to the
&lt;code&gt;SPECIAL&lt;/code&gt;
keyword, the
&lt;code&gt;TAG&lt;/code&gt;
keyword lets you assign one or more tags
to your reminder.
&lt;figure&gt;
&lt;pre&gt;
REM 15 &lt;mark&gt;TAG work&lt;/mark&gt; MSG Run commission report
REM 1 &lt;mark&gt;TAG home TAG chores&lt;/mark&gt; MSG Test smoke alarms
REM 1 &lt;mark&gt;TAG car TAG chores&lt;/mark&gt; MSG Check oil levels
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
While
&lt;code&gt;remind&lt;/code&gt;
doesn't do anything with them directly,
it passes them through
to other back-ends
when using the
&lt;a href="https://blog.thechases.com/posts/remind/#raw-output"&gt;raw-output&lt;/a&gt;
modes.
Because we
&lt;a href="https://blog.thechases.com/posts/remind/#messages-based-on-date"&gt;write monthly letters
to our grandparents&lt;/a&gt;
I use this functionality to tag
newsworthy events in our calendar.
&lt;figure&gt;
&lt;pre&gt;
REM Jan 18 2020 &lt;mark&gt;TAG letter&lt;/mark&gt; \
  MSG Date to see the new Jumanji movie
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
I can then use
the raw output of the
&lt;code&gt;-s&lt;/code&gt;
and filter for only those items:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem -s &lt;var&gt;2020-01-01&lt;/var&gt; |
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;awk '$3 ~ /letter/'
&lt;/pre&gt;
&lt;/figure&gt;
so I have a list of what we did during the month.
This works well in my reminders too,
&lt;a href="https://blog.thechases.com/posts/remind/#run"&gt;using a
&lt;code&gt;RUN&lt;/code&gt;
directive&lt;/a&gt;:
&lt;figure&gt;
&lt;pre&gt;
REM 1 MSF [iif($Tm % 2, "Honey", "Hubby")] \
  writes %"Grandparent letter%"
REM 1 &lt;mark&gt;RUN rem -sr&lt;/mark&gt; [$U - 1] | \
  awk '$3 ~ /letter/{for (i=2; i&amp;lt;6; i++) $i=""; print}'
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
(the
&lt;code&gt;-r&lt;/code&gt;
prevents
&lt;code&gt;remind&lt;/code&gt;
from executing
&lt;code&gt;RUN&lt;/code&gt;
entries further,
and the expression
&lt;code&gt;[$U - 1]&lt;/code&gt;
finds the last day of the previous month
to pass to
&lt;code&gt;remind&lt;/code&gt;)
Thus, not only do I get a reminder
to let me know who writes the letter
but I get a list of the letter-worthy things
we did in the previous month.
&lt;/p&gt;

&lt;p&gt;
I wish
&lt;code&gt;remind&lt;/code&gt;
had more native functionality
to support tags
such as a
&lt;code&gt;trigtags()&lt;/code&gt;
function
(to return the tags
for the current reminder),
or a
&lt;code&gt;hastag()&lt;/code&gt;
function
(to ask if the current reminder
has a given tag).
This would help me
filter output
or expand tags in message-bodies.
Alas, not yet.
I look forward to some day (hopefully)
when these are even more useful.
Perhaps for my next patch.
&lt;/p&gt;

&lt;h2 id="shell"&gt;Shell tips &amp;amp; tricks&lt;/h2&gt;

 &lt;h3 id="aliases"&gt;Aliases&lt;/h3&gt;

&lt;p&gt;
I've set up a couple
&lt;code&gt;bash&lt;/code&gt;
aliases
in my shell to minimize typing:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
alias 1='rem -gaad -iCOLOR=1'
for d in {2..6} ; do
    alias ${i}='rem -gaad -iCOLOR=1 "*"'${i}
done
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.bash_aliases&lt;/figcaption&gt;
&lt;/figure&gt;
These let me type
&lt;code&gt;3&lt;/code&gt;
as a command
and get 3 days
of colorized agenda output.
&lt;/p&gt;

 &lt;h3 id="crontab"&gt;Crontab&lt;/h3&gt;

&lt;p&gt;
I have
&lt;code&gt;crontab&lt;/code&gt;
entries that email me my
today/tomorrow reminders each day
and a full week of reminders on Sunday morning:
&lt;figure&gt;
&lt;pre&gt;
@daily rem -g '*2'
&lt;time datetime="6:45"&gt;45 6&lt;/time&gt; * * 0 rem '*7'
&lt;/pre&gt;
&lt;figcaption&gt;crontab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="version-control"&gt;Version control&lt;/h3&gt;

&lt;p&gt;
I find it useful to keep my reminder directory,
&lt;code&gt;~/.config/remind/&lt;/code&gt;,
in version control.
I use
&lt;a href="https://git-scm.com/"&gt;&lt;code&gt;git&lt;/code&gt;&lt;/a&gt;
for multiple files,
but you could just as easily use
&lt;a href="https://www.mercurial-scm.org/"&gt;&lt;code&gt;mercurial&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://subversion.apache.org/"&gt;&lt;code&gt;Subversion&lt;/code&gt;&lt;/a&gt;,
or even
&lt;a href="https://en.wikipedia.org/wiki/Concurrent_Versions_System"&gt;&lt;code&gt;CVS&lt;/code&gt;&lt;/a&gt;
or
&lt;a href="https://en.wikipedia.org/wiki/Revision_Control_System"&gt;&lt;code&gt;rcs&lt;/code&gt;&lt;/a&gt;.
This gives me a lot of freedom
to experiment with complex reminder syntax.
If I mess something up,
I can always revert to the known-good state.
Also, I can delete reminders that have passed
so they don't clutter my calendar
or increase processing time,
but still have historical record of their existence.
I can also
&lt;code&gt;git push&lt;/code&gt;
&amp;amp;
&lt;code&gt;git pull&lt;/code&gt;
changes between different machines
to keep my reminders in sync.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;cd ~/.config/remind
&lt;label class="user"&gt;user$ &lt;/label&gt;git init .
&lt;label class="user"&gt;user$ &lt;/label&gt;git add *.rem
&lt;label class="user"&gt;user$ &lt;/label&gt;git commit -m "Initial checkin"
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;var&gt;modify reminder files&lt;/var&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;git commit -am "Added John's birthday"
&lt;/pre&gt;
&lt;figcaption&gt;git&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;cd
&lt;label class="user"&gt;user$ &lt;/label&gt;mkdir RCS
&lt;label class="user"&gt;user$ &lt;/label&gt;echo "Initial checkin" | ci -l .reminders
&lt;label class="user"&gt;user$ &lt;/label&gt;ed -p"* " .reminders
3141
&lt;label&gt;* &lt;/label&gt;a
&lt;var&gt;REM Apr 1 MSG Tom's birthday%&lt;/var&gt;
.
&lt;label&gt;* &lt;/label&gt;wq
3170
&lt;label class="user"&gt;user$ &lt;/label&gt;echo "Added Tom's birthday" | ci -l .reminders
&lt;/pre&gt;
&lt;figcaption&gt;rcs&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="speech"&gt;Speech&lt;/h3&gt;

&lt;p&gt;
The readable nature of the output
makes it easy to pipe output to a program like
&lt;code&gt;espeak&lt;/code&gt;
(or another text-to-speech engine
which you would also have to install)
to read your reminders aloud.
You might create a custom
&lt;code&gt;spoken.rem&lt;/code&gt;
file that you don't
&lt;code&gt;INCLUDE&lt;/code&gt;
in your regular
&lt;code&gt;reminders.rem&lt;/code&gt;
in which you remove the
&lt;code&gt;BANNER&lt;/code&gt;,
emit section dividers
(if you don't choose to use
&lt;a href="https://blog.thechases.com/posts/remind/#labels"&gt;display labels&lt;/a&gt;),
and
&lt;code&gt;INCLUDE&lt;/code&gt;
a select subset of calendars:
&lt;figure&gt;
&lt;pre&gt;
BANNER %
MSG Birthdays%
INCLUDE [filedir()]/birthdays.rem
MSG Personal items%
INCLUDE [filedir()]/tim.rem
MSG Work items%
INCLUDE [filedir()]/work.rem
&lt;/pre&gt;
&lt;figcaption&gt;spoken.rem&lt;/figcaption&gt;
&lt;/figure&gt;
(the
&lt;code&gt;[filedir()]&lt;/code&gt;
expands to the path of the containing
&lt;code&gt;spoken.rem&lt;/code&gt;
file)
&lt;/p&gt;

&lt;p&gt;
And to read them:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;remind ~/.config/remind/spoken.rem&lt;mark&gt; | espeak&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2 id="easy-recipes"&gt;Easier or more boring recipes&lt;/h2&gt;

 &lt;h3 id="car-registration"&gt;Car registration&lt;/h3&gt;

&lt;p&gt;
We need to get our car inspected
and get our (re)registration submitted
by the end of February
so I want notifications starting at the beginning of February:
&lt;figure&gt;
&lt;pre&gt;
REM Mar 1 --1 ++[26 + isleap($Uy)] \
  SATISFY [$Uy &amp;gt; &lt;mark&gt;2019&lt;/mark&gt;] \
  MSG %"Car reg./inspection due%" %b
&lt;/pre&gt;
&lt;figcaption&gt;household.rem&lt;/figcaption&gt;
&lt;/figure&gt;
The
&lt;code&gt;Mar 1 --1&lt;/code&gt;
results in the last day of February
while the
&lt;code&gt;++[26 + isleap($Uy)]&lt;/code&gt;
gives 26 or 27 days of advanced warning
depending on whether the current year
is a leap year.
Once I've done the registration/inspection,
I update the year in the
&lt;a href="https://blog.thechases.com/posts/remind/#satisfy"&gt;&lt;code&gt;SATISFY&lt;/code&gt;&lt;/a&gt;
so it stops reminding me about it for that year.
&lt;/p&gt;

 &lt;h3 id="bonds"&gt;Maturing bonds&lt;/h3&gt;

&lt;p&gt;
I purchased a couple simple-interest bonds
and want to know when to expect the interest payment checks.
They arrive
around the 25&lt;sup&gt;th&lt;/sup&gt; of the month
(or
&lt;code&gt;AFTER&lt;/code&gt;
if it falls on a Sunday
when the USPS doesn't deliver mail),
every 6 months
until the end of the bond-term.
But
&lt;code&gt;remind&lt;/code&gt;
makes this pretty easy:
&lt;figure&gt;
&lt;pre&gt;
REM 25 UNTIL Feb 26, 2025 \
  OMIT Sun AFTER \
  SATISFY [$Tm % 6 == 2] \
  MSG %"Expect bond check for $31.41%"
&lt;/pre&gt;
&lt;figcaption&gt;finances.rem&lt;/figcaption&gt;
&lt;/figure&gt;
This uses the
&lt;a href="https://blog.thechases.com/posts/remind/#satisfy"&gt;&lt;code&gt;SATISFY&lt;/code&gt;&lt;/a&gt;
keyword,
repeatedly looking for the next
25&lt;sup&gt;th&lt;/sup&gt; of the month
until
&lt;code&gt;$Tm % 6 == 2&lt;/code&gt;
— the month in question
is either February or August.
&lt;/p&gt;

 &lt;h3 id="notice-until-action"&gt;Notice until action&lt;/h3&gt;

&lt;p&gt;
I send birthday cards
to certain friends &amp;amp; family members
so I want about a week of notice
to get a card,
write a note,
and get it in the mail.
However, once I've sent the card,
I don't need further
advanced warning reminders.
By updating that year in the expression
once I've completed the task
I no longer get advanced warning for the given year.
Similar to the
&lt;a href="https://blog.thechases.com/posts/remind/#car-registration"&gt;car registration&lt;/a&gt;
example,
in this case I still want to receive notification
on the day
&lt;em&gt;of&lt;/em&gt;
the birthday too,
not drop the reminder entirely.
&lt;figure&gt;
&lt;pre&gt;
FSET &lt;mark&gt;last_sent(y)&lt;/mark&gt; iif($Uy &amp;gt; y, 7, 0)
&lt;label class="visual"&gt;⋮&lt;/label&gt;
# once I've sent a card to Grandma in 2020
# I'll change the "2019" to "2020":
REM Jul 6 +[&lt;mark&gt;last_sent(2019)&lt;/mark&gt;] MSG %"Grandma%" %b
&lt;/pre&gt;
&lt;figcaption&gt;birthdays.rem&lt;/figcaption&gt;
&lt;/figure&gt;
I use a single
"&lt;code&gt;+&lt;/code&gt;"
for Grandma's notification
because sometimes holidays interfere with the mail
so I appreciate having the extended notice.
&lt;/p&gt;

 &lt;h3 id="messages-based-on-date"&gt;Messages tweaked based on date&lt;/h3&gt;

&lt;p&gt;
As a Christmas gift to our grandparents,
we take turns hand-writing a letter to them each month.
So on the first of each month,
I want a reminder,
but also want to know whose turn:
&lt;figure&gt;
&lt;pre&gt;
REM 1 MSF [&lt;mark&gt;iif($Tm % 2, "Honey", "Hubby")&lt;/mark&gt;] \
  writes %"Grandparent letter%"
&lt;/pre&gt;
&lt;figcaption&gt;household.rem&lt;/figcaption&gt;
&lt;/figure&gt;
Similarly, if I scrub the tile grout
in the kitchen one week,
our master bathroom the next,
and the kids' bathroom the third week
I can achieve this with:
&lt;figure&gt;
&lt;pre&gt;
REM Sat MSF Scrub %"[ \
  &lt;mark&gt;choose((weekno() % 3)+1,&lt;/mark&gt; \
  &lt;mark&gt;"Kitchen",&lt;/mark&gt; \
  &lt;mark&gt;"Master",&lt;/mark&gt; \
  &lt;mark&gt;"Kids'")&lt;/mark&gt; \
  ] grout%"
&lt;/pre&gt;
&lt;figcaption&gt;household.rem&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="selective-advanced-notice"&gt;Selectively getting advanced notice&lt;/h3&gt;

&lt;p&gt;
For friends I see at church on Sunday
I would like advance notice of their birthday on Sunday
but don't want additional reminders
during the intervening days of the week.
For this, I use an expression evaluation
to dynamically determine the number of days worth of notice:
&lt;figure&gt;
&lt;pre&gt;
FSET church_notice() iif($Uw == 0, 6, 0)
&lt;label class="visual"&gt;⋮&lt;/label&gt;
REM Jan 16 ++[church_notice()] MSG %"Tony %c%"
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
On Sunday, January 12&lt;sup&gt;th&lt;/sup&gt;,
I get notice about Tony's birthday coming up on Thursday
so I can wish him a happy birthday when I see him.
And I get notice on Thursday, his actual birthday.
But I don't have my agenda cluttered
with extra advance warnings on Monday through Wednesday.
&lt;/p&gt;

 &lt;h3 id="credit-reporting"&gt;Annoying credit-reporting company rules&lt;/h3&gt;

&lt;p&gt;
I learned the hard way that the credit-reporting companies
will not let you order your credit report on the same day each year
because a full year hasn't actually passed.
Yes, I'd love to pull my free credit report on the same day of each year.
However, I need to pull them
&lt;strong&gt;more&lt;/strong&gt;
than 365 days (366 on a leap-year) apart.
So I use the repeat modifier
to get reminded every 367 days.
I also intersperse them throughout the year
rather than all at once
so that I get a more frequent sampling in case something has gone wrong:
&lt;figure&gt;
&lt;pre&gt;
REM Jan 1 2019 *367 MSF Order credit report: %"Equifax%" \
  (1-877-322-8228)%_\
  &lt;a href="https://www.annualcreditreport.com/index.action"&gt;https://www.annualcreditreport.com/index.action&lt;/a&gt;
REM May 1 2019 *367 MSF Order credit report: %"Experian%" \
  (1-877-322-8228)%_\
  &lt;a href="https://www.annualcreditreport.com/index.action"&gt;https://www.annualcreditreport.com/index.action&lt;/a&gt;
REM Sep 13 2019 *367 MSF Order credit report: %"TransUnion%" \
  (1-877-322-8228)%_\
  &lt;a href="https://www.annualcreditreport.com/index.action"&gt;https://www.annualcreditreport.com/index.action&lt;/a&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
The subsequent dates then drift forward automatically
but I don't have to worry about trying again
inside the one-year window of time.
Also, I include the phone-number
and the URL
so I don't have to look it up every time.
&lt;/p&gt;

 &lt;h3 id="candy-sales"&gt;Post-holiday candy sales&lt;/h3&gt;

&lt;p&gt;
I tend to stock up on chocolate
when it goes on sale after some of the big candy holidays.
(I keep stashes of chocolate hidden around the house
so that when my beloved has a hankering for a certain type,
I can often play the miracle-worker
and bring it out of hiding).
So I use
&lt;code&gt;remind&lt;/code&gt;
to make sure I don't forget:
&lt;figure&gt;
&lt;pre&gt;
REM Feb 15 MSG %"Post-Valentine candy sales%"%
REM &lt;mark&gt;[easterdate($Uy) + 1]&lt;/mark&gt; MSG %"Post-Easter candy sales%"%
REM Nov 1 MSG %"Post-Halloween candy sales%"%
REM Dec 26 MSG %"Post-Christmas candy sales%"%
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
The post-Easter one requires actual calculations
and
&lt;code&gt;remind&lt;/code&gt;
does this without much thought at all
thanks to the built-in
&lt;code&gt;easterdate()&lt;/code&gt;
function.
The rest wouldn't pose any trouble
for an ordinary calendar program.
&lt;/p&gt;

 &lt;h3 id="any-reminders"&gt;Did any of these reminders trigger?&lt;/h3&gt;

&lt;p&gt;
Sometimes you have several events
and you want to provide some
&lt;code&gt;SPECIAL&lt;/code&gt;
modifier for all of them.
You can use
&lt;code&gt;remind&lt;/code&gt;'s
special
&lt;code&gt;$NumTrig&lt;/code&gt;
variable to check this:
&lt;figure&gt;
&lt;pre&gt;
SET n &lt;mark&gt;$NumTrig&lt;/mark&gt;
REM Sat Sun SATISFY 1
REM Feb 14 MSG Valentine's Day
REM [easterdate()] MSG Easter
IF &lt;mark&gt;$NumTrig &amp;gt; n&lt;/mark&gt;
  # At least one triggered
  REM SPECIAL SHADE 75
ENDIF
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
If any of the events occurred
after we snapshot the
&lt;code&gt;$NumTrig&lt;/code&gt;,
then the
&lt;code&gt;IF&lt;/code&gt;
will allow for the
&lt;code&gt;SPECIAL SHADE&lt;/code&gt;
to happen for the day in question.
&lt;/p&gt;

 &lt;h3 id="fizzbuzz"&gt;FizzBuzz&lt;/h3&gt;

&lt;p&gt;
Often in interviews
the interviewer will instruct
the candidate to whiteboard the classic
&lt;code&gt;fizzbuzz&lt;/code&gt;
solution where the program emits
&lt;var&gt;n&lt;/var&gt;
numbers but
if the number is divisible by 3
print "fizz",
if the number is divisible by 5
print "buzz",
and if the number is divisible by 3 &amp;amp; 5
print "fizzbuzz"
(otherwise just printing the number).
So in case you ever need to do this using
&lt;code&gt;remind&lt;/code&gt;:
&lt;figure&gt;
&lt;pre&gt;
FSET doy() $U - date($Uy-1, 12, 31)
MSG [iif(doy() % 15 == 0, \
  "fizzbuzz", \
  iif(doy() % 5 == 0, \
    "buzz", \
    iif(doy() % 3 == 0, \
      "fizz", \
      doy() \
      ) \
    ) \
  )]
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
The
&lt;code&gt;doy()&lt;/code&gt;
function returns the Julian day of the year
(as found by taking the current date,
&lt;code&gt;$U&lt;/code&gt;,
and subtracting December 31&lt;sup&gt;st&lt;/sup&gt;
of the previous year)
and then uses that to calculate
the fizzbuzz-ness of the day.
Dumb? Yep.
Useless? You bet.
Fun demonstration? Why not.
&lt;/p&gt;

&lt;h2 id="more-recipes"&gt;More complex calendar recipes&lt;/h2&gt;

 &lt;h3 id="trash-day"&gt;Shifting garbage day&lt;/h3&gt;

&lt;p&gt;
The go-to for demonstrating the power of
&lt;code&gt;remind&lt;/code&gt;
involves shifting your garbage collection day back
if a holiday occurs earlier in the week:
&lt;figure&gt;
&lt;pre&gt;
PUSH
CLEAR

INCLUDE [filedir()]/holidays.rem

SET garbday &lt;mark&gt;4&lt;/mark&gt;
SET garbdayname wkday(garbday)
FSET _garbhol(x) wkdaynum(x) == garbday \
  &amp;amp;&amp;amp; slide(x - garbday, garbday) != x
FSET _garbdel() iif($Tw != garbday, " (Delayed)", "")
REM [garbdayname] +1 AFTER OMITFUNC _garbhol \
  MSG %"Garbage day[_garbdel()]%" %b%
POP
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Change the
&lt;code&gt;garbday&lt;/code&gt;
value to your garbage day
(Monday = 1, Friday = 5)
and set up your holiday
&lt;code&gt;OMIT&lt;/code&gt;
entries in your
&lt;code&gt;holiday.rem&lt;/code&gt;
(or hard-code them inside the
&lt;code&gt;PUSH&lt;/code&gt;/&lt;code&gt;CLEAR&lt;/code&gt;/&lt;code&gt;POP&lt;/code&gt;
block)
and
&lt;code&gt;remind&lt;/code&gt;
will automatically shift the garbage day reminder
based on the holiday schedule.
&lt;/p&gt;

 &lt;h3 id="pretty-date-diff"&gt;Pretty date-diff&lt;/h3&gt;

&lt;p&gt;
While
&lt;code&gt;remind&lt;/code&gt;
lets you subtract two dates
to get the number of days between them,
sometimes you want that difference
expresses as a number of years, months, and days.
To do that,
I have this
&lt;code&gt;ymd_diff&lt;/code&gt;
function which returns
values like
"3y 2m 11d"
or
"3y 1m 14d ago"
which you might find more useful:
&lt;figure&gt;
&lt;pre&gt;
FSET ymd_diff_gt(d1, d2) \
  iif(year(d2) &amp;gt; year(d1), \
    (year(d2) - year(d1)) - ( \
      (monnum(d2) &amp;lt; monnum(d1)) \
      || \
      (monnum(d2) == monnum(d1) &amp;amp;&amp;amp; day(d2) &amp;lt; day(d1)) \
      ), \
    0 \
    ) + "y " + \
  iif(monnum(d2) &amp;gt; monnum(d1), \
    (monnum(d2) - monnum(d1)) - (day(d2) &amp;lt; day(d1)), \
    iif(monnum(d2) &amp;lt; monnum(d1), \
      (12 + monnum(d2) - monnum(d1)) - (day(d2) &amp;lt; day(d1)), \
      iif(day(d2) &amp;lt; day(d1), 11, 0) \
      )) + "m " + \
  iif(day(d2) &amp;gt;= day(d1), \
    day(d2) - day(d1), \
    (daysinmon(monnum(d1), year(d2)) + day(d2)) - day(d1) \
    ) + "d"
FSET &lt;mark&gt;ymd_diff&lt;/mark&gt;(d1, d2) \
  iif(d1 &amp;gt; d2, ymd_diff_gt(d2, d1) + " ago",\
  ymd_diff_gt(d1, d2) \
  )
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="manpages"&gt;Generating reminder files programmatically&lt;/h3&gt;

&lt;p&gt;
One of my calendars gives me a daily
&lt;code&gt;man&lt;/code&gt;-page
to read based on my list of system
&lt;code&gt;man&lt;/code&gt;-pages
looking something like
&lt;figure&gt;
&lt;pre&gt;
SET ManStart date(2020,1,1)
REM [ManStart + 0] MSG man 5 %"fstab%"%
REM [ManStart + 1] MSG man 1 %"rs%"%
REM [ManStart + 2] MSG man 1 %"awk%"%
REM [ManStart + 3] MSG man 6 %"cribbage%"%
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;manpages.rem&lt;/figcaption&gt;
&lt;/figure&gt;
Creating this manually would have taken a long time.
A little shell-script easily generated the file:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;cat remify_manpages.awk
BEGIN {
 print "SET ManStart date(2020,1,5)"
}
$7 ~ /^[1678]$/ {
 printf("REM [ManStart + %i] MSG man %s %%\"%s%%\"%%\n", NR - 1, $7, $6)
}

&lt;label class="user"&gt;user$ &lt;/label&gt;find /usr/share/man/man[1678]/ -maxdepth 2 -type f \
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;| grep -vi perl \
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;| sort -R \
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;| awk -F'[./]' -f remify_manpages.awk &amp;gt; manpages.rem
&lt;/pre&gt;
&lt;/figure&gt;
This finds all of the
&lt;code&gt;man&lt;/code&gt;
files in
&lt;code&gt;/usr/share/man/man[1678]/&lt;/code&gt;
removing the many
&lt;code&gt;perl&lt;/code&gt;-related pages I don't care about,
shuffles them so I get a nice variety,
then uses the
&lt;code&gt;awk&lt;/code&gt;
script to transform them into a
&lt;code&gt;remind&lt;/code&gt;
file.
Now I have several years worth
of daily man-pages to read.
If you have a pre-existing list of dates,
you can use the standard suite of Unix tools
to transform the list
into something that
&lt;code&gt;remind&lt;/code&gt;
can use.
&lt;/p&gt;

 &lt;h3 id="relative-dates"&gt;Relative date calculations for event-planning&lt;/h3&gt;

&lt;p&gt;
I serve on the leadership team
for a local non-profit
that works to provide clothing-vouchers
to kids on the school free/reduced-lunch program.
However the calendar changes each year
based on certain fixed dates including
when the school counselors have their training before school starts,
when school starts,
and which weekends the shopping event(s) take(s) place.
I use
&lt;code&gt;SET&lt;/code&gt;
to define the fixed-dates,
and calculate the other dates based off them.
&lt;figure&gt;
&lt;pre&gt;
SET CounselorTraining date(2019,7,31)
SET SchoolStart date(2019,8,15)
SET Event date(2019,10,19)

SET ApplicationsGoHome (Event - 47)

REM [CounselorTraining] MSG %"Attend counselor training%"
REM [SchoolStart] MSG %"First day of school%"
REM [ApplicationsGoHome - 3] \
  MSG %"Remind counselors applications will be going home%"
REM [ApplicationsGoHome] \
  MSG %"Applications go home with kids%"
REM [ApplicationsGoHome] \
  MSG %"School calls families to let them know to watch for applications%"
REM [Event - 37] \
  MSG %"Remind counselors that applications must be back [Event - 33]%"
REM [Event - 33] MSG %"Check funding levels%"
REM [Event - 32] MSG %"Pick up applications from school%"
REM [Event - 32] THROUGH [Event - 26] MSG %"Data entry%"
REM [Event - 10] MSG %"Print acceptance letters%"
REM [Event - 8] MSG %"Email counselors to expect letters%"
REM [Event - 8] MSG %"Acceptance letters go home%"
REM [Event - 5] MSG %"Email volunteers a reminder%"
REM [Event] MSG %"Event%"
REM [Event + 2] MSG %"Email volunteers to thank%"
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
(I have some
&lt;code&gt;OMIT&lt;/code&gt;
and
&lt;code&gt;BEFORE&lt;/code&gt;
in there too to jostle things around a bit)
I can then use
&lt;code&gt;rem -p3 2019-08-01 | rem2ps | ps2pdf - calendar.pdf&lt;/code&gt;
to generate the calendar PDF
that I can email out to the rest of the leadership team.
&lt;/p&gt;

 &lt;h3 id="medication"&gt;Medicine schedule&lt;/h3&gt;

&lt;aside&gt;
&lt;span class="warning"&gt;Warning:&lt;/span&gt;
This one gets a bit complex
but hopefully walking through it step-by-step
helps make it easy to digest.
But I've never encountered any other calendar program
that came remotely close to letting me do things like this.
&lt;/aside&gt;

&lt;p&gt;
One of our kids
takes a medication
that we receive in a 30-day supply.
This means we need to pick it up
at the pharmacy at least the day before
to ensure uninterrupted dosing.
The pharmacy occasionally needs
a business day or two
to get the medication in,
so I like to drop off the prescription
a couple days early.
Which means I need to pick up
the prescription
at the doctor's office before that.
Which means I need to call
the doctor's office 72hr before that.
And all of those dates
can get shifted by weekends and holidays.
On a less-powerful calendar,
I'd schedule a reminder every 30 days
and then hope I remember to take
weekends and holidays into consideration.
&lt;/p&gt;

&lt;p&gt;
On the other hand,
&lt;code&gt;remind&lt;/code&gt;
makes this fairly straightforward to express.
While doing this,
I'd also like to get
nagging reminders
and advanced notice
if I haven't yet picked up the pills.
But once I have,
I don't want continued reminders.
First we'll begin
by creating an
&lt;code&gt;OMIT&lt;/code&gt;
context to hold the holidays
that the pharmacy &amp;amp; doctor's office close.
&lt;figure&gt;
&lt;pre&gt;
PUSH
CLEAR
# the Dr. and pharmacy close on these dates
&lt;label class="visual"&gt;⋮&lt;/label&gt;
OMIT Dec 24
OMIT Dec 25
&lt;label class="visual"&gt;⋮&lt;/label&gt;
# reminders will go here
&lt;label class="visual"&gt;⋮&lt;/label&gt;
POP
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
With that in place,
we'll set up some variables
to know when we most recently
picked up the medication
and when we started taking the medication
as well as how many days worth of medication
we receive
and the number of days of lead-time
the doctor's office has requested:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
SET &lt;mark&gt;MostRecentlyGotPills&lt;/mark&gt; date(2020, 1, 24)
# Started taking the medication on
SET &lt;mark&gt;MedStartDate&lt;/mark&gt; date(2019, 3, 18)
# we get 30 pills
SET &lt;mark&gt;MedFreq&lt;/mark&gt; 30
# Dr. office requests 72hr of notice
# but remind needs it in days
SET &lt;mark&gt;RXLeadTime&lt;/mark&gt; (72/24)
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Now we need to find the date
of the next time we run out of pills.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
REM [MedStartDate] *[MedFreq] SATISFY 1
SET PillsRunOut trigdate()
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
The
&lt;code&gt;SATISFY 1&lt;/code&gt;
produces no output,
but sets the most recent trigger date.
Using the
&lt;code&gt;trigdate()&lt;/code&gt;
function,
you can capture the date of the most recent event
for use in further calculations
and later reuse.
So those lines determine
the next day we'll run out of pills.
&lt;/p&gt;

&lt;p&gt;
From here,
we know that we need to pick up the pills
the day before then
(possibly more than one day before
if there's an intervening holiday or weekend):
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
# need to pick up meds at least one day before we run out
REM &lt;mark&gt;[PillsRunOut] -1&lt;/mark&gt; +1 \
    &lt;mark&gt;OMIT Sun&lt;/mark&gt; BEFORE \
    SATISFY [$U &amp;gt; MostRecentlyGotPills] \
    MSG %"Pick up pills%" at pharmacy %b
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
We evaluate the day when the
pills run out,
and then back it off by one day
("&lt;code&gt;-1&lt;/code&gt;").
Because this uses the single minus,
it will skip over
&lt;code&gt;OMIT&lt;/code&gt;
days, including holidays
that close the pharmacy &amp;amp; doctor's office,
as well as Sundays when the pharmacy is closed.
The
&lt;code&gt;SATISFY&lt;/code&gt;
ensures that,
if we have picked up the pills
on or after the date currently under consideration,
we don't bother reminding about this.
I also like a little advanced warning
(&lt;code&gt;+1&lt;/code&gt;).
In case life gets busy,
I have a little more room to
fit in a trip to the pharmacy.
&lt;/p&gt;

&lt;p&gt;
Once we know when we need to pick up the pills,
we can capture that date
(again, with
&lt;code&gt;trigdate()&lt;/code&gt;)
and use it to determine
when we need to
pick up the prescription
and take it to the pharmacy
(the day before,
but the doctor's office
doesn't open for prescriptions
on weekends or over holidays
so it might get bumped back
further).
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
SET RealPillPickUpDate trigdate()
REM [RealPillPickUpDate - 1] +1 \
    OMIT Sat Sun BEFORE \
    SATISFY [$U &amp;gt; MostRecentlyGotPills] \
    MSF %"Pick up Rx%" at Dr. %b \
    and take to pharmacy
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Then we need to create a reminder
based on the date we need to pick up the pills
and back it off by the number of days
that the doctor's office says
they need for lead-time
(72 hours):
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="visual"&gt;⋮&lt;/label&gt;
SET RealRxPickUpDate trigdate()
REM [RealRxPickUpDate] -[RXLeadTime] \
    OMIT Sat Sun BEFORE \
    SATISFY [$U &amp;gt; MostRecentlyGotPills] \
    MSF %"Call Dr. office to request Rx refill%" %b
&lt;label class="visual"&gt;⋮&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Again, we use the
&lt;code&gt;SATISFY&lt;/code&gt;
to ensure we only get this
if we haven't already picked up
the most recent batch of pills.
&lt;/p&gt;

&lt;p&gt;
This produces a final series
of variables and reminders
that notify me at appropriate times
and do all the holiday/weekend math
so we don't accidentally end up
without medication:
&lt;figure&gt;
&lt;pre&gt;
################
# Update here: #
################
SET &lt;mark&gt;MostRecentlyGotPills&lt;/mark&gt; date(2020, 1, 24)

###################
# Configure here: #
###################
SET MedStartDate date(&lt;mark&gt;2019, 11, 27&lt;/mark&gt;)
# we get 30 pills
SET MedFreq &lt;mark&gt;30&lt;/mark&gt;
# Dr. office requests 72hr of notice
# but remind needs it in days
SET RXLeadTime &lt;mark&gt;(72/24)&lt;/mark&gt;

PUSH
CLEAR
# &lt;mark&gt;the Dr. and pharmacy close on these dates&lt;/mark&gt;
OMIT Jan 1
&lt;label class="visual"&gt;⋮&lt;/label&gt;
OMIT Dec 24
OMIT Dec 25

#########################

# when's the next occurrence?
REM [MedStartDate] *[MedFreq] SATISFY 1
SET PillsRunOut trigdate()

# need to pick up meds at least one day before we run out
REM [PillsRunOut] -1 +1 \
    OMIT Sat Sun BEFORE \
    SATISFY [$U &amp;gt; MostRecentlyGotPills] \
    MSG %"Pick up pills%" at pharmacy %b
SET RealPillPickUpDate trigdate()
REM [RealPillPickUpDate - 1] +1 \
    OMIT Sat Sun BEFORE \
    SATISFY [$U &amp;gt; MostRecentlyGotPills] \
    MSF %"Pick up Rx%" at Dr. %b \
    and take to pharmacy
SET RealRxPickUpDate trigdate()
REM [RealRxPickUpDate] -[RXLeadTime] \
    OMIT Sat Sun BEFORE \
    SATISFY [$U &amp;gt; MostRecentlyGotPills] \
    MSF %"Call Dr. office to request Rx refill%" %b
POP
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Then each time I pick up the pills,
I update the
&lt;code&gt;MostRecentlyGotPills&lt;/code&gt;
with the new start-date.
You can use the same template
adjusting the
&lt;code&gt;MostRecentlyGotPills&lt;/code&gt;,
&lt;code&gt;MedStartDate&lt;/code&gt;,
&lt;code&gt;MedFreq&lt;/code&gt;,
and
&lt;code&gt;RXLeadTime&lt;/code&gt;,
for your use case.
&lt;/p&gt;&lt;p&gt;

 &lt;/p&gt;&lt;h3 id="scanfrom"&gt;Scanning forward from an earlier date&lt;/h3&gt;

&lt;p&gt;
Sometimes you have an
&lt;a href="https://blog.thechases.com/posts/remind/#omit"&gt;&lt;code&gt;OMIT&lt;/code&gt;&lt;/a&gt;
that has happened recently
and would thus impact how
&lt;a href="https://blog.thechases.com/posts/remind/#notice"&gt;advanced notice with
&lt;code&gt;+&lt;var&gt;&lt;/var&gt;&lt;/code&gt;&lt;/a&gt;
and
&lt;a href="https://blog.thechases.com/posts/remind/#adjusting"&gt;adjusting dates with
&lt;code&gt;BEFORE&lt;/code&gt;/&lt;code&gt;AFTER&lt;/code&gt;/&lt;code&gt;SKIP&lt;/code&gt;&lt;/a&gt;
calculations occur,
but because
&lt;code&gt;remind&lt;/code&gt;
first determines the
&lt;code&gt;OMIT&lt;/code&gt;
dates based on the "today" in question,
it might not adjust properly.
For example,
consider a floating holiday like Labor Day
(the first Monday in September):
&lt;figure&gt;
&lt;pre&gt;
REM Mon 1 Sep SATISFY 1
OMIT [$T] MSG Labor Day
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
If you run this on
&lt;time datetime="2020-9-7"&gt;September 7&lt;sup&gt;th&lt;/sup&gt;, 2020&lt;/time&gt;
it will fail to find the event
on today but will instead find the
&lt;strong&gt;next&lt;/strong&gt;
instance on
&lt;time datetime="2021-9-6"&gt;September 6&lt;sup&gt;th&lt;/sup&gt;, 2021&lt;/time&gt;.
By starting the search a week back using
&lt;code&gt;SCANFROM&lt;/code&gt;,
we encounter the right date:
&lt;figure&gt;
&lt;pre&gt;
REM Mon 1 Sep &lt;mark&gt;SCANFROM [$U-7]&lt;/mark&gt; SATISFY 1
OMIT [$T] MSG Labor Day
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Similarly, if you want to find both
the 4&lt;sup&gt;th&lt;/sup&gt; of July
and the day on which it gets observed,
you might need to look backwards a couple days
to find the closest 4&lt;sup&gt;th&lt;/sup&gt;:
&lt;figure&gt;
&lt;pre&gt;
# Start looking for July 4th
# beginning 7 days ago:
REM 4 July &lt;mark&gt;SCANFROM [$U - 7]&lt;/mark&gt; SATISFY 1

# if it fell on a Saturday
# observed on the preceding Friday
IF wkdaynum(trigdate()) == 6
  REM [trigdate()] MSG Independence day (actual)
  OMIT [trigdate()-1] MSG Independence day (observed)
ELSE
  # if it fell on a Sunday
  # observed on the following Monday
  IF wkdaynum(trigdate()) == 0
    REM [trigdate()] MSG Independence day (actual)
    OMIT [trigdate()+1] MSG Independence day (observed)
  ELSE
    # otherwise, observed=actual
    OMIT [trigdate()] MSG Independence day
  ENDIF
ENDIF
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
(taken from the
&lt;a href="https://www.roaringpenguin.com/wiki/index.php/Defs.rem"&gt;&lt;code&gt;defs.rem&lt;/code&gt;&lt;/a&gt;)
&lt;/p&gt;

 &lt;h3 id="school-cycle"&gt;School N-day cycles
 ignoring holidays &amp;amp; weekends&lt;/h3&gt;

&lt;p&gt;
Sometimes you want to know the number of days
between two dates,
without counting holidays
or other
&lt;code&gt;OMIT&lt;/code&gt;
dates.
This often shows up in school schedules
where a 4- or 6-day cycle
often rotates across the standard 5-day week.
The
&lt;code&gt;nonomitted()&lt;/code&gt;
function lets you do this
with minimal fuss.
However, the start-date
(the first parameter)
&lt;strong&gt;must&lt;/strong&gt;
be less-than or equal to the end-date or
&lt;code&gt;remind&lt;/code&gt;
will produce an error.
&lt;figure&gt;
&lt;pre&gt;
SET FirstDayOfSchool &lt;mark&gt;date(2019, 8, 14)&lt;/mark&gt;
# a 4-day cycle
SET SchoolPhase &lt;mark&gt;4&lt;/mark&gt;
PUSH
CLEAR
# list of school holidays
# and in-service days:
OMIT Jan 1
&lt;label class="visual"&gt;⋮&lt;/label&gt;
OMIT Dec 23 2019 THROUGH Jan 6 2020 \
  SATISFY [$Uw &amp;gt; 0 &amp;amp;&amp;amp; $Uw &amp;lt; 6] \
  MSG %"Christmas: No school%"%
&lt;label class="visual"&gt;⋮&lt;/label&gt;
OMIT Mar 13 2020 MSG %"In-service: No school%"%

IF $U &amp;gt;= FirstDayOfSchool
  SET DayNumber &lt;mark&gt;nonomitted&lt;/mark&gt;(  \
    FirstDayOfSchool, \
    $U, \
    "SAT", "SUN" \
    )
  SET schedule DayNumber % SchoolPhase + 1
  REM Aug 13 2021 AT 7:15 THROUGH May 21 2020 \
    SKIP \
    OMIT Sat Sun \
    MSG %"School%" %2 ([&lt;mark&gt;choose&lt;/mark&gt;( \
      schedule, \
      "music", \
      "art", \
      "language", \
      "health" \
      )])%
ENDIF
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2 id="daemon"&gt;Daemon mode&lt;/h2&gt;

&lt;p&gt;
In addition to invoking
&lt;code&gt;remind&lt;/code&gt;
manually or from scripts,
&lt;code&gt;remind&lt;/code&gt;
also offers a "daemon mode"
that will run in the background
and fire off commands as
&lt;code&gt;AT&lt;/code&gt;
events come around.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;remind -z&lt;mark&gt;5&lt;/mark&gt; -k'&lt;mark&gt;espeak %s &amp;amp;&lt;/mark&gt;' ~/.reminders
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
This will launch
&lt;code&gt;remind&lt;/code&gt;
which will check for changes in your
&lt;code&gt;~/.reminders&lt;/code&gt;
every 5 minutes.
If an
&lt;code&gt;AT&lt;/code&gt;
passes,
&lt;code&gt;remind&lt;/code&gt;
will spawn
&lt;code&gt;espeak&lt;/code&gt;
passing the reminder-text
(shell-escaped)
in place of the
&lt;code&gt;%s&lt;/code&gt;
so that the reminder text gets spoken.
Alternatively you could use
&lt;code&gt;xmessage&lt;/code&gt;
or
&lt;code&gt;zenity&lt;/code&gt;
to pop up a dialog
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;remind -z5 -k'&lt;mark&gt;xmessage %s &amp;amp;&lt;/mark&gt;' ~/.reminders
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
(beware that this can end up
creating lot of
&lt;code&gt;xmessage&lt;/code&gt;
dialog boxes)
or use
&lt;code&gt;notify-send&lt;/code&gt;
to display the reminder-text
as a notification:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;remind -z5 -k'&lt;mark&gt;notify-send %s &amp;amp;&lt;/mark&gt;' ~/.reminders
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
Because of the composability
of commands,
you could create your own shell-script to
email reminders,
text them to your phone,
hit a web service,
send them to a printer,
or print your
&lt;code&gt;MSG&lt;/code&gt;
contents on a 3d-printer.
Endless possibilities.
&lt;/p&gt;

&lt;p&gt;
Your reminder file can use the
&lt;code&gt;$Daemon&lt;/code&gt;
variable to test to see if
&lt;code&gt;remind&lt;/code&gt;
is running in daemon mode:
&lt;figure&gt;
&lt;pre&gt;
REM Jan 5 2020 AT 10:30 MSG [ \
  iif($Daemon, "Hey, you! ", "") \
  ] Doctor's appointment
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
When run in daemon mode,
this will prefix your message
with "Hey, you!"
to get your attention,
especially if you
&lt;a href="https://blog.thechases.com/posts/remind/#speech"&gt;use
&lt;code&gt;espeak&lt;/code&gt;
to speak your reminders&lt;/a&gt;
&lt;/p&gt;

&lt;h2 id="other"&gt;Other utilities&lt;/h2&gt;

 &lt;h3 id="rem2ics"&gt;Generating iCalendar/&lt;code&gt;.ics&lt;/code&gt; files&lt;/h3&gt;

&lt;p&gt;
Great.
Now you have all your reminders
generating just the way you want.
How do you share them with other people?
Most other
&lt;em&gt;cough*inferior*cough&lt;/em&gt;
calendar programs still support
&lt;code&gt;.ics&lt;/code&gt;
format files to describe events.
You can use the
&lt;a href="https://launchpad.net/rem2ics/"&gt;&lt;code&gt;rem2ics&lt;/code&gt;&lt;/a&gt;
utility to convert
the output of
&lt;code&gt;remind&lt;/code&gt;
into a
&lt;code&gt;.ics&lt;/code&gt;
file that you can import
into Google Calendar,
Outlook,
or iCalendar.
&lt;span class="warning"&gt;Note:&lt;/span&gt;
While it will export all of your reminders
including repeating reminders,
it will not include enough meta-information
to preserve things like repeats,
so the events show up in the external calendar
as individual events.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem -s&lt;var&gt;12&lt;/var&gt; |
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;TZ=&lt;var&gt;CST8CDT&lt;/var&gt; &lt;mark&gt;rem2ics -do &amp;gt;&lt;var&gt;reminders.ics&lt;/var&gt;&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
If you have your local timezone exported
as an environment variable,
you don't need to set it explicitly,
but it gets copied into the resulting
&lt;code&gt;reminders.ics&lt;/code&gt;
file verbatim,
and having the wrong timezone
can cause lots of problems.
&lt;/p&gt;

 &lt;h3 id="other-external"&gt;Other external utilities&lt;/h3&gt;

&lt;p&gt;
Outside the scope of this article
you'll find things like
&lt;code id="tkremind"&gt;tkremind&lt;/code&gt;
(a GUI front-end to
&lt;code&gt;remind&lt;/code&gt;)
and
&lt;code id="wyrd"&gt;wyrd&lt;/code&gt;
(a TUI front-end to
&lt;code&gt;remind&lt;/code&gt;).
I don't currently use either of these
so I'll leave those
for the reader to explore.
&lt;/p&gt;

&lt;h2 id="debugging"&gt;Troubleshooting &amp;amp; debugging&lt;/h2&gt;

&lt;p&gt;
While the vast majority of my reminders
pose no issues,
occasionally one ends up complex enough
that I want to make sure it really works
like I expect it to.
Because these really are code,
I appreciate having some tools
to debug them.
&lt;/p&gt;

 &lt;h3 id="debug-echo"&gt;Echoing into
 &lt;code&gt;remind -&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;
As a first line of debugging,
I usually try to isolate the single reminder
and then run
&lt;code&gt;remind&lt;/code&gt;
with the date I want to test.
If you specify a filename of
"&lt;code&gt;-&lt;/code&gt;",
&lt;code&gt;remind&lt;/code&gt;
will read reminders from
&lt;code&gt;&lt;abbr title="Standard Input"&gt;stdin&lt;/abbr&gt;&lt;/code&gt;
which lets you
&lt;code&gt;echo&lt;/code&gt;
a single reminder
to test it:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;&lt;mark&gt;echo&lt;/mark&gt; 'REM 1 --1 MSG Last' |
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;remind &lt;mark&gt;-&lt;/mark&gt; 2020-1-31
&lt;/pre&gt;
&lt;/figure&gt;
Occasionally you need a couple lines
to thoroughly test,
in which case wrap the
&lt;code&gt;echo&lt;/code&gt;
statements in parentheses:
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;&lt;mark&gt;(&lt;/mark&gt;echo 'OMIT Dec 31'
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;echo 'REM 1 --1 MSG Last'
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;&lt;mark&gt;)&lt;/mark&gt; | remind - 2020-12-31
&lt;/pre&gt;
&lt;/figure&gt;
Alternatively, sometimes you need to test multiple days
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;echo REM 1 --1 MSG Last \
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;remind - 2020-12-31 &lt;mark&gt;'*33'&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
or months
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;for m in {1..12}
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;do
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;echo "REM 1 --1 MSG Last" |
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;remind - 2020-&lt;mark&gt;${m}&lt;/mark&gt;-30
&lt;label class="PS2"&gt;&amp;gt;  &lt;/label&gt;done
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="debug-custom"&gt;Using a custom
 &lt;code&gt;.reminders&lt;/code&gt;
 file&lt;/h3&gt;

&lt;p&gt;
Sometimes you really do need a lengthier test case.
For these, I back-up my
&lt;code&gt;~/.reminders&lt;/code&gt;
file,
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;&lt;mark&gt;cp ~/.reminders{,_orig}&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
create a new one afresh
or modify the existing one
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;&lt;mark&gt;$EDITOR .reminders&lt;/mark&gt; # create test case
&lt;/pre&gt;
&lt;/figure&gt;
test against that until I get it right.
I then snapshot the working reminders,
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;&lt;mark&gt;mv ~/{.,working_}reminders&lt;/mark&gt;
&lt;/pre&gt;
&lt;/figure&gt;
and restore my original
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;&lt;mark&gt;mv ~/.reminders{_orig,}&lt;/mark&gt; # restore the original
&lt;/pre&gt;
&lt;/figure&gt;
Finally, I use my
&lt;code&gt;$EDITOR&lt;/code&gt;
to copy the working bits from
&lt;code&gt;~/working_reminders&lt;/code&gt;
into my reminder file.
&lt;/p&gt;

&lt;p&gt;
Alternatively, I also have my machines
configured with a "guest" user,
so sometimes it ends up faster
to log in as the "guest"
(who has no
&lt;code&gt;~/.reminders&lt;/code&gt;
file of consequence),
blow away any previous test
&lt;code&gt;~/.reminders&lt;/code&gt;
file,
and test there on an isolated case
without risk to my
&lt;em&gt;real&lt;/em&gt;
reminders.
&lt;/p&gt;

 &lt;h3 id="debug-calendar"&gt;Showing &lt;em&gt;LOTS&lt;/em&gt; of dates&lt;/h3&gt;

&lt;p&gt;
Sometimes I find it easiest to
look at a spew of calendars
and eyeball the results.
I like to do this in
&lt;a href="https://blog.thechases.com/posts/remind/#views"&gt;month view&lt;/a&gt;,
producing a whole year
and paging through the results with my
&lt;code&gt;$PAGER&lt;/code&gt;
(&lt;code&gt;less&lt;/code&gt; in my case).
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem -c12 -w$COLUMNS 2020-1-1 | less
&lt;/pre&gt;
&lt;/figure&gt;
Alternatively, I will use the
&lt;a href="https://blog.thechases.com/posts/remind/#raw-output"&gt;&lt;code&gt;-p&lt;/code&gt;/&lt;code&gt;-s&lt;/code&gt;&lt;/a&gt;
flags to dump
all the reminders in a easy-to-filter format.
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem -p12 2020-1-1 | less
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="debug-printf"&gt;Printing values in
 &lt;code&gt;MSG&lt;/code&gt; output&lt;/h3&gt;

&lt;p&gt;
In the same classic tradition of
&lt;code&gt;printf()&lt;/code&gt;
debugging,
I also find it useful to see what
&lt;code&gt;remind&lt;/code&gt;
thinks a variable holds:
&lt;figure&gt;
&lt;pre&gt;
SET SomeVar &lt;span class="comment"&gt;some complex expression&lt;/span&gt;
REM … MSG SomeVar=&lt;mark&gt;[SomeVar]&lt;/mark&gt; on &lt;mark&gt;[$T]/[$U]&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;~/.reminders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

 &lt;h3 id="debug-flags"&gt;Debugging flags&lt;/h3&gt;

&lt;p&gt;
For really getting under the hood
&lt;code&gt;remind&lt;/code&gt;
has a
&lt;code&gt;-d&lt;/code&gt;
option that turns on additional debugging output.
It gets verbose quickly,
but invoking
&lt;figure&gt;
&lt;pre&gt;
&lt;label class="user"&gt;user$ &lt;/label&gt;rem &lt;mark&gt;-dextvlf&lt;/mark&gt; 2&amp;gt;&amp;amp;1 | less
&lt;/pre&gt;
&lt;/figure&gt;
turns on all the debugging flags.
The
&lt;code&gt;man&lt;/code&gt;-page
details each of those options,
but it has occasionally helped me
spot something I would have otherwise missed.
I find the
&lt;code&gt;x&lt;/code&gt;
(tracing expression evaluation),
&lt;code&gt;t&lt;/code&gt;
(tracing trigger-date computation),
and
&lt;code&gt;v&lt;/code&gt;
(dumping all variables)
help the most.
&lt;/p&gt;

&lt;h2 id="wrapup"&gt;Wrap up&lt;/h2&gt;

&lt;p&gt;
When I first heard about
&lt;code&gt;remind&lt;/code&gt;
it sounded interesting,
but I tried multiple times
before it finally clicked for me
(much like
&lt;code&gt;git&lt;/code&gt;
which took me about three serious attempts
before I felt like I understood it).
But once I figured out that I could express things
that no other calendar program let me do,
I have trouble keeping my primary calendar
in anything else.
I use
&lt;code&gt;rsync&lt;/code&gt;
and
&lt;code&gt;git&lt;/code&gt;
to keep my calendar in sync
across multiple machines
without exposing my calendar info
to third-parties
like Google
or Microsoft.
Hopefully this post gives you the motivation
to evaluate it for your hardest calendar test-cases.
&lt;/p&gt;

&lt;p&gt;
And if you come up with
strange questions,
use-cases,
or other recipes
for using
&lt;code&gt;remind&lt;/code&gt;
feel free to contact me via
&lt;a href="mailto:blog@tim.thechases.com?subject=How%20I%20use%20remind(1)"&gt;email&lt;/a&gt;,
&lt;a href="https://dianne.skoll.ca/mailman/listinfo/remind-fans"&gt;on the
&lt;code&gt;remind&lt;/code&gt;
mailing list&lt;/a&gt;,
or
&lt;a href="https://twitter.com/gumnos"&gt;over on Twitter
@gumnos&lt;/a&gt;
and I'll do my best
to help out.
&lt;/p&gt;&lt;/ul&gt;</description><category>awk</category><category>ed</category><category>remind</category><guid>https://blog.thechases.com/posts/remind/</guid><pubDate>Sun, 16 Feb 2020 19:35:08 GMT</pubDate></item><item><title>Setting up a terminal screen-reader on OpenBSD</title><link>https://blog.thechases.com/posts/bsd/setting-up-a-terminal-screen-reader-on-openbsd/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
 This post walks through installing the screen-reader
 &lt;code&gt;yasr&lt;/code&gt;
 and configuring it to use
 &lt;code&gt;speech-dispatcher&lt;/code&gt;
 with a
 &lt;abbr title="text to speech"&gt;TTS&lt;/abbr&gt;
 engine.
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;p&gt;
 This assumes you have successfully installed a stock OpenBSD system
 and that your user has
 &lt;kbd&gt;doas&lt;/kbd&gt;
 access to install software.
&lt;/p&gt;

&lt;h2&gt;Install required packages&lt;/h2&gt;
&lt;p&gt;
 First, install the requisite packages from the repos:
&lt;figure&gt;
&lt;pre&gt;
$ doas pkg_add speech-dispatcher espeak flite
&lt;/pre&gt;
&lt;/figure&gt;
 The
 &lt;code&gt;flite&lt;/code&gt;
 package is optional,
 but can provide you with an alternate set of voices.
&lt;/p&gt;

&lt;p&gt;
 Currently
 &lt;code&gt;yasr&lt;/code&gt;
 isn't available as an OpenBSD package,
 but it's fairly easy to install.
 You should be able to obtain
 the source code from the
 &lt;a href="https://github.com/mgorse/yasr"&gt;
 &lt;code&gt;yasr&lt;/code&gt;
 source repository&lt;/a&gt;.
&lt;/p&gt;&lt;pre&gt;
&lt;label class="user"&gt;$ &lt;/label&gt; git clone https://github.com/mgorse/yasr
&lt;/pre&gt;
 and to build it
&lt;figure&gt;
&lt;pre&gt;
$ cd yasr/yasr
$ make
&lt;/pre&gt;
&lt;/figure&gt;
 At some point, hopefully
 &lt;code&gt;yasr&lt;/code&gt;
 will be in the package repositories,
 making it as easy as
 &lt;kbd&gt;pkg_add yasr&lt;/kbd&gt;.


&lt;h2&gt;Ensure that sound works&lt;/h2&gt;
&lt;p&gt;
 If you haven't already,
 make sure that your OpenBSD box can play audio.
 You can check this by using 
&lt;figure&gt;
&lt;pre&gt;
$ aucat -i &lt;var&gt;somefile.wav&lt;/var&gt;
&lt;/pre&gt;
&lt;/figure&gt;
 If you don't have any
 &lt;code&gt;.wav&lt;/code&gt;
 files on your machine,
 &lt;code&gt;speech-dispatcher&lt;/code&gt;
 provides
 &lt;code&gt;/usr/local/share/sounds/speech-dispatcher/test.wav&lt;/code&gt;
 so
&lt;figure&gt;
&lt;pre&gt;
aucat -i /usr/local/share/sounds/speech-dispatcher/test.wav
&lt;/pre&gt;
&lt;/figure&gt;
 should play a short flourish.
 If it is too quiet,
 use
 &lt;a href="https://man.openbsd.org/mixerctl.1"&gt;
 &lt;code&gt;mixerctl&lt;/code&gt;
 &lt;/a&gt;
 to set the volume
 or try
 &lt;a href="https://www.openbsd.org/faq/faq13.html"&gt;
 additional OpenBSD audio troubleshooting
 &lt;/a&gt;
 techniques.
&lt;/p&gt;

&lt;h2&gt;Set up &lt;code&gt;speech-dispatcher&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;
 To create a user configuration file in
 &lt;code&gt;~/.config/speech-dispatcher/&lt;/code&gt;
 use
&lt;figure&gt;
&lt;pre&gt;
$ spd-conf
&lt;/pre&gt;
&lt;/figure&gt;
 with the default output of "espeak"
 (you can later try changing this to "flite"
 but I leave that as an exercise for the reader)
 and output via
 &lt;code&gt;libao&lt;/code&gt;.
 You can also set the default output speed.
 I prefer it a little faster than the default
 but it might be good to start with the default
 and then tune it later.
 As you configure,
 it should offer you the option to run some tests.
 If you choose "yes" for the first test,
 you should hear
 "Speech dispatcher works."
 I recommend skipping the other tests.
 With this in place, you should now have a
 &lt;code&gt;~/.config/speech-dispatcher/&lt;/code&gt;
 directory with various configuration settings within.
&lt;/p&gt;

&lt;p&gt;
 The
 &lt;code&gt;yasr&lt;/code&gt;
 screen-reader requires 
 &lt;code&gt;speech-dispatcher&lt;/code&gt;
 to listen on an inet socket
 rather than a
 Unix socket,
 so edit the config file with your favorite text editor
 and uncomment/change the CommunicationMethod from
 "unix_socket"
 to
 "inet_socket"
&lt;figure&gt;
&lt;pre&gt;
$ ed ~/.config/speech-dispatcher/speechd.conf
&lt;kbd&gt;/CommunicationMethod.*unix_socket&lt;/kbd&gt;
&lt;kbd&gt;a
CommunicationMethod "inet_socket"
.
&lt;/kbd&gt;
wq
&lt;/pre&gt;
&lt;/figure&gt;
 This does open a local port (6560)
 allowing other processes on the same machine
 to connect to
 &lt;code&gt;speech-dispatcher&lt;/code&gt;
 just so you're aware.
 If you choose to change the port,
 just make sure to also change it in the
 &lt;code&gt;yasr&lt;/code&gt;
 configuration later.
&lt;/p&gt;

&lt;h2&gt;Configure &lt;code&gt;yasr&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;
 Copy the default
 &lt;code&gt;yasr&lt;/code&gt;
 configuration file for modification:
&lt;figure&gt;
&lt;pre&gt;
$ cp ~/tmp/yasr-0.6.9/yasr.conf ~/.yasr.conf
&lt;/pre&gt;
&lt;/figure&gt;
 Make the following changes:
 &lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;
   Comment out all the
   &lt;code&gt;synthesizer=…&lt;/code&gt;
   lines except the
   &lt;code&gt;synthesizer=speech dispatcher&lt;/code&gt;
   line, uncommenting that one
  &lt;/li&gt;
  &lt;li&gt;
   Uncomment the
   &lt;code&gt;synthesizer port=127.0.0.1:6560&lt;/code&gt;
   line
  &lt;/li&gt;
  &lt;li&gt;
   Set the
   &lt;code&gt;shell=…&lt;/code&gt;
   to your preferred shell.
   OpenBSD doesn't install
   &lt;code&gt;bash&lt;/code&gt;
   at
   &lt;code&gt;/bin/bash&lt;/code&gt;
   so you'll either want to use
   &lt;code&gt;/bin/ksh&lt;/code&gt;
   or,
   (if you have
   &lt;code&gt;bash&lt;/code&gt;
   installed)
   &lt;code&gt;/usr/local/bin/bash&lt;/code&gt;,
   or your preferred default shell.
  &lt;/li&gt;
 &lt;/ul&gt;
 You can change other aspects of
 &lt;code&gt;yasr&lt;/code&gt;
 in here,
 but for now,
 leave the defaults.


&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;
 It took me a bit to figure out that
 &lt;code&gt;speech-dispatcher&lt;/code&gt;
 listens for a connection but times-out
 (by default, 5 seconds)
 and then quits.
 So you need to invoke it and then immediately launch
 &lt;code&gt;yasr&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
$ speech-dispatcher &amp;amp;&amp;amp; ~/tmp/yasr-0.6.9/yasr/yasr
&lt;/pre&gt;
&lt;/figure&gt;
 If all is working successfully
 it should start
 &lt;code&gt;yasr&lt;/code&gt;
 and you should hear it read your prompt.
&lt;/p&gt;

&lt;h2&gt;Getting started with &lt;code&gt;yasr&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;
 The
 &lt;code&gt;~/.yasr.conf&lt;/code&gt;
 file lists all of the keyboard commands
 for both normal/interactive mode
 (the default)
 and for review mode.
 Keys are given by ASCII value written in hex.
&lt;/p&gt;
&lt;table&gt;
 &lt;caption&gt;Normal/interactive mode commands&lt;/caption&gt;
 &lt;thead&gt;
  &lt;tr&gt;
   &lt;th&gt;Hex value(s)&lt;/th&gt;
   &lt;th&gt;Keyboard command&lt;/th&gt;
   &lt;th&gt;Action&lt;/th&gt;
  &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
  &lt;tr&gt;&lt;td&gt;0x01&lt;/td&gt;&lt;td&gt;Ctrl+A&lt;/td&gt;&lt;td&gt;say cursor&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x0c&lt;/td&gt;&lt;td&gt;Ctrl+L&lt;/td&gt;&lt;td&gt;say line&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x0e&lt;/td&gt;&lt;td&gt;Ctrl+N&lt;/td&gt;&lt;td&gt;bypass&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x18&lt;/td&gt;&lt;td&gt;Ctrl+X&lt;/td&gt;&lt;td&gt;flush&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b0b&lt;/td&gt;&lt;td&gt;Ctrl+Alt+K&lt;/td&gt;&lt;td&gt;keyboard wizard&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b0f&lt;/td&gt;&lt;td&gt;Ctrl+Alt+O&lt;/td&gt;&lt;td&gt;options menu&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b13&lt;/td&gt;&lt;td&gt;Ctrl+Alt+S&lt;/td&gt;&lt;td&gt;write configuration&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b30&lt;/td&gt;&lt;td&gt;Alt+0&lt;/td&gt;&lt;td&gt;say line:256&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b62&lt;/td&gt;&lt;td&gt;Alt+b&lt;/td&gt;&lt;td&gt;say character:-1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b63&lt;/td&gt;&lt;td&gt;Alt+c&lt;/td&gt;&lt;td&gt;say character&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b64&lt;/td&gt;&lt;td&gt;Alt+d&lt;/td&gt;&lt;td&gt;say word&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b65&lt;/td&gt;&lt;td&gt;Alt+e&lt;/td&gt;&lt;td&gt;read cursor to bottom&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b69&lt;/td&gt;&lt;td&gt;Alt+i&lt;/td&gt;&lt;td&gt;reinitialize&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b6b&lt;/td&gt;&lt;td&gt;Alt+k&lt;/td&gt;&lt;td&gt;say line:-1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b6c&lt;/td&gt;&lt;td&gt;Alt+l&lt;/td&gt;&lt;td&gt;say line&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b6d&lt;/td&gt;&lt;td&gt;Alt+m&lt;/td&gt;&lt;td&gt;say line:1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b72&lt;/td&gt;&lt;td&gt;Alt+r&lt;/td&gt;&lt;td&gt;toggle review mode&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b74&lt;/td&gt;&lt;td&gt;Alt+t&lt;/td&gt;&lt;td&gt;read top to cursor&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b77&lt;/td&gt;&lt;td&gt;Alt+w&lt;/td&gt;&lt;td&gt;read screen&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b78&lt;/td&gt;&lt;td&gt;Alt+x&lt;/td&gt;&lt;td&gt;flush:1&lt;/td&gt;&lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
 When you enter review-mode,
 commands go to
 &lt;code&gt;yasr&lt;/code&gt;
 directly instead of being passed to the application.
&lt;/p&gt;&lt;p&gt;

&lt;/p&gt;&lt;table&gt;
 &lt;caption&gt;Review-mode commands&lt;/caption&gt;
 &lt;thead&gt;
  &lt;tr&gt;
   &lt;th&gt;Hex value(s)&lt;/th&gt;
   &lt;th&gt;Keyboard command&lt;/th&gt;
   &lt;th&gt;Action&lt;/th&gt;
  &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
  &lt;tr&gt;&lt;td&gt;0x20&lt;/td&gt;&lt;td&gt;space&lt;/td&gt;&lt;td&gt;say review cursor&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x24&lt;/td&gt;&lt;td&gt;$&lt;/td&gt;&lt;td&gt;end of line&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x28&lt;/td&gt;&lt;td&gt;(&lt;/td&gt;&lt;td&gt;previous paragraph&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x29&lt;/td&gt;&lt;td&gt;)&lt;/td&gt;&lt;td&gt;next paragraph&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x3c&lt;/td&gt;&lt;td&gt;&amp;lt;&lt;/td&gt;&lt;td&gt;search back&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x3e&lt;/td&gt;&lt;td&gt;&amp;gt;&lt;/td&gt;&lt;td&gt;search forward&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x5e&lt;/td&gt;&lt;td&gt;^&lt;/td&gt;&lt;td&gt;beginning of line&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x60&lt;/td&gt;&lt;td&gt;`&lt;/td&gt;&lt;td&gt;ascii&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x62&lt;/td&gt;&lt;td&gt;b&lt;/td&gt;&lt;td&gt;say character:-1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x63&lt;/td&gt;&lt;td&gt;c&lt;/td&gt;&lt;td&gt;say character&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x65&lt;/td&gt;&lt;td&gt;e&lt;/td&gt;&lt;td&gt;read cursor to bottom&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x66&lt;/td&gt;&lt;td&gt;f&lt;/td&gt;&lt;td&gt;find&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x6a&lt;/td&gt;&lt;td&gt;j&lt;/td&gt;&lt;td&gt;say line:1:0&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x6b&lt;/td&gt;&lt;td&gt;k&lt;/td&gt;&lt;td&gt;say line:-1:0&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x6c&lt;/td&gt;&lt;td&gt;l&lt;/td&gt;&lt;td&gt;say line&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x6d&lt;/td&gt;&lt;td&gt;m&lt;/td&gt;&lt;td&gt;say line:1:0&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x6e&lt;/td&gt;&lt;td&gt;n&lt;/td&gt;&lt;td&gt;bypass&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x74&lt;/td&gt;&lt;td&gt;t&lt;/td&gt;&lt;td&gt;read top to cursor&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x76&lt;/td&gt;&lt;td&gt;v&lt;/td&gt;&lt;td&gt;set option:6&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x77&lt;/td&gt;&lt;td&gt;w&lt;/td&gt;&lt;td&gt;read screen&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x78&lt;/td&gt;&lt;td&gt;x&lt;/td&gt;&lt;td&gt;say word:1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x7a&lt;/td&gt;&lt;td&gt;z&lt;/td&gt;&lt;td&gt;say word:-1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b73&lt;/td&gt;&lt;td&gt;Alt+s&lt;/td&gt;&lt;td&gt;set option:5&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b5b41&lt;/td&gt;&lt;td&gt;Up arrow&lt;/td&gt;&lt;td&gt;say line:-1:1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b5b42&lt;/td&gt;&lt;td&gt;Down arrow&lt;/td&gt;&lt;td&gt;say line:1:1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b5b44&lt;/td&gt;&lt;td&gt;Left arrow&lt;/td&gt;&lt;td&gt;say character:-1&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;0x1b5b43&lt;/td&gt;&lt;td&gt;Right arrow&lt;/td&gt;&lt;td&gt;say character:1&lt;/td&gt;&lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
 I'm not quite sure what several of those do,
 but feel free to experiment with them.
&lt;/p&gt;

&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;
 From here, you can tweak your
 &lt;code&gt;yasr&lt;/code&gt;
 keyboard commands in your
 &lt;code&gt;~/.yasr.conf&lt;/code&gt;
 or change your
 &lt;code&gt;speech-dispatcher&lt;/code&gt;
 settings in your
 &lt;code&gt;~/.config/speech-dispatcher/speechd.conf&lt;/code&gt;
 and
 &lt;code&gt;~/.config/speech-dispatcher/modules/espeak.conf&lt;/code&gt;
 files.
&lt;/p&gt;

&lt;p&gt;
 You might copy/link the
 &lt;code&gt;yasr&lt;/code&gt;
 binary into some place on your
 &lt;code&gt;$PATH&lt;/code&gt;
 such as
&lt;figure&gt;
&lt;pre&gt;
$ mkdir ~/bin
$ ln -s ~/tmp/yasr-0.6.9/yasr/yasr ~/bin
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
 If you're feeling adventuous and prefer
 &lt;code&gt;flite&lt;/code&gt;
 you might try configuring
 &lt;code&gt;speech-dispatcher&lt;/code&gt;
 to use that instead.
&lt;/p&gt;

&lt;p&gt;
 Finally, it helps to have a suite of applications
 that play nicely with text-to-speech output.
 A few you might want to try:
 &lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;Local mail:
   &lt;a href="https://man.openbsd.org/mail.1"&gt;
   &lt;code&gt;mail&lt;/code&gt;/&lt;code&gt;mailx&lt;/code&gt;
   &lt;/a&gt;
   &lt;/li&gt;
  &lt;li&gt;Remote mail:
   &lt;code&gt;s-nail&lt;/code&gt;
   (available as a package)
   &lt;/li&gt;
  &lt;li&gt;Calendar:
   &lt;code&gt;remind&lt;/code&gt;
   (available as a package)
   &lt;/li&gt;
  &lt;li&gt;Web-browsing:
   &lt;code&gt;lynx&lt;/code&gt;
   or
   &lt;code&gt;edbrowse&lt;/code&gt;
   &lt;/li&gt;
  &lt;li&gt;RSS:
   &lt;code&gt;rss2email&lt;/code&gt;
   or 
   &lt;code&gt;newsboat&lt;/code&gt;
   (both available as as packages)
   &lt;/li&gt;
  &lt;li&gt;IRC:
   &lt;code&gt;ii&lt;/code&gt;
   (available as a package)
   or
   &lt;code&gt;tinyirc&lt;/code&gt;
   (not currently available as a package)
   &lt;/li&gt;
  &lt;li&gt;Task management:
   &lt;code&gt;taskwarrior&lt;/code&gt;
   or
   &lt;code&gt;devtodo&lt;/code&gt;
   (both available as as packages)
   &lt;/li&gt;
  &lt;li&gt;Music:
   &lt;code&gt;cmus&lt;/code&gt;
   or
   &lt;code&gt;mpd/mpc&lt;/code&gt;
   (both available as as packages)
   &lt;/li&gt;
  &lt;li&gt;Terminal multiplexing:
   &lt;a href="https://man.openbsd.org/tmux.1"&gt;
   &lt;code&gt;tmux&lt;/code&gt;
   &lt;/a&gt;
   or
   GNU &lt;code&gt;screen&lt;/code&gt;
   (available as packages)
   It helps to either hide the status-bar
   or remove the clock
   so that it doesn't constantly spam you with time updates
   &lt;/li&gt;
  &lt;li&gt;Games:
   &lt;ul&gt;
    &lt;li&gt;&lt;a href="https://man.openbsd.org/intro.6"&gt;bsdgames&lt;/a&gt; collection&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;frotz&lt;/code&gt; interpreter for Infocom-style text adventures&lt;/li&gt;
   &lt;/ul&gt;&lt;/li&gt;
 &lt;/ul&gt;</description><category>a11y</category><category>accessibility</category><category>BSD</category><category>ed</category><category>screen-reader</category><guid>https://blog.thechases.com/posts/bsd/setting-up-a-terminal-screen-reader-on-openbsd/</guid><pubDate>Sun, 19 May 2019 23:52:20 GMT</pubDate></item><item><title>CLI best practices</title><link>https://blog.thechases.com/posts/cli/cli-best-practices/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
 This article pulls together a number of tips
 and best practices
 for building
 &lt;abbr title="Command Line Interface"&gt;CLI&lt;/abbr&gt;
 applications.
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;h2&gt;Input&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;
  Use standard library input functions
  to read from
  &lt;tt&gt;stdin&lt;/tt&gt;
  that can play nicely with
  &lt;tt&gt;rlwrap(1)&lt;/tt&gt;
  and other input sources.
 &lt;/li&gt;
 &lt;li&gt;
  If using readline functionality,
  provide an option to disable it.
 &lt;/li&gt;
 &lt;li&gt;
  Only use readline functionality
  if you have used
  &lt;tt&gt;isatty(3)&lt;/tt&gt;
  to test if
  &lt;tt&gt;stdin&lt;/tt&gt;
  is a terminal.
 &lt;/li&gt;
 &lt;li&gt;
  Don't pass sensitive information
  like passwords and API tokens
  on the command-line
  since they will appear in the output of
  &lt;tt&gt;ps(1)&lt;/tt&gt;.
  Instead store them
  in a file with appropriate
  ownership &amp;amp; permissions
  or pass them to the application
  in an environment variable.
 &lt;/li&gt;
 &lt;li&gt;
  If you need to interactively read sensitive information
  such as passwords,
  read directly from
  &lt;code&gt;/dev/tty&lt;/code&gt;
  or use
  &lt;tt&gt;curses&lt;/tt&gt;/&lt;tt&gt;ncurses&lt;/tt&gt;
  calls to prevent sensitive input from displaying on the screen.
 &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Output&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;
  Though obvious, put errors on
  &lt;tt&gt;stderr&lt;/tt&gt;
  and output on
  &lt;tt&gt;stdout&lt;/tt&gt;.
 &lt;/li&gt;
 &lt;li&gt;
  Only show progress meters if requested via a command-line option.
 &lt;/li&gt;
 &lt;li&gt;
  Allow tunable output verbosity,
  preferrably including a completely silent mode
  (often
  &lt;code&gt;-q&lt;/code&gt;/&lt;code&gt;--quiet&lt;/code&gt;
  or
  &lt;code&gt;-s&lt;/code&gt;/&lt;code&gt;--silent&lt;/code&gt;)
  and increasing levels of info/debugging output
  (often one or more
  &lt;code&gt;-v&lt;/code&gt;/&lt;code&gt;--verbose&lt;/code&gt;
  options).
 &lt;/li&gt;
 &lt;li&gt;
  Make color optional/smart
  by respecting
  &lt;a href="https://no-color.org/"&gt;&lt;var&gt;$NO_COLOR&lt;/var&gt;&lt;/a&gt;
  if set, or
  &lt;var&gt;$COLOR={always,never,auto}&lt;/var&gt;.
  If
  &lt;var&gt;$COLOR&lt;/var&gt;
  (or
  &lt;var&gt;$PROGNAME_COLOR&lt;/var&gt;)
  is not set,
  use
  &lt;tt&gt;isatty(3)&lt;/tt&gt;
  to establish a default.
 &lt;/li&gt;
 &lt;li&gt;
  Default output format should have one record per line
  with easy-to-discern/use column delimiters,
  not one record spanning multiple lines
  (unless requested as an output option like sqlite's
  "&lt;kbd&gt;.mode line&lt;/kbd&gt;").
  Use command-line options to specify other output formats such as
  &lt;a href="http://json.org/"&gt;JSON&lt;/a&gt;,
  &lt;a href="https://www.w3.org/XML/"&gt;XML&lt;/a&gt;,
  &lt;a href="https://yaml.org/"&gt;YAML&lt;/a&gt;,
  &lt;a href="https://en.wikipedia.org/wiki/S-expression"&gt;S-expressions&lt;/a&gt;,
  &lt;a href="https://en.wikipedia.org/wiki/Comma-separated_values"&gt;CSV&lt;/a&gt;
  or
  &lt;a href="https://en.wikipedia.org/wiki/Tab-separated_values" tab separated values""&gt;TSV&lt;/a&gt;.
 &lt;/li&gt;
 &lt;li&gt;
  Don't produce binary output on
  &lt;tt&gt;stdout&lt;/tt&gt;
  unless explicitly requested.
 &lt;/li&gt;
 &lt;li&gt;
  Print numbers unformatted
  unless requested otherwise.
  This might include internationalizing/localizing things like
  currency markers,
  thousands separators,
  and decimal separators.
  Or this might involve humanizing units,
  often with a
  &lt;code&gt;-k&lt;/code&gt; (Kilobytes),
  &lt;code&gt;-m&lt;/code&gt; (Megabytes),
  &lt;code&gt;-g&lt;/code&gt; (Gigibytes),
  or
  &lt;code&gt;-h&lt;/code&gt;/&lt;code&gt;--humanize&lt;/code&gt;
  flag.
 &lt;/li&gt;
 &lt;li&gt;
  Offer formatting control for dates/time including
  internationalizing/localizing
  or explicitly specifying a format string.
  If using a format string,
  stick to one standard:
  preferrably
  &lt;a href="http://strftime.org/"&gt;&lt;tt&gt;strftime(3)&lt;/tt&gt;&lt;/a&gt;
  strings
  (&lt;code&gt;%Y-%m-%d %H:%M&lt;/code&gt;),
  but
  &lt;a href="http://cldr.unicode.org/translation/date-time"&gt;CLDR format date strings&lt;/a&gt;,
  or
  &lt;a href="http://php.net/manual/en/function.date.php"&gt;PHP style date format strings&lt;/a&gt;
  are also common in certain communities.
 &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Command-line options &amp;amp; switches&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;
  Provide both
  short
  (&lt;code&gt;-x&lt;/code&gt;)
  and long
  (&lt;code&gt;--long-option&lt;/code&gt;)
  versions of options.
  On non-GNU systems,
  long options are less expected.
 &lt;/li&gt;
 &lt;li&gt;
  Use common command-line option conventions:
  &lt;ul&gt;
   &lt;li&gt;
    &lt;code&gt;-h&lt;/code&gt;/&lt;code&gt;--help&lt;/code&gt;
    should give an example of usage,
    a description of what the program does,
    along with a list of options and their descriptions.
    Note that
    &lt;code&gt;-h&lt;/code&gt;
    may conflict with "human output".
   &lt;/li&gt;
   &lt;li&gt;
    &lt;code&gt;-v&lt;/code&gt;/&lt;code&gt;--version&lt;/code&gt;
    should give version information.
    Note that
    &lt;code&gt;-v&lt;/code&gt;
    may conflict with verbosity flags.
   &lt;/li&gt;
   &lt;li&gt;
    If a command has side-effets
    such as deleting/renaming/modifying files
    or transferring large volumes of data,
    providea
    &lt;code&gt;-n&lt;/code&gt;/&lt;code&gt;--dry-run&lt;/code&gt;
    to let the user see what will happen
    without actually performing the requested action.
   &lt;/li&gt;
   &lt;li&gt;
    &lt;code&gt;--&lt;/code&gt;
    to separate options and arguments.
   &lt;/li&gt;
   &lt;li&gt;
    Use
    &lt;code&gt;-&lt;/code&gt;
    as a file-name to read from
    &lt;tt&gt;stdin&lt;/tt&gt;.
   &lt;/li&gt;
   &lt;li&gt;
    Use
    &lt;code&gt;@&lt;var&gt;filename&lt;/var&gt;&lt;/code&gt;
    to read data from
    &lt;var&gt;filename&lt;/var&gt;
    instead of data provided directly on the command-line.
   &lt;/li&gt;
  &lt;/ul&gt;
 &lt;/li&gt;
 &lt;li&gt;
  For long options that take arguments,
  allow either an equals sign
  or a space
  to separate the option
  from its arguments.
  E.g.
  &lt;code&gt;--name=Sam&lt;/code&gt;
  or
  &lt;code&gt;--name Sam&lt;/code&gt;.
 &lt;/li&gt;
 &lt;li&gt;
  It can be beneficial to provide an option
  to expose expected completions
  for shell command-line completion functions.
 &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Defaults&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;
  If an option can have a default value,
  provide one.
 &lt;/li&gt;
 &lt;li&gt;
  If output is truncated to the width of the tty,
  use curses calls to determine the width
  and fall back to 80 columns otherwise.
 &lt;/li&gt;
 &lt;li&gt;
  If spawning an external editor,
  check the following environment variables and binaries in order
  &lt;ol&gt;
   &lt;li&gt;&lt;var&gt;$PROGNAME_EDITOR&lt;/var&gt;&lt;/li&gt;
   &lt;li&gt;&lt;var&gt;$VISUAL&lt;/var&gt;&lt;/li&gt;
   &lt;li&gt;&lt;var&gt;$EDITOR&lt;/var&gt;&lt;/li&gt;
   &lt;li&gt;&lt;tt&gt;vi(1)&lt;/tt&gt;&lt;/li&gt;
   &lt;li&gt;&lt;tt&gt;ed(1)&lt;/tt&gt;&lt;/li&gt;
  &lt;/ol&gt;
  using the first one that matches.
 &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Other considerations&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;
  Where possible,
  allow the user to specify options
  in any order.
  Requiring options in a particular order
  makes things more fragile.
 &lt;/li&gt;
 &lt;li&gt;
  If one tool provides multiple actions,
  use the form
  "&lt;code&gt;programname &lt;var&gt;{global options}&lt;/var&gt; &lt;var&gt;action&lt;/var&gt; &lt;var&gt;{action-specific options}&lt;/var&gt;&lt;/code&gt;".
  Examples include
  &lt;ul&gt;
   &lt;li&gt;git (&lt;code&gt;git commit&lt;/code&gt;)&lt;/li&gt;
   &lt;li&gt;Subversion (&lt;code&gt;svn commit&lt;/code&gt;)&lt;/li&gt;
   &lt;li&gt;Mercurial (&lt;code&gt;hg commit&lt;/code&gt;)&lt;/li&gt;
   &lt;li&gt;CVS (&lt;code&gt;cvs commit&lt;/code&gt;)&lt;/li&gt;
   &lt;li&gt;taskwarrior (&lt;code&gt;task list&lt;/code&gt;)&lt;/li&gt;
   &lt;li&gt;managing services (&lt;code&gt;service httpd start&lt;/code&gt;)&lt;/li&gt;
  &lt;/ul&gt;
 &lt;/li&gt;
 &lt;li&gt;
  Use
  &lt;var&gt;$LC_CTYPE&lt;/var&gt;
  to determine the encoding of
  &lt;tt&gt;stdin&lt;/tt&gt;/&lt;tt&gt;stdout&lt;/tt&gt;
  but allow the user to specify alternate encodings
  and error/replacement strategies.
 &lt;/li&gt;
 &lt;li&gt;
  Return a 0 exit status on success.
  Return a non-0 exit status
  if any errors occurred,
  preferrably using standard values from
  &lt;code&gt;/usr/include/sysexits.h&lt;/code&gt;.
 &lt;/li&gt;
 &lt;li&gt;
  Store local user configuration in
  &lt;code&gt;~/.config/&lt;var&gt;$PROGNAME&lt;/var&gt;/&lt;/code&gt;
 &lt;/li&gt;
 &lt;li&gt;
  Settings should be determined in the following order
  (from lowest priority to highest precedence)
  &lt;ol&gt;
   &lt;li&gt;
    built in configuration
   &lt;/li&gt;
   &lt;li&gt;
    system-wide configuration from either
    &lt;code&gt;/etc/&lt;var&gt;${PROGNAME}&lt;/var&gt;rc&lt;/code&gt;,
    &lt;code&gt;/etc/&lt;var&gt;${PROGNAME}&lt;/var&gt;.conf&lt;/code&gt;,
    &lt;code&gt;/etc/&lt;var&gt;${PROGNAME}&lt;/var&gt;/*&lt;/code&gt;,
    &lt;code&gt;/usr/local/etc/&lt;var&gt;${PROGNAME}&lt;/var&gt;rc&lt;/code&gt;
    or
    &lt;code&gt;/usr/local/etc/&lt;var&gt;${PROGNAME}&lt;/var&gt;.conf&lt;/code&gt;
   &lt;/li&gt;
   &lt;li&gt;
    user configuration file in
    &lt;code&gt;~/.config/&lt;var&gt;$PROGNAME&lt;/var&gt;/*&lt;/code&gt;
   &lt;/li&gt;
   &lt;li&gt;
    environment variables
   &lt;/li&gt;
   &lt;li&gt;
    command-line options
   &lt;/li&gt;
  &lt;/ol&gt;
 &lt;/li&gt;
 &lt;li&gt;
  On Microsoft Windows,
  these conventions may differ.
  Notably, the use of
  &lt;code&gt;/option:&lt;var&gt;value&lt;/var&gt;&lt;/code&gt;
  instead of
  &lt;code&gt;--option=&lt;var&gt;value&lt;/var&gt;&lt;/code&gt;
 &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Other resources&lt;/h2&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://www.gnu.org/prep/standards/standards.html#User-Interfaces"&gt;GNU standards for CLIs&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://softwareengineering.stackexchange.com/questions/307467/what-are-good-habits-for-designing-command-line-arguments"&gt;Stack Overflow discussion on good CLIs&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://eng.localytics.com/exploring-cli-best-practices/"&gt;Exploring CLI Best Practices
&lt;/a&gt; by Kevin Deisz&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://zapier.com/engineering/how-to-cli/"&gt;How to CLI&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://dev.to/nickparsons/crafting-a-command-line-experience-that-developers-love-4451"&gt;Crafting a command line experience that developers will love&lt;/a&gt; by Nick Parsons&lt;/li&gt;
&lt;/ul&gt;</description><category>ed</category><guid>https://blog.thechases.com/posts/cli/cli-best-practices/</guid><pubDate>Sat, 20 Apr 2019 01:08:31 GMT</pubDate></item><item><title>HOFFA: httpd, OpenBSD, flat-files, and awk</title><link>https://blog.thechases.com/posts/bsd/hoffa/</link><dc:creator>Tim Chase</dc:creator><description>Upon learning that
&lt;a href="https://openbsd.org"&gt;OpenBSD&lt;/a&gt;
was
&lt;a href="https://www.openbsd.org/faq/upgrade61.html"&gt;discontinuing
&lt;code&gt;sqlite&lt;/code&gt;
in the base system&lt;/a&gt;,
I joked that the
&lt;a href="https://learnbchs.org/"&gt;BCHS&lt;/a&gt;
web stack would need to drop
&lt;code&gt;sqlite&lt;/code&gt;
and investigate new options for a web-stack available purely in the
OpenBSD base system.
So continuing that joke, I proposed the
&lt;a href="https://twitter.com/gumnos/status/1005155069296304130" title="httpd, OpenBSD, flat-files, and awk"&gt;HOFFA&lt;/a&gt;
web stack:
&lt;code&gt;httpd&lt;/code&gt;,
OpenBSD, flat-files, and
&lt;code&gt;awk&lt;/code&gt;.

&lt;!-- TEASER_END --&gt;

&lt;h2 id="server"&gt;Server configuration&lt;/h2&gt;

&lt;p&gt;
 This walk-through assumes you've managed to follow one of the many fine
 &lt;a href="https://www.openbsd.org/faq/faq4.html"&gt;OpenBSD
 installation guides&lt;/a&gt;
 and now have a fresh OpenBSD system with no packages installed.
&lt;/p&gt;

&lt;p&gt;
 Begin by copying the template
 &lt;code&gt;httpd.conf&lt;/code&gt;
 into the
 &lt;code&gt;/etc&lt;/code&gt;
 directory:
&lt;figure&gt;
&lt;pre&gt;
$ doas cp /etc/examples/httpd.conf /etc/
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
 Next, edit
 &lt;code&gt;/etc/httpd.conf&lt;/code&gt;
 and in your
 &lt;code&gt;server&lt;/code&gt;
 block, add a
 &lt;code&gt;location&lt;/code&gt;
 directive to point to your
 &lt;code&gt;cgi-bin/&lt;/code&gt;
 directory (this is relative to your
 &lt;code&gt;$CHROOT&lt;/code&gt;
 so the absolute path ends up being
 &lt;code&gt;/var/www/cgi-bin/&lt;/code&gt;
 by default)
&lt;figure&gt;
&lt;pre&gt;
server "example.com" {
    ⋮
    location "/cgi-bin/*" {
        fastcgi
        root "/"
    }
    ⋮
}
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
 Now with the web server configured, it, and the
 &lt;code&gt;slowcgi&lt;/code&gt;
 proxy need to be enabled.
&lt;figure&gt;
&lt;pre&gt;
$ doas rcctl enable slowcgi
$ doas rcctl enable httpd
$ doas rcctl start slowcgi
$ doas rcctl check slowcgi
$ doas rcctl start httpd
&lt;/pre&gt;
&lt;/figure&gt;
 (the
 &lt;code&gt;rcctl check slowcgi&lt;/code&gt;
 may be optional but it doesn't hurt to confirm that it's running)
&lt;/p&gt;

&lt;h2 id="chroot"&gt;Populating binaries &amp;amp; libraries in the
 &lt;code&gt;$CHROOT&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;
 With the server and CGI proxy running, binaries need to be put where
 they'll be found.
 As this is
 &lt;abbr title="httpd, OpenBSD, flat-files, and awk"&gt;HOFFA&lt;/abbr&gt;
 this uses
 &lt;code&gt;awk&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
$ doas cp `which awk` /var/www/bin/
&lt;/pre&gt;
&lt;/figure&gt;
 then create the library directories and copy in the needed libraries.
 First, find out which libraries
 &lt;code&gt;awk&lt;/code&gt;
 needs:
&lt;figure&gt;
&lt;pre&gt;
$ ldd `which awk`
/usr/bin/awk:
 Start    End      Type  Open Ref GrpRef Name
 17b0e000 37b20000 exe   1    0   0      /usr/bin/awk
 05321000 2532c000 rlib  0    1   0      /usr/lib/libm.so.10.1
 07f46000 27f76000 rlib  0    1   0      /usr/lib/libc.so.92.3
 02875000 02875000 ld.so 0    1   0      /usr/libexec/ld.so
&lt;/pre&gt;
&lt;/figure&gt;
 so we need to create those library directories in the
 &lt;code&gt;$CHROOT&lt;/code&gt;
&lt;figure&gt;
&lt;pre&gt;
$ doas mkdir -p /var/www/usr/lib /var/www/usr/libexec
&lt;/pre&gt;
&lt;/figure&gt;
 and copy the libraries in
&lt;figure&gt;
&lt;pre&gt;
$ doas cp /usr/lib/lib[mc].so* /var/www/usr/lib/
$ doas cp /usr/libexec/ld.so /var/www/usr/libexec/
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2 id="hello"&gt;Hello world&lt;/h2&gt;
&lt;p&gt;
 Now with the web server &amp;amp;
 &lt;code&gt;slowcgi&lt;/code&gt;
 in place, along with the required binaries &amp;amp; libraries, it's time
 to write some code.
&lt;figure&gt;
&lt;pre&gt;
$ cd /var/www/cgi-bin
$ doas ed example.awk
a
#!/bin/awk -f
BEGIN {
 printf("Status: 200 OK\r\n");
 printf("Content-type: text/plain\r\n\r\n");
 print "Hello", ENVIRON["REMOTE_ADDR"], "from awk!";
}
.
wq
$ doas chown www:www example.awk
$ doas chmod +x example.awk
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
With this in place, it should now be possible to point your browser at
your newly configured script.  If your browser is on the same machine
you can visit
&lt;a href="http://localhost/cgi-bin/example.awk"&gt;http://localhost/cgi-bin/example.awk&lt;/a&gt;
or if it's on a remote machine, use its domain-name or IP address
&lt;a href="http://example.com/cgi-bin/example.awk"&gt;http://example.com/cgi-bin/example.awk&lt;/a&gt;
&lt;/p&gt;</description><category>awk</category><category>ed</category><category>HOFFA</category><guid>https://blog.thechases.com/posts/bsd/hoffa/</guid><pubDate>Sat, 09 Jun 2018 00:17:32 GMT</pubDate></item><item><title>Unsorted ed(1) Tips and Tricks</title><link>https://blog.thechases.com/posts/cli/unsorted-ed-tips-and-tricks/</link><dc:creator>Tim Chase</dc:creator><description>&lt;h2&gt;Edit text like a pro with ed(1)&lt;/h2&gt;
&lt;p&gt;
I came across
&lt;a href="http://blog.terminal.com/vi-tips-and-tricks/"&gt;Ten
unsorted vi/vim tricks, volumes one&lt;/a&gt;
&amp;amp;
&lt;a href="https://blog.terminal.com/ten-unsorted-vi-vim-tricks-volume-2/"&gt;Ten
unsorted vi/vim tricks, volume two&lt;/a&gt;
and thought I'd compile a similar list for
&lt;code&gt;ed(1)&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
In this post I will present counterpoints with easy &amp;amp;
but useful tricks using
&lt;code&gt;ed(1)&lt;/code&gt;
commands and, in the Unix philosophy, external tools.
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;p&gt;
You might ask, &lt;a href="https://blog.thechases.com/posts/cli/why-ed1/"&gt;&lt;strong&gt;why
&lt;code&gt;ed(1)&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;?

&lt;/p&gt;

&lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;&lt;code&gt;ed(1)&lt;/code&gt; is always&lt;sup&gt;*&lt;/sup&gt; there.&lt;/strong&gt;
  Since &lt;code&gt;ed(1)&lt;/code&gt; is required by POSIX, you should
  find it in any *nix/Linux distribution.  Well, it
  &lt;em&gt;should&lt;/em&gt; be there.  Sadly, many distros are now
  dropping &lt;code&gt;ed(1)&lt;/code&gt; from their core installations,
  making it an optional add-on package.
 &lt;/li&gt;

 &lt;li&gt;&lt;strong&gt;&lt;code&gt;ed(1)&lt;/code&gt; is small but mighty.&lt;/strong&gt;
 On various systems at my disposal, &lt;code&gt;ed(1)&lt;/code&gt;
 clocks in at between 51k and 183k, while &lt;code&gt;nano&lt;/code&gt;
 is 2-4x as large, &lt;code&gt;vi/vim&lt;/code&gt;
 is 3-20x as large, and &lt;code&gt;emacs&lt;/code&gt;
 clocks in at over
 &lt;span title="Insert standard joke about Eight Megabytes And Continually Swapping"&gt;8 megs&lt;/span&gt;.
 Yet &lt;code&gt;ed(1)&lt;/code&gt; provides a lot of functionality in
 those meager few bytes.
 &lt;/li&gt;

 &lt;li&gt;&lt;strong&gt;&lt;code&gt;ed(1)&lt;/code&gt; needs no configuration.&lt;/strong&gt;
 You don't have to worry about sitting down at a foreign
 machine and now knowing how &lt;code&gt;ed(1)&lt;/code&gt;
 is configured.  Yes, there are some subtle differences
 between GNU &lt;code&gt;ed(1)&lt;/code&gt; and BSD &lt;code&gt;ed(1)&lt;/code&gt;
 but the vast majority is the same.
 &lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;Getting some help&lt;/h2&gt;
&lt;p&gt;
&lt;code&gt;ed(1)&lt;/code&gt;
is known for its terse "?" reply to any issue.  You can
request in-line help with the "&lt;kbd&gt;h&lt;/kbd&gt;" command for a
bit of a hint.  Additionally, the &lt;code&gt;man&lt;/code&gt; page
should cover everything you need to know.
&lt;figure&gt;
&lt;pre&gt;
s/oof/bar/
?
h
No match
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Search and replace&lt;/h2&gt;
&lt;p&gt;
To search, use the "&lt;kbd&gt;/&lt;/kbd&gt;" or "&lt;kbd&gt;?&lt;/kbd&gt;" command
followed by your search pattern.
&lt;figure&gt;
&lt;pre&gt;
/pattern/n
18 this line contains "pattern"
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;
To search and replace on the current line, use the
"&lt;kbd&gt;s&lt;/kbd&gt;" command:
&lt;figure&gt;
&lt;pre&gt;
s/foo/bar/g
&lt;/pre&gt;
&lt;/figure&gt;
or, apply the changes over a range:
&lt;figure&gt;
&lt;pre&gt;
4,18s/foo/bar/g
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Show line numbers&lt;/h2&gt;
&lt;p&gt;
To show line numbers in &lt;code&gt;ed(1)&lt;/code&gt;
add the suffix "&lt;kbd&gt;n&lt;/kbd&gt;" to your command, for example
&lt;figure&gt;
&lt;pre&gt;
,s/foo/bar/n
g/pattern/n
10zn
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Execute an external command from within
&lt;code&gt;ed(1)&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;
Use the "&lt;kbd&gt;!&lt;/kbd&gt;" command to execute 
&lt;figure&gt;
&lt;pre&gt;
!ls
file.txt
!
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Insert an existing file into the current one&lt;/h2&gt;
&lt;p&gt;
In case you need to insert the contents of a file into the
one you're currently writing, use the "&lt;kbd&gt;r&lt;/kbd&gt;"
command.
&lt;figure&gt;
&lt;pre&gt;
r file.txt
1625
&lt;/pre&gt;
&lt;/figure&gt;
Note that &lt;code&gt;ed(1)&lt;/code&gt; lets you know how many bytes
were added to your file.
&lt;/p&gt;

&lt;h2&gt;Display changes performed since last save&lt;/h2&gt;
&lt;p&gt;
This trick uses &lt;code&gt;diff&lt;/code&gt; to display the difference
between the file on disk and the current editing buffer.
&lt;figure&gt;
&lt;pre&gt;
w !diff -u file.txt -
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Indendent and un-indenting lines&lt;/h2&gt;
&lt;p&gt;
To indent a range of lines, substitute the beginning of the
line with either spaces or tabs:
&lt;figure&gt;
&lt;pre&gt;
12,18s/^/    /
.,+4s/^/{tab}/
&lt;/pre&gt;
&lt;/figure&gt;
To unindent, strip off that number of leading indentation:
&lt;figure&gt;
&lt;pre&gt;
12,18s/^    //
12,18s/^ \{4\}//
.,+4s/^{tab}//
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Undo &amp;amp; redo&lt;/h2&gt;
&lt;p&gt;
&lt;code&gt;ed(1)&lt;/code&gt; provides one level of undo/redo, using
the "&lt;kbd&gt;u&lt;/kbd&gt;" command to toggle between them:
&lt;figure&gt;
&lt;pre&gt;
$ ed
1a
hello
world
.
2d
,n
1 hello
u
,n
1 hello
2 world
u
,n
1 hello
Q
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Insert the output of an external command&lt;/h2&gt;
&lt;p&gt;
Similar to the insertion of a file above, &lt;code&gt;ed(1)&lt;/code&gt;
lets you use the "&lt;kbd&gt;r !&lt;/kbd&gt;" command to read the output 
of an external program:
&lt;figure&gt;
&lt;pre&gt;
r !ls -l
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Record commands&lt;/h2&gt;
&lt;p&gt;
&lt;figure&gt;
&lt;pre&gt;
&lt;/pre&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;h2&gt;Command history&lt;/h2&gt;
&lt;p&gt;
Since &lt;code&gt;ed(1)&lt;/code&gt; appends to the terminal window
rather than overwriting and redrawing the screen, the entire
editing history for the given &lt;code&gt;ed(1)&lt;/code&gt; session is
available for review.
&lt;/p&gt;</description><category>cli</category><category>ed</category><guid>https://blog.thechases.com/posts/cli/unsorted-ed-tips-and-tricks/</guid><pubDate>Sat, 06 Feb 2016 23:35:09 GMT</pubDate></item><item><title>Using ed(1) as a password manager</title><link>https://blog.thechases.com/posts/cli/using-ed1-as-a-password-manager/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
I recently came across
&lt;a href="http://invert.svbtle.com/using-vim-as-a-password-manager"&gt;a
post on using vim as a password manager&lt;/a&gt;
so I thought I'd post a companion article on using
&lt;code&gt;ed(1)&lt;/code&gt;
as a password manager.
&lt;/p&gt;

&lt;p&gt;
The main goal is to be able to keep the secrets encrypted at all
times on disk, only decrypting within the active
&lt;code&gt;ed(1)&lt;/code&gt;
session.
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;p&gt;
BSD
&lt;code&gt;ed(1)&lt;/code&gt;
offers an
&lt;kbd&gt;x&lt;/kbd&gt;
command (well,
&lt;a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/bin/ed/main.c.diff?r1=1.36&amp;amp;r2=1.37"&gt;OpenBSD removed it in revision 1.37 of
&lt;code&gt;/src/bin/ed/main.c&lt;/code&gt;
"because DES"&lt;/a&gt;)
that allows for weak DES encryption of the file.  However, we
want stronger encryption and portability between various versions
of
&lt;code&gt;ed(1)&lt;/code&gt;
so will ignore this option.
&lt;/p&gt;

&lt;h2&gt;Writing our encrypted password file to disk&lt;/h2&gt;

&lt;p&gt;
To begin, we'll open up
&lt;code&gt;ed(1)&lt;/code&gt;
and add some password content
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
user@hostname$ ed
a
https://example.com
Username: demo
Password: Pa$$w0rd1

imaps://example.edu
Username: jstudent@example.edu
Password: ed(1)uc8

.
&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;
Now, instead of writing the unencrypted password file, we'll use
&lt;code&gt;gnupg&lt;/code&gt;
to encrypt the file and write it out to the disk by piping the
file through
&lt;code&gt;gpg&lt;/code&gt;
instructing it to write to our password file.
Note the trailing "&lt;kbd&gt;-&lt;/kbd&gt;" which tells
&lt;code&gt;gpg&lt;/code&gt;
to read the input from
&lt;code&gt;stdin&lt;/code&gt;.
&lt;code&gt;gpg&lt;/code&gt;
will prompt for a passphrase and confirmation of that password.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
w !gpg --symmetric --output passwords.gpg -
Enter passphrase: Password1
Repeat passphrase: Password1
127
&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;
&lt;code&gt;ed(1)&lt;/code&gt;
informs us that it successfully piped our 127 bytes of data
through
&lt;code&gt;gpg&lt;/code&gt;
but we can confirm that the
&lt;code&gt;passwords.gpg&lt;/code&gt;
file was written and then we can quit to go about our day:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
!ls passwords.gpg
passwords.gpg
!
q
&lt;/pre&gt;
&lt;/figure&gt;

&lt;h2&gt;Reading our encrypted password file from disk&lt;/h2&gt;

&lt;p&gt;
Now, we want to be able to look up a password to enter at some
future point.  So we fire up
&lt;code&gt;ed(1)&lt;/code&gt;
and decrypt our passwords.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
user@hostname$ ed
r !gpg --decrypt passwords.gpg
Enter passphrase: Password1
&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;
We want to look up our log-in credentials for our email server so
we issue
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
?imap.*edu
imaps://example.edu
+
Username: jstudent@example.edu
+
Password: ed(1)uc8
Q
&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;
(Yes, using
"&lt;kbd&gt;+&lt;/kbd&gt;"
can be replaced with just hitting
"&lt;kbd&gt;&amp;lt;Enter&amp;gt;&lt;/kbd&gt;") Alternatively, we could use
&lt;code&gt;grep(1)&lt;/code&gt;
or
&lt;code&gt;sed(1)&lt;/code&gt;
to filter the results and show some context:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
user@hostname$ gpg --decrypt passwords.gpg | grep -A2 example.com
Enter passphrase: Password1
https://example.com
Username: demo
Password: Pa$$w0rd1
user@hostname$ gpg --decrypt passwords.gpg | sed -n '/example.com/,/^$/p'
Enter passphrase: Password1
https://example.com
Username: demo
Password: Pa$$w0rd1
&lt;/pre&gt;
&lt;/figure&gt;

&lt;h2&gt;Modifying our password lists&lt;/h2&gt;

&lt;p&gt;
Now we want to modify our document and/or change our
master-password:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
user@hostname$ ed
r !gpg --decrypt passwords.gpg
Enter passphrase: Password1
127
3s/Pa..w0rd1/Pbuttwrd1
Password: Pbuttwrd1
$a
https://twitter.com/
Username: ed1conf
Password: EyeDonutThinkSew

.
w !gpg --symmetric --output passwords.gpg -
Enter passphrase: NewPassword2
Repeat passphrase: NewPassword2
File `passwords.gpg` exists. Overwrite? (y/N) y
194
&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;
And there you have it:  using
&lt;code&gt;ed(1)&lt;/code&gt;
as a password manager.
&lt;/p&gt;</description><category>cli</category><category>ed</category><guid>https://blog.thechases.com/posts/cli/using-ed1-as-a-password-manager/</guid><pubDate>Fri, 06 Nov 2015 21:36:29 GMT</pubDate></item><item><title>Why ed(1)?</title><link>https://blog.thechases.com/posts/cli/why-ed1/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
As the weirdo behind the somewhat tongue-in-cheek
&lt;tt&gt;@ed1conf&lt;/tt&gt;
account on
&lt;a href="https://twitter.com/ed1conf"&gt;Twitter&lt;/a&gt;
and
&lt;a href="https://bsd.network/@ed1conf"&gt;Mastodon&lt;/a&gt;,
account I'm occasionally asked "Why
&lt;code&gt;ed(1)&lt;/code&gt;?"
Hopefully some of my reasons for learning &amp;amp; using
&lt;code&gt;ed(1)&lt;/code&gt;
can pique your interest in taking the time to get to know
this little piece of history.
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;h2&gt;Ubiquity&lt;/h2&gt;
&lt;p&gt;
Sometimes your favorite
&lt;code&gt;$EDITOR&lt;/code&gt;
is installed, sometimes it's not.  Some, like
&lt;code&gt;vi&lt;/code&gt;/&lt;code&gt;vim&lt;/code&gt;
are just about everywhere.  Other times, you would have to
have sufficient privileges/space to install or compile your
editor of choice.  But if you know
&lt;code&gt;ed&lt;/code&gt;,
nearly every Linux/BSD/Mac has it installed because
&lt;a href="http://pubs.opengroup.org/onlinepubs/009604599/utilities/ed.html"&gt;it's part of the POSIX standard&lt;/a&gt;.  It's even small
enough to fit on most recovery media without breaking the
bank.  But between
&lt;code&gt;ed&lt;/code&gt;
and
&lt;code&gt;vi/vim&lt;/code&gt;,
I know that I can get things done even when I'm on a new
machine.
&lt;/p&gt;

&lt;h2&gt;Sometimes it's the only editor you have&lt;/h2&gt;
&lt;p&gt;
Several times in my life
&lt;code&gt;ed&lt;/code&gt;
has been the only editor available in certain environments.
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;
 At
 &lt;code&gt;$DAYJOB[-1]&lt;/code&gt;
 the Linux-based router needed some configuration changes
 that the web interface didn't accommodate.  So a quick
 terminal connection later
 (&lt;code&gt;telnet&lt;/code&gt;, &lt;em&gt;sigh&lt;/em&gt;), I discovered that
 &lt;code&gt;ed&lt;/code&gt;
 was the only editor available.  No problem.  Edited the
 config file and we were back up and running with the proper
 changes.
 &lt;/li&gt;

 &lt;li&gt;
 At the same
 &lt;code&gt;$DAYJOB[-1]&lt;/code&gt;,
 I developed software for a
 &lt;a href="http://cybarcode.com/sites/cy/files/styles/xlarge_1/public/images/epson/eht-30.jpg"&gt;ruggedized hand-held device and its attached printer&lt;/a&gt;.
 This was back when
 &lt;abbr title="Personal Digital Assitants"&gt;PDA&lt;/abbr&gt;s
 were just becoming popular, so this thing was a brick.
 The DOS-based operating system had no built-in editor,
 meaning editing files usually meant laboriously copying the
 file over a serial link to the PC, editing it there, then
 sending it back down.  I longed for the ability to cut that
 time down but very few of the full-screen editors I tried
 were even able to successfully paint on the small LCD
 screen-buffer properly, and of those that did, the on-screen
 keyboard took up so much screen real-estate as to make them
 useless.  So I installed a DOS build of
 &lt;code&gt;ed&lt;/code&gt;
 and it worked like a charm (better than
 &lt;code&gt;edlin.exe&lt;/code&gt;
 that I also tried).  Editing turn-around and testing went
 from 15-20 minutes down to 3-5 minutes per iteration.
 &lt;/li&gt;

 &lt;li&gt;
 Some platforms such as
 &lt;a href="https://heroku.com/"&gt;Heroku&lt;/a&gt;
 &lt;a href="http://www.evans.io/posts/heroku-survival-guide/"&gt;
 provide only
 &lt;code&gt;ed&lt;/code&gt;
 as their editor&lt;/a&gt;.
 Not usually an issue since most of the time you're not
 trying to edit live on the server.  But if you need to do
 it, it's nice to know how.
 &lt;/li&gt;

 &lt;li&gt;
 On some &lt;abbr title="Multi-user dungeons"&gt;MUD&lt;/abbr&gt;
 games
 and old
 &lt;abbr title="Bulletin board systems"&gt;BBS&lt;/abbr&gt;es,
 the text-editor is often an
 &lt;code&gt;ed&lt;/code&gt;-like editor.
 &lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;Visible editing history&lt;/h2&gt;
&lt;p&gt;
Unless you have a key-echoing utility like
&lt;a href="http://seminar.io/projects/screenkey/"&gt;Screenkey&lt;/a&gt;
or Screenflick, it's hard for an audience to see exactly
what you did when you're editing during a presentation.
It's nice to for the audience to be able to see
&lt;em&gt;exactly&lt;/em&gt;
what you typed if they're trying to follow
along.
&lt;/p&gt;

&lt;h2&gt;All commands are ASCII text&lt;/h2&gt;
&lt;p&gt;
Sometimes your terminal emulator or keyboard isn't
configured correctly.  Function keys, arrows,
&lt;code&gt;alt-&lt;/code&gt;
and
&lt;code&gt;meta-&lt;/code&gt;modifiers
may not transmit properly.  Since all of
&lt;code&gt;ed&lt;/code&gt;'s commands are basic ASCII, it works even
if your keyboard/terminal is unable to send extended
characters properly.
&lt;/p&gt;

&lt;h2&gt;It works when &lt;code&gt;$TERM&lt;/code&gt; is messed up&lt;/h2&gt;
&lt;p&gt;
Likewise, your
&lt;code&gt;$TERM&lt;/code&gt;
setting can get messed up.  Sometimes full-screen terminal
applications leave the screen in a state where everything is
somewhat garbled.  Yes, there's
&lt;code&gt;reset&lt;/code&gt;
which will let you reset the terminal back to some sensible
defaults, but sometimes your
&lt;code&gt;termcap&lt;/code&gt;
database has trouble too.  An editor that only uses
&lt;code&gt;stdin&lt;/code&gt;
and
&lt;code&gt;stdout&lt;/code&gt;
can save your bacon.
&lt;/p&gt;

&lt;h2&gt;Accessibility&lt;/h2&gt;
&lt;p&gt;
Because
&lt;code&gt;ed&lt;/code&gt;
reads all of its commands from
&lt;code&gt;stdin&lt;/code&gt;
and writes all output to
&lt;code&gt;stdout&lt;/code&gt;
in a serial fashion, it's very usable in a screen-reader
like
&lt;a href="http://yasr.sourceforge.net/"&gt;&lt;code&gt;yasr&lt;/code&gt;&lt;/a&gt;
or
&lt;a href="http://www.linux-speakup.org/"&gt;&lt;code&gt;speakup&lt;/code&gt;&lt;/a&gt;
allowing you to edit text without a screen.  If you've never
edited text sightless, give it a try some time.
&lt;/p&gt;

&lt;h2&gt;Scriptability&lt;/h2&gt;
&lt;p&gt;
Because
&lt;code&gt;ed&lt;/code&gt;
reads all of its commands from
&lt;code&gt;stdin&lt;/code&gt;
it's easy to write a script that will edit a file in an
automated fashion.
&lt;/p&gt;

&lt;h2&gt;Previous output&lt;/h2&gt;
&lt;p&gt;
On occasion, I want to see the output of one or more
previous shell commands while I continue to edit.  A
full-screen editor takes over the entire screen, preventing
me from seeing that output.  With
&lt;code&gt;ed&lt;/code&gt;
the previous output is right there and remains in my
scroll-back buffer for reference while I edit.
I find this particularly useful when using
&lt;code&gt;\e&lt;/code&gt;
in
&lt;code&gt;psql&lt;/code&gt;
or
&lt;code&gt;mysql&lt;/code&gt;
if my
&lt;code&gt;$EDITOR&lt;/code&gt;
is set to
&lt;code&gt;ed&lt;/code&gt;.
This allows me to edit the SQL while keeping the results of
my previous query visible.
&lt;/p&gt;

&lt;h2&gt;Small, fast, light&lt;/h2&gt;
&lt;p&gt;
On resource-constrained systems, sometimes you need
something light like
&lt;code&gt;ed&lt;/code&gt;
where the executable and memory-footprint are measured in
kilobytes rather than megabytes.  This is less of a problem
on most systems these days, but with small
resource-constrained
&lt;abbr title="System On Chip"&gt;SOC&lt;/abbr&gt;
and embedded boards running Linux or BSD, a light-weight
yet powerful editor can help.
&lt;/p&gt;

&lt;h2&gt;Usable on low-bandwidth/high-latency connections&lt;/h2&gt;
&lt;p&gt;
Sometimes you are connected by a slow or very laggy
connection.  Whether this is a satellite uplink, a 300-baud
serial connection, or a congested WAN link, sometimes you
simply want to edit productively without the overhead of
repainting the screen.
&lt;/p&gt;

&lt;h2&gt;Showing off&lt;/h2&gt;
&lt;p&gt;
Finally, there's a small measure of grey-beard prestige
that comes with
&lt;a href="http://www.gnu.org/fun/jokes/ed.msg.html"&gt;using
an editor that baffles so many people&lt;/a&gt;.  It's a fast way
to demonstrate that I'm not some newbie with cert-only
knowledge, but that I enjoy Unix history and working at the
command-line.  Or maybe it shows that I'm just a little
crazy.
&lt;/p&gt;</description><category>cli</category><category>ed</category><guid>https://blog.thechases.com/posts/cli/why-ed1/</guid><pubDate>Thu, 10 Sep 2015 01:55:08 GMT</pubDate></item></channel></rss>