aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/blog/2023-06-16-regex/regex.md201
-rw-r--r--src/blog/2023-07-11-feed/feed.md340
-rw-r--r--src/blog/blog.md2
-rw-r--r--src/blog/feed.xml14
4 files changed, 557 insertions, 0 deletions
diff --git a/src/blog/2023-06-16-regex/regex.md b/src/blog/2023-06-16-regex/regex.md
new file mode 100644
index 0000000..e6c46f1
--- /dev/null
+++ b/src/blog/2023-06-16-regex/regex.md
@@ -0,0 +1,201 @@
1# UNIX text filters, part 0 of 3: regular expressions
2
3One of the most important features of UNIX and its descendants, if
4not *the* most important feature, is input / output redirection:
5the output of a command can be displayed to the user, written to a
6file or used as the input for another command seamlessly, without
7the the program knowing which of these things is happening. This
8is possible because most UNIX programs use *plain text* as their
9input/output language, which is understood equally well by the three
10types of users - humans, files and other running programs.
11
12Since this is such a fundamental feature of UNIX, I thought it would
13be nice to go through some of the standard tools that help the user
14take advantage of it. At first I thought of doing this as part of
15my *man page reading club* series, but in the end I decided to give
16them their own space. My other series has also been going on for
17more than a year now, so it is a good time to end it and start a
18new one.
19
20Let me then introduce you to: **UNIX text filters**.
21
22## Text filters
23
24For the purpose of this blog series, a *text filter* is a program
25that reads plain text from standard input and writes a modified,
26or *filtered* version of the same text to standard output. According
27to the introductory paragraph, this definition includes most UNIX
28programs; but we are going to focus on the following three, in
29increasing order of complexity:
30
31* grep
32* sed
33* awk
34
35In order to unleash the true power of these tools, we first need
36to grasp the basics of
37[regular expressions](https://en.wikipedia.org/wiki/Regular_expression).
38And what better way to do it than following the dedicated
39[OpenBSD manual page](https://man.openbsd.org/OpenBSD-7.3/re_format)?
40
41## (Extended) regular expressions
42
43Regular expressions, or regexes for short, are a convenient way to
44describe text patterns. They are commonly used to solve genering
45string matching problems, such as determining if a given piece
46of text is a valid URL. Many standard UNIX tools, including the three
47we are going to cover in this series, support regexes.
48
49Let's deal with the nasty part first: even within POSIX, there is
50not one single standard for regular expressions; there are at least
51two of them: Basic Regular Expressions (BREs) and Extended Regular
52Expressions (ERE). As it always happens when there is more than one
53standard for the same thing, other people decided to come up with
54another version to replace all previous "standards", so we have also
55[PCREs](https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions),
56and probably more. [Things got out of hand quickly](https://xkcd.com/927).
57
58In this post I am going to follow the structure of
59[re_format(7)](https://man.openbsd.org/OpenBSD-7.3/re_format) and
60present *extended* regular expresssions first. After that I'll point
61out the differences with *basic* regular expressions.
62
63The goal is not to provide a complete guide to regexes, but rather
64an introduction to the most important features, glossing over the
65nasty edge-cases. Keep also in mind that I am in no way an expert
66on the subject: we are learning together, here!
67
68### The basics
69
70You can think of a regular expression as a *pattern*, or a *rule*,
71that describes which strings are "valid" (they are *matched* by the
72regular expression) and which are not. As a trivial example, the
73regular expression `hello` matches only the string "hello". A less
74trivial example is the regex `.*` that matches *any* string. I'll
75explain why in a second.
76
77Beware not to confuse regular expressions with *shell globs*, i.e.
78the rules for shell command expansion. Although they use similar
79symbols to achieve a similar goal, they are not the same thing. See
80[my post on sh(1)](../2022-09-13-sh-1) or
81[glob(7)](https://man.openbsd.org/OpenBSD-7.3/glob.7) for an
82explanation on shell globs.
83
84### General structure and terminology
85
86A general regex looks something like this:
87
88```
89piece piece piece ... | piece piece piece ... | ...
90```
91
92A sequence of *pieces* is called a *branch*, and a regex is a
93sequence of branches separated by pipes `|`. Pieces are not separated
94by spaces, they are simply concatenated.
95
96The pipes `|` are read "or": a regex matches a given string if any
97of its branches does. A branch matches a given string if the latter
98can be written as a sequence of strings, each matching one of the
99pieces, in the given order.
100
101Before going into what pieces are exactly, consider the following
102example:
103
104```
105hello|world
106```
107
108This regex matches both the string "hello" and the string "world",
109and nothing else. The pieces are the single letters composing the
110two words, and as you can see they are juxtaposed without spaces.
111
112But what else is a valid piece? In general, a piece is made up of
113an *atom*, optionally followed by a *multiplier*.
114
115### Atoms
116
117As we have already seen, the most simple kind of atom is a single
118character. The most *general* kind of atom, on the other hand, is
119a whole regular expression enclosed in parentheses `()`. Yes, regexes
120are recursive.
121
122There are some special characters: for example, a single dot `.`
123matches *any* single character. The characters `^` and `$` match
124an empty string at the beginning and at the end of a line, respectively.
125If you want to match a special character as if it was regular, say
126because you want to match strings that represent values in the
127dollar currency, you can *escape* them with a backslash. For example
128`\$` matches the string "$".
129
130The last kind of atoms are *bracket expressions*, which consist of
131lists of characters enclosed in brackets `[]`. A simple list of
132characters in brackets, like `[xyz]`, matches any character in the
133list, unless the first character is a `^`, in which case it matches
134every character *not* in the list. Two characters separated by a
135dash `-` denote a range: for example `[a-z]` matches every lowercase
136letter and `[1-7]` matches all digits from 1 to 7.
137
138You can also use cetain special sets of characters, like `[:lower:]`
139to match every lowercase letter (same as `[a-z]`), `[:alnum:]` to
140match every alphanumeric character or `[:digit:]` to match every
141decimal digit. Check the
142[man page](https://man.openbsd.org/OpenBSD-7.3/re_format)
143for the full list.
144
145### Multipliers
146
147The term "multiplier" does not appear anywhere in the manual page, I
148made it up. But I think it fits, so I'll keep using it.
149
150Multipliers allow you to match an atom repeating a specified or
151unspecified amount of times. The most general one is the *bound*
152multiplier, which consists of one or two comma-separated numbers
153enclosed in braces `{}`.
154
155In its most simple form, the multiplier `{n}` repeats the multiplied
156atom `n` times. For example, the regex `a{7}` is equivalent to the
157regex `aaaaaaa` (and it matches the string "aaaaaaa").
158
159The form `{n,m}` matches *any number* between `n` and `m` of copies
160of the preceeding atom. For example `a{2,4}` is equivalent to
161`aa|aaa|aaaa`. If the integer `m` is not specified, the multiplied
162atom matches any string that consists of *at least* `n` copies of
163the atom.
164
165Now we can explain very quickly the more common multipliers `+`,
166`*` and `?`: they are equivalent to `{1,}`, `{0,}` and `{0,1}`
167respectively. That is to say, `+` matches at least one copy of the
168atom, `*` matches any number of copies (including none) and `?`
169matches either one copy or none.
170
171## Basic regular expressions
172
173Basic regular expressions are less powerful than their extended
174counterpart (with one exception, see below) and require more
175backslashes, but it is worth knowing them, because they are used
176by default in some programs (for example [ed(1)](../2022-12-24-ed)).
177The main differences between EREs and BREs are:
178
179* BREs consist of one single branch, i.e. there is no `|`.
180* Multipliers `+` and `?` do not exist.
181* You need to escape parentheses `\(\)` and braces `\{\}` to
182 use them with their special meaning.
183
184There is one feature of BREs, called *back-reference*, that is
185absent in EREs. Apparently it makes the implementation much more
186complex, and it makes BREs more powerful. I noticed the author of
187the manual page despises back-references, so I am not going to learn
188them out of respect for them.
189
190## Conclusion
191
192Regexes are a powerful tool, and they are more than worth knowing.
193But, quoting from the manual page:
194
195```
196 Having two kinds of REs is a botch.
197```
198
199I hope you enjoyed this post, despite the lack of practical examples.
200If you want to see more applications of regular expressions, stay
201tuned for the next entries on grep, sed and awk!
diff --git a/src/blog/2023-07-11-feed/feed.md b/src/blog/2023-07-11-feed/feed.md
new file mode 100644
index 0000000..feb5cd6
--- /dev/null
+++ b/src/blog/2023-07-11-feed/feed.md
@@ -0,0 +1,340 @@
1# My minimalistic RSS feed setup
2
3A couple of years ago I started using
4[RSS](https://en.wikipedia.org/wiki/Rss)
5(or [atom](https://en.wikipedia.org/wiki/Atom_(standard)))
6feeds to stay up to date with websites and blogs I wanted to read.
7This method is more convenient than what I used before (i.e. open
8Firefox and open each website I want to follow in a new tab, one
9by one), but unfortunately not every website provides an RSS feed
10these days.
11
12At first I used [newsboat](https://newsboat.org), but I soon started
13disliking the curses interface - see also my rant on curses at the
14end of [this other blog post](../2022-12-24-ed). Then I discovered
15`sfeed`.
16
17## sfeed
18
19[`sfeed`](https://codemadness.org/sfeed-simple-feed-parser.html)
20is an extremely minimalistic RSS and atom reader: it reads
21the xml content of feed file from standard input and it outputs one line per
22feed item, with tab-separated timestamps, title, link and so on. This tool
23comes bundled with other commands that can be combined with it, such as
24`sfeed_plain`, which converts the output of sfeed into something
25more readable:
26
27```
28$ curl -L https://sebastiano.tronto.net/blog/feed.xml | sfeed | sfeed_plain
29 2023-06-16 02:00 UNIX text filters, part 0 of 3: regular expressions https://sebastiano.tronto.net/blog/2023-06-16-regex
30 2023-05-05 02:00 I had to debug C code on a smartphone https://sebastiano.tronto.net/blog/2023-05-05-debug-smartphone
31 2023-04-10 02:00 The big rewrite https://sebastiano.tronto.net/blog/2023-04-10-the-big-rewrite
32 2023-03-30 02:00 The man page reading club: dc(1) https://sebastiano.tronto.net/blog/2023-03-30-dc
33 2023-03-06 01:00 Resizing my website's pictures with ImageMagick and find(1) https://sebastiano.tronto.net/blog/2023-03-06-resize-pictures
34...
35```
36
37One can also write a configuration file with all the desired feeds
38and fetch them with `sfeed_update`, or even use the `sfeed_curses`
39UI. But the reasons I tried out `sfeed` in the first place is that
40I *did not* want to use a curses UI, so I decided to stick with
41`sfeed_plain`.
42
43## My wrapper script - old versions
44
45In the project's homepage the following short script is presented to
46demonstrate the flexibility of sfeed:
47
48```
49#!/bin/sh
50url=$(sfeed_plain "$HOME/.sfeed/feeds/"* | dmenu -l 35 -i | \
51 sed -n 's@^.* \([a-zA-Z]*://\)\(.*\)$@\1\2@p')
52test -n "${url}" && $BROWSER "${url}"
53```
54
55The first line shows a list of feed items in
56[dmenu](https://tools.suckless.org/dmenu)
57to let the user select one, the second line opens the selected item
58in a web browser. I was impressed by how simple and clever this
59example was, and I decided to expand on it to build "my own" feed
60reader UI.
61
62In the first version I made, my feeds were separated in folders,
63one per file, and one could select multiple feeds or even entire
64folders via dmenu using
65[dmenu-filepicker](https://git.tronto.net/scripts/file/dmenu-filepicker.html)
66for file selection.
67Once the session was terminated, all shown feeds were marked as
68"read" by writing the timestamp of the last read item on a cache
69file, and they were not shown again on successive calls.
70
71This system worked fine for me, but at some point I grew tired of
72feeds being marked as "read" automatically. I also disliked the
73complexity of my own script. So I rewrote it from scratch, giving
74up the idea of marking feeds as read. This second version can still
75be found in the *old* folder of my
76[scripts repo](https://git.tronto.net/scripts), but I may remove it
77in the future. You will still be able to find it in the git history.
78
79I have happily used this second version for more than a year, but
80I had some minor issues with it. The main one was that, as I started
81adding more and more websites to my feed list, fetching them took
82longer and longer - up to 20-30 seconds; while the feed was loading,
83I could not start doing other stuff, because later dmenu would have
84grapped my keyboard while I was typing. Moreover, having a way to
85filter out old feed items is kinda useful when you check your feed
86relatively often. A few weeks ago I had enough and I decided to
87rewrite my wrapper script once again.
88
89## My wrapper script - current version
90
91In its current version, my `feed` scripts accepts four sub-commands:
92`get` to update the feed, `menu` to prompt a dmenu selection, `clear`
93to remove the old items and `show` to list all the new items.
94Since `clear` is a separate action, I do not have the problem I
95used to have with my first version, i.e. that feeds are automatically
96marked as read even if I sometimes do not want them to be.
97
98Let's walk through my last iteration on this script - you can find
99it in my scripts repository, but I'll include it at the end of this
100section too.
101
102At first I define some variables (mostly filenames), so that I can
103easily adapt the script if one day I want to move stuff around:
104
105```
106dir=$HOME/box/sfeed
107feeddir=$dir/urls
108destdir=$dir/new
109olddir=$dir/old
110readdir=$dir/last
111menu="dmenu -l 20 -i"
112urlopener=open-url
113```
114
115Here `open-url` is another one of my utility scripts.
116
117To update the feed, I loop over the files in my feed folder. Each
118file contains a single line with the feed's url, and the name of
119the file is the name / title of the website. The results of `sfeed`
120are piped into `sfeed_plain` and then saved to a file, and the most
121recent time stamp for each feed is updated.
122
123```
124getnew() {
125 for f in "$feeddir"/*; do
126 read -r url < "$f"
127 name=$(basename "$f")
128 d="$destdir/$name"
129 r="$readdir/$name"
130
131 [ -f "$r" ] && read -r lr < "$r" || lr=0
132
133 # Get new feed items
134 tmp=$(mktemp)
135 curl -s "$url" | sfeed | \
136 awk -v lr="$lr" '$1 > lr {print $0}' | \
137 tee "$tmp" | sfeed_plain >> "$d"
138
139 # Update last time stamp
140 awk -v lr="$lr" '$1 > lr {lr=$1} END {print lr}' <"$tmp" >"$r"
141 done
142}
143```
144
145The next snippet is used to show the new feed items.
146The `for` loop could be replaced by a simple
147`cat "$destdir"/*`, but I also want to prepend each line with
148the name of the website.
149
150```
151show() {
152 for f in "$destdir"/*; do
153 ff=$(basename "$f")
154 if [ -s "$f" ]; then
155 while read -r line; do
156 printf '%20s %s\n' "$ff" "$line"
157 done < "$f"
158 fi
159 done
160}
161```
162
163Finally, the following one-liner can be used to prompt the user to
164select and open the desired items in a browser using dmenu:
165
166```
167selectmenu() {
168 $menu | awk '{print $NF}' | xargs $urlopener
169}
170```
171
172The "clear" action is a straightfortward file management routine,
173and the rest of the script is just shell boilerplate code to parse
174the command line options and sub-commands. Putting it all together,
175the script looks like this:
176
177```
178#!/bin/sh
179
180# RSS feed manager
181
182# Requires: sfeed, sfeed_plain (get), dmenu, open-url (menu)
183
184# Usage: feed [-m menu] [get|menu|clear|show]
185
186dir=$HOME/box/sfeed
187feeddir=$dir/urls
188destdir=$dir/new
189olddir=$dir/old
190readdir=$dir/last
191menu="dmenu -l 20 -i"
192urlopener=open-url
193
194usage() {
195 echo "Usage: feed [get|menu|clear|show]"
196}
197
198getnew() {
199 for f in "$feeddir"/*; do
200 read -r url < "$f"
201 name=$(basename "$f")
202 d="$destdir/$name"
203 r="$readdir/$name"
204
205 [ -f "$r" ] && read -r lr < "$r" || lr=0
206
207 # Get new feed items
208 tmp=$(mktemp)
209 curl -s "$url" | sfeed | \
210 awk -v lr="$lr" '$1 > lr {print $0}' | \
211 tee "$tmp" | sfeed_plain >> "$d"
212
213 # Update last time stamp
214 awk -v lr="$lr" '$1 > lr {lr=$1} END {print lr}' <"$tmp" >"$r"
215 done
216}
217
218show() {
219 for f in "$destdir"/*; do
220 ff=$(basename "$f")
221 if [ -s "$f" ]; then
222 while read -r line; do
223 printf '%20s %s\n' "$ff" "$line"
224 done < "$f"
225 fi
226 done
227}
228
229selectmenu() {
230 $menu | awk '{print $NF}' | xargs $urlopener
231}
232
233while getopts "m:" opt; do
234 case "$opt" in
235 m)
236 menu="$OPTARG"
237 ;;
238 *)
239 usage
240 exit 1
241 ;;
242 esac
243done
244
245shift $((OPTIND - 1))
246
247if [ -z "$1" ]; then
248 usage
249 exit 1
250fi
251
252case "$1" in
253 get)
254 getnew
255 countnew=$(cat "$destdir"/* | wc -l)
256 echo "$countnew new feed items"
257 ;;
258 menu)
259 show | selectmenu
260 ;;
261 clear)
262 d="$olddir/$(date +'%Y-%m-%d-%H-%M-%S')"
263 mkdir "$d"
264 mv "$destdir"/* "$d/"
265 ;;
266 show)
267 show
268 ;;
269 *)
270 usage
271 exit 1
272 ;;
273esac
274```
275
276I personally like this approach of taking a simple program that
277only uses standard output and standard input and wrapping it around
278a shell script to have it do exactly what I want. The bulk of the
279work is done the "black box" program, and the shell scripts glues
280it together with the "configuration" files (in this case, my feed
281folder) and presents the results to me, interactively (e.g. via
282dmenu) or otherwise.
283
284At this point my feed-comsumption workflow would be something like
285this: first I `feed get`, then I do other stuff while the feed loads
286and later, after a couple of minutes or so, I run a `feed show` or
287`feed menu`. This is still not ideal, because whenever I want to
288check my feeds I still have to wait for them to be downloaded. The
289only way to go around it would be to have `feed get` run automatically
290when I am not thinking about it...
291
292## Setting up a cron job
293
294My personal laptop is not always connected to the internet, and in
295general I do not like having too many network-related jobs running
296in the background. But I do have a machine that is always connected
297to the internet: the VM instance hosting this website.
298
299Since my new setup saves my feed updates to local files, I can have
300a [cron job](https://en.wikipedia.org/wiki/Cron_job) fetch the new
301items and update files in a folder sync'd via
302[syncthing](https://syncthing.net) (yes, I do have that *one* network
303service constantly running in the background...). This setup is
304similar to the one I use to [fetch my email](../2022-10-19-email-setup).
305
306I rarely use cron, and I am always a little intimitaded by its
307syntax. But in the end to have `feed get` run every hour I just
308needed to add the following two lines via `crontab -e`:
309
310```
311MAILTO=""
3120 * * * * feed get
313```
314
315This is my definitive new setup, and I like it. It also has the
316advantage that I only need to install `sfeed` on my server and not
317locally, though I prefer to still keep it around.
318
319So far I have found one little caveat: if my feed gets updated after
320I read it and before I run a `feed clear`, some items may be deleted
321before I see them. This is easilly worked around by running a quick
322`feed show` before I clear the feeds up, but it is still worth
323keeping in mind.
324
325## Conclusions
326
327This is a summary of my last script-crafting adventure. As I was
328writing this post I realized I could probably use `sfeed_update`
329to simplify the script a bit, since I do not separate feeds into
330folders anymore. I have also found out that `sfeed_mbox` was created
331(at least I *think* it was not there the last time I checked) and I
332could use it to browse my feed with a mail client - see also
333[this video tutorial](https://josephchoe.com/rss-terminal) for a demo.
334
335With all of this, did I solve my problem in the best possible way?
336Definitely not. But does it work for me? Absolutely! Did I learn
337something new while doing this? Kind of, but mostly I have just
338excercised skills that I already had.
339
340All in all, it was a fun exercise.
diff --git a/src/blog/blog.md b/src/blog/blog.md
index be9015d..6c21cd5 100644
--- a/src/blog/blog.md
+++ b/src/blog/blog.md
@@ -5,6 +5,8 @@
5 5
6## 2023 6## 2023
7 7
8* 2023-07-11 [My minimalistic RSS feed setup](2023-07-11-feed)
9* 2023-06-16 [UNIX text filters, part 0 of 3: regular expressions](2023-06-16-regex)
8* 2023-05-05 [I had to debug C code on a smartphone](2023-05-05-debug-smartphone) 10* 2023-05-05 [I had to debug C code on a smartphone](2023-05-05-debug-smartphone)
9* 2023-04-10 [The big rewrite](2023-04-10-the-big-rewrite) 11* 2023-04-10 [The big rewrite](2023-04-10-the-big-rewrite)
10* 2023-03-30 [The man page reading club: dc(1)](2023-03-30-dc) 12* 2023-03-30 [The man page reading club: dc(1)](2023-03-30-dc)
diff --git a/src/blog/feed.xml b/src/blog/feed.xml
index 58df461..9c39603 100644
--- a/src/blog/feed.xml
+++ b/src/blog/feed.xml
@@ -9,6 +9,20 @@ Thoughts about software, computers and whatever I feel like sharing
9</description> 9</description>
10 10
11<item> 11<item>
12<title>My minimalistic RSS feed setup</title>
13<link>https://sebastiano.tronto.net/blog/2023-07-11-feed</link>
14<description>My minimalistic RSS feed setup</description>
15<pubDate>2023-07-11</pubDate>
16</item>
17
18<item>
19<title>UNIX text filters, part 0 of 3: regular expressions</title>
20<link>https://sebastiano.tronto.net/blog/2023-06-16-regex</link>
21<description>UNIX text filters, part 0 of 3: regular expressions</description>
22<pubDate>2023-06-16</pubDate>
23</item>
24
25<item>
12<title>I had to debug C code on a smartphone</title> 26<title>I had to debug C code on a smartphone</title>
13<link>https://sebastiano.tronto.net/blog/2023-05-05-debug-smartphone</link> 27<link>https://sebastiano.tronto.net/blog/2023-05-05-debug-smartphone</link>
14<description>I had to debug C code on a smartphone</description> 28<description>I had to debug C code on a smartphone</description>

Generated with cgit - Back to sebastiano.tronto.net