
<article>

<h2 id="introduction">
Introduction</h2>

<p>
On 2025-06-19 Ken Fallon did a show, number <a
href="https://hackerpublicradio.org/eps/hpr4404/index.html">
4404</a>
,
responding to Kevie's show <a
href="https://hackerpublicradio.org/eps/hpr4398/index.html">
4398</a>
,
which came out on 2025-06-11.</p>

<p>
Kevie was using a Bash pipeline to find the latest episode in an RSS
feed, and download it. He used <code>
grep</code>
 to parse the XML of the
feed.</p>

<p>
Ken's response was to suggest the use of <code>
xmlstarlet</code>
 to
parse the XML because such a complex structured format as XML cannot
reliably be parsed without a program that "understands" the intricacies
of the format's structure. The same applies to other complex formats
such as HTML, YAML and JSON.</p>

<p>
In his show Ken presented a Bash script which dealt with this problem
and that of the ordering of episodes in the feed. He asked how others
would write such a script, and thus I was motivated to produce this
response to his response!</p>

<h2 id="alternative-script">
Alternative script</h2>

<p>
My script is a remodelling of Ken's, not a completely different
solution. It contains a few alternative ways of doing what Ken did, and
a reordering of the parts of his original. We will examine the changes
in this episode.</p>

<h3 id="script">
Script</h3>

<pre>
<code>
#!/bin/bash
# Original (c) CC-0 Ken Fallon 2025
# Modified by Dave Morriss, 2025-06-14 (c) CC-0

podcast=&quot;https://tuxjam.otherside.network/feed/podcast/&quot;

