Files
hpr_website/www/eps/hpr2659/hpr2659_full_shownotes.html

386 lines
24 KiB
HTML
Raw Normal View History

2025-10-28 18:39:57 +01:00
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<meta name="author" content="Dave Morriss">
<title>Further ancillary Bash tips - 11 (HPR Show 2659)</title>
<style type="text/css">code{white-space: pre;}</style>
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
</head>
<body id="home">
<div id="container" class="shadow">
<header>
<h1 class="title">Further ancillary Bash tips - 11 (HPR Show 2659)</h1>
<h2 class="author">Dave Morriss</h2>
<hr/>
</header>
<main id="maincontent">
<article>
<header>
<h1>Table of Contents</h1>
<nav id="TOC">
<ul>
<li><a href="#making-decisions-in-bash">Making decisions in Bash</a></li>
<li><a href="#bash-conditional-expressions">Bash Conditional Expressions</a></li>
<li><a href="#combining-expressions">Combining expressions</a><ul>
<li><a href="#operators-used-with-test-and-...">Operators used with <code>test</code> and <code>'[...]'</code></a></li>
<li><a href="#operators-used-with-...">Operators used with <code>'[[...]]'</code></a></li>
</ul></li>
<li><a href="#conditional-expression-examples">Conditional expression examples</a><ul>
<li><a href="#example-1">Example 1</a></li>
<li><a href="#example-2">Example 2</a></li>
<li><a href="#example-3">Example 3</a></li>
</ul></li>
<li><a href="#string-comparisons">String comparisons</a><ul>
<li><a href="#pattern-matching">Pattern matching</a></li>
<li><a href="#pattern-matching-examples">Pattern matching examples</a><ul>
<li><a href="#example-4">Example 4</a></li>
<li><a href="#example-5">Example 5</a></li>
<li><a href="#example-6">Example 6</a></li>
<li><a href="#example-7">Example 7</a></li>
</ul></li>
</ul></li>
<li><a href="#links">Links</a></li>
</ul>
</nav>
</header>
<h2 id="making-decisions-in-bash">Making decisions in Bash</h2>
<p>This is the eleventh episode in the <em>Bash Tips</em> sub-series. It is the third of a group of shows about making decisions in Bash.</p>
<p>In the last two episodes we saw the types of test Bash provides, and we looked briefly at some of the commands that use these tests. Now we want to start examining the expressions that can be used in these tests, and how to combine them. We will also start looking at string comparisons in extended tests.</p>
<h2 id="bash-conditional-expressions">Bash Conditional Expressions</h2>
<p>This section is based very closely on the section of the <a href="https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions" title="Bash Conditional Expressions">GNU Bash Manual</a> of the same name (and the Bash manpage). The list below is essentially the same except that the explanations are a little longer where it seemed necessary to add more detail.</p>
<p>Conditional expressions are used by the <code>'[['</code> and <code>']]'</code> <em>extended test</em> operators and the <code>test</code> and <code>'['</code> and <code>']'</code> builtin commands (see part 1, <a href="http://hackerpublicradio.org/eps/hpr2639" title="Some ancillary Bash tips - 9">episode 2639</a>).</p>
<p>Expressions may be unary or binary. Unary operators take a single argument to the right, whereas binary operators take two arguments to the left and right. Unary expressions are often used to examine the status of a file. There are string operators and numeric comparison operators as well.</p>
<p>When used with <code>'[['</code>, the <code>'&lt;'</code> and <code>'&gt;'</code> operators sort lexicographically using the current locale. The <code>test</code> command uses ASCII ordering.</p>
<p>Unless otherwise specified, primaries that operate on files follow symbolic links and operate on the target of the link, rather than the link itself.</p>
<p><small></p>
<dl>
<dt><b>-a</b> <em>file</em></dt>
<dd>True if <em>file</em> exists. This is identical in effect to <b>-e</b>. It has been “deprecated,” and its use is discouraged.
</dd>
<dt><b>-b</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a block special file. (A block device reads and/or writes data in chunks, or blocks, in contrast to a character device, which acesses data in character units. Examples of block devices are hard drives, CDROM drives, and flash drives. Examples of character devices are keyboards, modems, sound cards.)
</dd>
<dt><b>-c</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a character special file. (See the <b>-b</b> description for an explanation)
</dd>
<dt><b>-d</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a directory.
</dd>
<dt><b>-e</b> <em>file</em></dt>
<dd>True if <em>file</em> exists.
</dd>
<dt><b>-f</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a regular file. (Not a directory or any of the other <em>special</em> files)
</dd>
<dt><b>-g</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and its set-group-id bit is set. (If a directory has the <em>sgid</em> flag set, then a file created within that directory belongs to the group that owns the directory, not necessarily to the group of the user who created the file. This may be useful for a directory shared by a workgroup)
</dd>
<dt><b>-h</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a symbolic link.
</dd>
<dt><b>-k</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and its “sticky” bit is set. (Commonly known as the sticky bit, the <em>save-text-mode</em> flag is a special type of file permission. If a file has this flag set, that file will be kept in cache memory, for quicker access. However, note that on Linux systems, the sticky bit is no longer used for files, only on directories)
</dd>
<dt><b>-p</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a named pipe (FIFO).
</dd>
<dt><b>-r</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is readable.
</dd>
<dt><b>-s</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and has a size greater than zero.
</dd>
<dt><b>-t</b> <em>fd</em></dt>
<dd>True if file descriptor <em>fd</em> is open and refers to a terminal. (This test option may be used to check whether the <code>stdin</code> <b>[ -t 0 ]</b> or <code>stdout</code> <b>[ -t 1 ]</b> in a given script is a terminal)
</dd>
<dt><b>-u</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and its set-user-id bit is set. (A binary owned by <em>root</em> with set-user-id flag set runs with <em>root</em> privileges, even when an ordinary user invokes it. A file with the <em>suid</em> flag set shows an <em>s</em> in its permissions)
</dd>
<dt><b>-w</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is writable.
</dd>
<dt><b>-x</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is executable.
</dd>
<dt><b>-G</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is owned by the effective group id.
</dd>
<dt><b>-L</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a symbolic link.
</dd>
<dt><b>-N</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and has been modified since it was last read.
</dd>
<dt><b>-O</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is owned by the effective user id.
</dd>
<dt><b>-S</b> <em>file</em></dt>
<dd>True if <em>file</em> exists and is a socket.
</dd>
<dt><em>file1</em> <b>-ef</b> <em>file2</em></dt>
<dd>True if <em>file1</em> and <em>file2</em> refer to the same device and inode numbers. (Files <em>file1</em> and <em>file2</em> are hard links to the same file)
</dd>
<dt><em>file1</em> <b>-nt</b> <em>file2</em></dt>
<dd>True if <em>file1</em> is newer (according to modification date) than <em>file2</em>, or if <em>file1</em> exists and <em>file2</em> does not.
</dd>
<dt><em>file1</em> <b>-ot</b> <em>file2</em></dt>
<dd>True if <em>file1</em> is older than <em>file2</em>, or if <em>file2</em> exists and <em>file1</em> does not.
</dd>
<dt><b>-o</b> <em>optname</em></dt>
<dd>True if the shell option <em>optname</em> is enabled. The list of options appears in the description of the -o option to the set builtin (see <a href="https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin" title="The Set Builtin">The Set Builtin</a>).
</dd>
<dt><b>-v</b> <em>varname</em></dt>
<dd>True if the shell variable <em>varname</em> is set (has been assigned a value).
</dd>
<dt><b>-R</b> <em>varname</em></dt>
<dd>True if the shell variable <em>varname</em> is set and is a name reference.
</dd>
<dt><b>-z</b> <em>string</em></dt>
<dd>True if the length of <em>string</em> is zero.
</dd>
<dt><b>-n</b> <em>string</em>  <u>or</u>  <em>string</em></dt>
<dd>True if the length of <em>string</em> is non-zero.
</dd>
<dt><em>string1</em> <b>==</b> <em>string2</em>  <u>or</u>  <em>string1</em> <b>=</b> <em>string2</em></dt>
<dd>True if the strings are equal. When used with the <code>[[</code> command, this performs pattern matching as <a href="#pattern-matching">described below</a><br />
The <code>'='</code> operator should be used with the <code>test</code> command for POSIX conformance.
</dd>
<dt><em>string1</em> <b>!=</b> <em>string2</em></dt>
<dd>True if the strings are not equal.
</dd>
<dt><em>string1</em> <b>&lt;</b> <em>string2</em></dt>
<dd>True if string1 sorts before string2 lexicographically.
</dd>
<dt><em>string1</em> <b>&gt;</b> <em>string2</em></dt>
<dd>True if string1 sorts after string2 lexicographically.
</dd>
<dt><em>arg1</em> <b>OP</b> <em>arg2</em></dt>
<dd>OP is one of <code>-eq</code>, <code>-ne</code>, <code>-lt</code>, <code>-le</code>, <code>-gt</code>, or <code>-ge</code>. These arithmetic binary operators return true if <em>arg1</em> is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to <em>arg2</em>, respectively. <em>Arg1</em> and <em>arg2</em> may be positive or negative integers. </small>
</dd>
</dl>
<h2 id="combining-expressions">Combining expressions</h2>
<h3 id="operators-used-with-test-and-...">Operators used with <code>test</code> and <code>'[...]'</code></h3>
<dl>
<dt>! <em>expr</em></dt>
<dd>True if <em>expr</em> is false.
</dd>
<dt>( <em>expr</em> )</dt>
<dd>Returns the value of <em>expr</em>. This may be used to override the normal precedence of operators.
</dd>
<dt><em>expr1</em> <b>-a</b> <em>expr2</em></dt>
<dd>True if both <em>expr1</em> and <em>expr2</em> are true.
</dd>
<dt><em>expr1</em> <b>-o</b> <em>expr2</em></dt>
<dd>True if either <em>expr1</em> or <em>expr2</em> is true.
</dd>
</dl>
<h3 id="operators-used-with-...">Operators used with <code>'[[...]]'</code></h3>
<dl>
<dt><em>expr1</em> <b>&amp;&amp;</b> <em>expr2</em></dt>
<dd>True if both <em>expr1</em> and <em>expr2</em> are true. Differs from <strong>-a</strong> in that if <em>expr1</em> returns False then <em>expr2</em> is never invoked. This operator <em>short circuits</em>.
</dd>
<dt><em>expr1</em> <b>||</b> <em>expr2</em></dt>
<dd>True if either <em>expr1</em> or <em>expr2</em> is true. Differs from <strong>-o</strong> in that if <em>expr1</em> returns True then <em>expr2</em> is never invoked. This operator <em>short circuits</em>.
</dd>
</dl>
<h2 id="conditional-expression-examples">Conditional expression examples</h2>
<h3 id="example-1">Example 1</h3>
<pre><code>if [[ ! -e &quot;$file&quot; ]]; then
echo &quot;File $file not found; aborting&quot;
exit 1
fi</code></pre>
<p>This is a typical use of the <code>-e</code> operator to test for the existence of a file that has been passed through as an argument, or is an expected constant name. It is good to make the script exit at this point and to do so with a failure result of <code>1</code>. This way the caller, which may be a script, can take error action as well.</p>
<p>This can also be written as a <em>command list</em> as mentioned in the <a href="http://hackerpublicradio.org/eps/hpr2649" title="More ancillary Bash tips - 10">previous episode</a>:</p>
<pre><code>[ -e &quot;$file&quot; ] || { echo &quot;File $file not found; aborting&quot;; exit 1; }</code></pre>
<p>Note that this time we test for existence of the file and if the <code>-e</code> operator returns False the next command will be executed. This is a compound command in curly braces, and as discussed in the <a href="http://hackerpublicradio.org/eps/hpr2649" title="More ancillary Bash tips - 10">previous episode</a> the two commands in it must end with semicolons inside the braces. See the appropriate section of the <a href="https://www.gnu.org/software/bash/manual/bash.html#Command-Grouping" title="Grouping Commands">GNU Bash Manual</a> for further details.</p>
<h3 id="example-2">Example 2</h3>
<pre><code>$ cat bash11_ex1.sh
#!/bin/bash
#
# A directory we want to create if it doesn&#39;t exist
#
BASEDIR=&quot;/tmp/testdir&quot;
#
# Check for the existence of the directory and create it if not found
#
if [[ ! -d &quot;$BASEDIR&quot; ]]; then
# Create directory and take action on failure
mkdir &quot;$BASEDIR&quot; || { echo &quot;Failed to create $BASEDIR&quot;; exit 1; }
echo &quot;Created $BASEDIR&quot;
fi
$ ./bash11_ex1.sh
Created /tmp/testdir</code></pre>
<p>This might be a way to determine if a particular directory exists, and if not, create it. Note how the <code>mkdir</code> command is part of a command list using a logical OR. If this command fails the following command is executed. As in Example 1 this is a compound command in curly braces, containing two individual commands, <code>echo</code> and <code>exit</code> and between them they will produce an error message and exit the script.</p>
<p>This example is available as a downloadable file (<a href="hpr2659_bash11_ex1.sh">bash11_ex1.sh</a>).</p>
<h3 id="example-3">Example 3</h3>
<p>Finding the length of a string is often something a script needs to do. The string may be the output from a command, or input from the script user for example. One way to check if the string is empty is:</p>
<pre><code>if [[ ${#reply} -eq 0 ]]; then
echo &quot;Please provide a non-empty reply&quot;
fi</code></pre>
<p>A better way is to use the <code>-z</code> operator:</p>
<pre><code>if [[ -z $reply ]]; then
echo &quot;Please provide a non-empty reply&quot;
fi</code></pre>
<p>The following script demonstrates these two alternatives:</p>
<pre><code>$ cat bash11_ex2.sh
#!/bin/bash
#
# Read a reply from the user, then check it&#39;s not zero length
#
read -r -p &quot;Please enter a string: &quot; reply
if [[ ${#reply} -eq 0 ]]; then
echo &quot;Please provide a non-empty reply&quot;
else
echo &quot;You said: $reply&quot;
fi
#
# Read a reply from the user, then check it&#39;s not zero length
#
read -r -p &quot;Please enter a string: &quot; reply
if [[ -z $reply ]]; then
echo &quot;Please provide a non-empty reply&quot;
else
echo &quot;You said: $reply&quot;
fi
$ ./bash11_ex2.sh
Please enter a string: OK
You said: OK
Please enter a string:
Please provide a non-empty reply</code></pre>
<p>This example is available as a downloadable file (<a href="hpr2659_bash11_ex2.sh">bash11_ex2.sh</a>).</p>
<h2 id="string-comparisons">String comparisons</h2>
<p>Because the string comparisons mentioned above are more complex (and more powerful) than other expressions, we will look at them in more detail. There also exists a binary operator which performs a regular expression match as a kind of string matching not listed in the above section. We will look at this subject in the next episode.</p>
<h3 id="pattern-matching">Pattern matching</h3>
<p>When comparing strings with <code>test</code> and <code>'[...]'</code> (using <code>==</code> or the POSIX-compliant <code>=</code>, and <code>!=</code>) the two strings being compared are treated as plain strings. However, when using the <em>extended test</em> operators <code>'[[...]]'</code> it is possible to compare the left-hand argument with a <em>pattern</em> as the right-hand argument. See the appropriate section of the <a href="https://www.gnu.org/software/bash/manual/bash.html#Pattern-Matching" title="Bash Pattern Matching">GNU Bash Manual</a> for full details.</p>
<p>The pattern and pattern comparison were discussed in episodes <a href="http://hackerpublicradio.org/eps/hpr2278" title="Some supplementary Bash tips">2278</a> and <a href="http://hackerpublicradio.org/eps/hpr2293" title="More supplementary Bash tips">2293</a> in the context of pathname expansion. However, in this case of string comparison the pattern is treated as if the <code>extglob</code> option were enabled. It is also possible to enable another <code>shopt</code> option called <code>'nocasematch'</code> to make the pattern case-insensitive.</p>
<p>It is possible to perform some quite sophisticated pattern matching this way, but the pattern <u>must not</u> be quoted. Doing so makes it a simple string in which the pattern features are not available. According to the documentation it is possible to quote part of a pattern however, if it is desired to treat pattern metacharacters as simple characters for example<a href="#fn1" class="footnote-ref" id="fnref1"><sup>1</sup></a>.</p>
<h3 id="pattern-matching-examples">Pattern matching examples</h3>
<h4 id="example-4">Example 4</h4>
<pre><code>animal=&quot;grizzly bear&quot;
if [[ $animal == *bear ]]; then
echo &quot;Detected a type of bear: $animal&quot;
fi</code></pre>
<p>In this example the test is for any string which ends with <code>'bear'</code>. It also matches <code>'bear'</code> with no earlier string.</p>
<h4 id="example-5">Example 5</h4>
<pre><code>str=&quot;Further ancillary Bash tips - 11&quot;
if [[ $str == +([[:alnum:] -]) ]]; then
echo &quot;Matched&quot;
fi</code></pre>
<p>Here we try to match the title of this show with a pattern. The pattern consists of:</p>
<ul>
<li>a POSIX <em>character class</em> which matches any alphabetic or numeric character - <code>[:alnum:]</code> (allowed only inside a character range expression)</li>
<li>a range expression which also includes a space and a hyphen as well as the <em>character class</em></li>
<li>a sub-pattern (normally only allowed when <code>extglob</code> is enabled) which specifies one or more occurrences of the given pattern - <code>+(pattern-list)</code></li>
</ul>
<p>The result is a pattern which matches one or more alphanumeric characters, space and hyphen. This matches the show title.</p>
<p>This example is available as a downloadable file: <a href="hpr2659_bash11_ex3.sh">bash11_ex3.sh</a></p>
<h4 id="example-6">Example 6</h4>
<pre><code>$ cat bash11_ex4.sh
#!/bin/bash
#
# String comparison with a pattern, using one of a list of patterns
#
for str in &#39;dog&#39; &#39;pig&#39; &#39;rat&#39; &#39;cat&#39; &#39;&#39;; do
if [[ $str == @(pig|dog|cat) ]]; then
echo &quot;Matched &#39;$str&#39;&quot;
else
echo &quot;Didn&#39;t match &#39;$str&#39;&quot;
fi
done
$ ./bash11_ex4.sh
Matched &#39;dog&#39;
Matched &#39;pig&#39;
Didn&#39;t match &#39;rat&#39;
Matched &#39;cat&#39;
Didn&#39;t match &#39;&#39;</code></pre>
<p>This example uses the <code>@(pattern-list)</code> form to match any one of a list of patterns where each subsidiary pattern is separated by <code>'|'</code> characters (alternative patterns). By this means the pattern will match any of the strings <code>&quot;pig&quot;</code>, <code>&quot;dog&quot;</code> or <code>&quot;cat&quot;</code> but nothing else, not even a blank string.</p>
<p>This example is available as a downloadable file (<a href="hpr2659_bash11_ex4.sh">bash11_ex4.sh</a>) which has been run in the demonstration above.</p>
<h4 id="example-7">Example 7</h4>
<pre><code>$ cat bash11_ex5.sh
#!/bin/bash
#
# String comparison with a pattern in a variable. The pattern matches any
# word that ends with &#39;man&#39; that is longer than 3 letters.
#
pattern=&quot;+([[:word:]])man&quot;
echo &quot;Pattern is: $pattern&quot;
for str in &#39;man&#39; &#39;woman&#39; &#39;German&#39; &#39;Xman&#39; &#39;romance&#39; &#39;&#39;; do
if [[ $str == $pattern ]]; then
echo &quot;Matched &#39;$str&#39;&quot;
else
echo &quot;Didn&#39;t match &#39;$str&#39;&quot;
fi
done
$ ./bash11_ex5.sh
Pattern is: +([[:word:]])man
Didn&#39;t match &#39;man&#39;
Matched &#39;woman&#39;
Matched &#39;German&#39;
Matched &#39;Xman&#39;
Didn&#39;t match &#39;romance&#39;
Didn&#39;t match &#39;&#39;</code></pre>
<p>This example matches any word that ends with <code>'man'</code> with letters before <code>'man'</code>. The expression <code>'+([[:word:]])'</code> specifies one or more characters that that match letters, numbers and the underscore</p>
<p>As an aside, I use the <code>shellcheck</code> tool inside <code>vim</code>. It checks that any scripts I type are valid, and flags any issues. It has a problem with:</p>
<pre><code>if [[ $str == $pattern ]]; then</code></pre>
<p>It tells me that I should quote <code>$pattern</code> because otherwise it might be subject to “glob matching”. Since that is precisely what Im trying to do, this is mildly amusing.</p>
<pre><code>In ./bash11_ex5.sh line 10:
if [[ $str == $pattern ]]; then
^-- SC2053: Quote the rhs of == in [[ ]] to prevent glob matching.</code></pre>
<p>This specific error can be turned off if desired.</p>
<p>The example is available as a downloadable file (<a href="hpr2659_bash11_ex5.sh">bash11_ex5.sh</a>).</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://www.gnu.org/software/bash/manual/bash.html"><em>GNU BASH Reference Manual</em></a>
<ul>
<li><a href="https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs">“Bash Conditional Constructs”</a></li>
<li><a href="https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions">“Bash Conditional Expressions”</a></li>
<li><a href="https://www.gnu.org/software/bash/manual/bash.html#Bourne-Shell-Builtins">“Bourne Shell Builtins”</a></li>
<li><a href="https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin">“The <code>set</code> Builtin”</a></li>
<li><a href="https://www.gnu.org/software/bash/manual/bash.html#Pattern-Matching">“Bash Pattern Matching”</a></li>
</ul></li>
<li><p><a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html"><em>POSIX Shell Command Language</em></a> - documentation of all of the POSIX features mentioned in this series.</p></li>
<li><p><a href="http://hackerpublicradio.org/series.php?id=42">HPR series: <em>Bash Scripting</em></a></p></li>
<li>Previous episodes under the heading <em>Bash Tips</em>:
<ol>
<li><a href="http://hackerpublicradio.org/eps/hpr1648">HPR episode 1648 “<em>Bash parameter manipulation</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr1843">HPR episode 1843 “<em>Some Bash tips</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr1884">HPR episode 1884 “<em>Some more Bash tips</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr1903">HPR episode 1903 “<em>Some further Bash tips</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr1951">HPR episode 1951 “<em>Some additional Bash tips</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr2045">HPR episode 2045 “<em>Some other Bash tips</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr2278">HPR episode 2278 “<em>Some supplementary Bash tips</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr2293">HPR episode 2293 “<em>More supplementary Bash tips</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr2639">HPR episode 2639 “<em>Some ancillary Bash tips - 9</em></a></li>
<li><a href="http://hackerpublicradio.org/eps/hpr2649">HPR episode 2649 “<em>More ancillary Bash tips - 10</em></a></li>
</ol></li>
<li>Resources:
<ul>
<li>Examples: <a href="hpr2659_bash11_ex1.sh">bash11_ex1.sh</a>, <a href="hpr2659_bash11_ex2.sh">bash11_ex2.sh</a>, <a href="hpr2659_bash11_ex3.sh">bash11_ex3.sh</a>, <a href="hpr2659_bash11_ex4.sh">bash11_ex4.sh</a>, <a href="hpr2659_bash11_ex5.sh">bash11_ex5.sh</a></li>
</ul></li>
</ul>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p>At the time of writing I have not been able to get this to work and have not found any detailed documentation about how it is <em>meant</em> to work.<a href="#fnref1" class="footnote-back"></a></p></li>
</ol>
</section>
</article>
</main>
</div>
</body>
</html>