# [1]
while read -r item
do
    # [2]
    pubDate=&quot;${item%;*}&quot;
    # [3]
    pubDate=&quot;$( &bsol;date --date=&quot;${pubDate}&quot; --universal +%FT%T )&quot;
    # [4]
    url=&quot;${item#*;}&quot;
    # [5]
    echo &quot;${pubDate};${url}&quot;
done &lt; &lt;(curl --silent &quot;${podcast}&quot; | &bsol;
    xmlstarlet sel --text --template --match &#39;rss/channel/item&#39; &bsol;
    --value-of &#39;concat(pubDate, &quot;;&quot;, enclosure/@url)&#39; --nl - ) | &bsol;
sort --numeric-sort --reverse | &bsol;
head -1 | &bsol;
cut -f2 -d&#39;;&#39; | wget --quiet --input-file=- # [6]</code>
</pre>

<p>
I have placed some comments in the script in the form of
<code>
'# [1]'</code>
 and I'll refer to these as I describe the changes
in the following numbered list.</p>

<p>
<small>
<em>
Note:</em>
 I checked, and the script will run with the
comments, though they are only there to make it easier to refer to
things.</small>
</p>

<ol type="1">

<li>
<p>
The format of the pipeline is different. It starts by defining a
<code>
while</code>
 loop, but the data which the <code>
read</code>

command receives comes from a <em>
process substitution</em>
 of the form
<code>
'&lt;(statements)'</code>
 (see the <a
href="https://hackerpublicradio.org/eps/hpr2045/hpr2045_full_shownotes.html#process-substitution">
<em>
process
substitution section</em>
 of "hpr2045 :: Some other Bash tips"</a>
). I
have arranged the pipeline in this way because it's bad practice to
place a <code>
while</code>
 in a pipeline, as discussed in the show: <a
href="">
hpr3985 :: Bash snippet - be careful when feeding data to
loops</a>
.<br />

<small>
(I added <code>
-r</code>
 to the <code>
read</code>
 because
<code>
shellcheck</code>
, which I run in the <code>
vim</code>
 editor,
nagged me!)</small>
</p>
</li>

<li>
<p>
The lines coming from the <em>
process substitution</em>
 are from
running <code>
curl</code>
 to collect the feed, then using
<code>
xmlstarlet</code>
 to pick out the <code>
pubDate</code>
 field of
the item, and the <code>
url</code>
 attribute of the
<code>
enclosure</code>
 field returning them as two strings separated by
a semicolon (<code>
';'</code>
). This is from Ken's original code. Each
line is read into the variable <code>
item</code>
, and the first element
(before the semicolon) is extracted with the Bash expression
<code>
"${item%;*}"</code>
. Parameter manipulation expressions were
introduced in <a
href="https://hackerpublicradio.org/eps/hpr1648/index.html">
HPR show
1648</a>
. See the full notes section <a
href="https://hackerpublicradio.org/eps/hpr1648/hpr1648_full_shownotes.html#remove-matching-suffix-pattern">
Remove
matching suffix pattern</a>
 for this one.</p>
</li>

<li>
<p>
I modified Ken's <code>
date</code>
 command to simplify the
generation of the ISO8601 date and time by using the pattern
<code>
+%FT%T</code>
. This just saves typing!</p>
</li>

<li>
<p>
The <code>
url</code>
 value is extracted from the contents of
<code>
item</code>
 with the expression <code>
"${item#*;}</code>
. See the
section of show 1648 entitled <a
href="https://hackerpublicradio.org/eps/hpr1648/hpr1648_full_shownotes.html#remove-matching-prefix-pattern">
Remove
matching prefix pattern</a>
 for details.</p>
</li>

<li>
<p>
The <code>
echo</code>
 which generates the list of podcast URLs
prefixed with an ISO time stamp uses <code>
';'</code>
 as the delimiter
where Ken used a <code>
tab</code>
 character. I assume this was done for
the benefit of either the following <code>
sort</code>
 or the
<code>
awk</code>
 script. It's not needed for <code>
sort</code>
 since it
sorts the line as-is and doesn't use fields. My version doesn't use
<code>
awk</code>
.</p>
</li>

<li>
<p>
Rather than using <code>
awk</code>
 I use <code>
cut</code>
 to
remove the time stamp from the front of each line, returning the second
field delimited by the semicolon. The result of this will be the URL for
<code>
wget</code>
 to download. In this case <code>
wget</code>
 receives
the URL on standard input (<em>
STDIN</em>
), and the
<code>
--input-file=-</code>
 option tells it to use that information for
the download.</p>
</li>

</ol>

<h2 id="conclusion">
Conclusion</h2>

<p>
I'm not sure my solution is <em>
better</em>
 in any significant way. I
prefer to use Bash functionality to do things where calling
<code>
awk</code>
 or <code>
sed</code>
 could be overkill, but that's just
a personal preference.</p>

<p>
I might have replaced the <code>
head</code>
 and <code>
cut</code>
 with
a <code>
sed</code>
 expression, such as the following as the last
line:</p>

<pre>
<code>
sed -e &#39;1{s/^.&bsol;+;//;q}&#39; | wget --quiet --input-file=-</code>
</pre>

<p>
Here, the <code>
sed</code>
 expression operates on the first line from
the <code>
sort</code>
, where it removes everything from the start of the
line to the semicolon. The expression then causes <code>
sed</code>
 to
quit, so that only the edited first line is passed to
<code>
wget</code>
.</p>

<h2 id="links">
Links</h2>

<ul>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr1648/index.html">
hpr1648 ::
Bash parameter manipulation</a>

<ul>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr1648/hpr1648_full_shownotes.html#remove-matching-suffix-pattern">
Section
entitled <em>
Remove matching suffix pattern</em>
</a>
</li>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr1648/hpr1648_full_shownotes.html#remove-matching-prefix-pattern">
Section
entitled <em>
Remove matching prefix pattern</em>
</a>
</li>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr1648/hpr1648_diagram.pdf">
Diagram
showing the Bash parameter manipulation methods</a>
</li>

</ul>
</li>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr2045/index.html">
hpr2045 ::
Some other Bash tips</a>

<ul>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr2045/hpr2045_full_shownotes.html#process-substitution">
Section
on <em>
process substitution</em>
</a>
</li>

</ul>
</li>

<li>
<a href="">
hpr3985 :: Bash snippet - be careful when feeding data to
loops</a>
</li>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr4398/index.html">
hpr4398 ::
Command line fun: downloading a podcast <em>
by Kevie</em>
</a>
</li>

<li>
<a
href="https://hackerpublicradio.org/eps/hpr4404/index.html">
hpr4404 ::
Kevie nerd snipes Ken by grepping xml <em>
by Ken Fallon</em>
</a>
</li>

</ul>

</article>


