Files
hpr_website/www/eps/hpr2649/hpr2649_full_shownotes.html

291 lines
20 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>More ancillary Bash tips - 10 (HPR Show 2649)</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">More ancillary Bash tips - 10 (HPR Show 2649)</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="#looping-constructs">Looping Constructs</a><ul>
<li><a href="#while-command"><code>while</code> command</a></li>
<li><a href="#until-command"><code>until</code> command</a></li>
<li><a href="#examples-of-while-and-until">Examples of <code>while</code> and <code>until</code></a><ul>
<li><a href="#example-1">Example 1</a></li>
<li><a href="#example-2">Example 2</a></li>
</ul></li>
</ul></li>
<li><a href="#conditional-constructs">Conditional Constructs</a><ul>
<li><a href="#if-command"><code>if</code> command</a></li>
<li><a href="#case-command"><code>case</code> command</a></li>
<li><a href="#examples-of-if-and-case">Examples of <code>if</code> and <code>case</code></a><ul>
<li><a href="#example-3">Example 3</a></li>
<li><a href="#example-4">Example 4</a></li>
<li><a href="#example-5">Example 5</a></li>
</ul></li>
</ul></li>
<li><a href="#lists-of-commands">Lists of Commands</a><ul>
<li><a href="#and-lists">AND Lists</a></li>
<li><a href="#or-lists">OR Lists</a><ul>
<li><a href="#an-insight-into-how-these-lists-behave">An insight into how these lists behave</a></li>
<li><a href="#examples">Examples</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 my tenth contribution to the <a href="http://hackerpublicradio.org/series.php?id=42" title="*Bash Scripting* series"><em>Bash Scripting</em></a> series under the heading of <em>Bash Tips</em>. The previous episodes are listed below in the <a href="#links">Links</a> section.</p>
<p>We are currently looking at decision making in Bash, and in the last episode we examined the tests themselves. In this episode well look at the constructs that use these tests: looping constructs, conditional constructs and lists of commands.</p>
<p><strong>Note</strong>: this episode and the preceding one were originally recorded as a single episode, but because it was so long it was split into two. As a consequence the audio contains references to examples such as <code>bash9_ex2.sh</code> where the true name is <code>bash10_ex1.sh</code>. The notes have been updated as necessary but not the audio.</p>
<h2 id="looping-constructs">Looping Constructs</h2>
<p>Bash supports a number of commands which can be used to build loops. These are documented in the <em>Looping Constructs</em> section of the <a href="https://www.gnu.org/software/bash/manual/bash.html#Looping-Constructs" title="Bash Looping Constructs">GNU Bash Manual</a>. We will look only at <code>while</code> and <code>until</code> here because they contain tests. We will leave <code>for</code> loops until a later episode.</p>
<h3 id="while-command"><code>while</code> command</h3>
<p>The syntax of the <code>while</code> command is:</p>
<blockquote>
<p><b>while</b> <em>test_commands</em><br />
<b>do</b><br />
    <em>commands</em><br />
<b>done</b></p>
</blockquote>
<p>The <em>commands</em> are executed as long as <em>test_commands</em> return an exit status which is zero (loop while the result is <em>true</em>).</p>
<h3 id="until-command"><code>until</code> command</h3>
<p>The syntax of the <code>until</code> command is:</p>
<blockquote>
<p><b>until</b> <em>test_commands</em><br />
<b>do</b><br />
    <em>commands</em><br />
<b>done</b></p>
</blockquote>
<p>The <em>commands</em> are executed as long as <em>test_commands</em> return an exit status which is non-zero (loop until the result is <em>true</em>).</p>
<h3 id="examples-of-while-and-until">Examples of <code>while</code> and <code>until</code></h3>
<h4 id="example-1">Example 1</h4>
<p>The following code snippet will print variable <code>i</code> and increment it while its value is less than 5, so it will output the numbers 0..4:</p>
<pre><code>i=0
while [ &quot;$i&quot; -lt 5 ]; do
echo &quot;$i&quot;
((i++))
done</code></pre>
<p>Note that in this example the <code>while</code> and <code>do</code> parts are both on the same line, separated by a semicolon. Also, as mentioned in the last show, the quotes around <code>&quot;$i&quot;</code> are advisable in case the variable is null, but if the variable is not initialised the loop will fail whether the quotes are used or not. Even the <code>shellcheck</code> tool I use to check my Bash scripts does not complain about missing quotes here.</p>
<h4 id="example-2">Example 2</h4>
<p>The next snippet will start with variable <code>i</code> set to 5 and decrement it down to zero:</p>
<pre><code>i=5
until [ &quot;$i&quot; -eq 0 ]; do
echo &quot;$i&quot;
((i--))
done</code></pre>
<p>In this case the last value printed will be 1, after which <code>i</code> will be decremented to 0, which will stop the loop.</p>
<h2 id="conditional-constructs">Conditional Constructs</h2>
<p>Bash offers three commands under this heading, two of which have a conditional component. The commands are <code>if</code>, <code>case</code> and <code>select</code>. They are documented in the <em>Conditional Constructs</em> section of the <a href="https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs" title="Bash Conditional Constructs">GNU Bash Manual</a>. We will look only at <code>if</code> and <code>case</code> in this episode and will leave <code>select</code> until a later episode.</p>
<h3 id="if-command"><code>if</code> command</h3>
<p>This command has the syntax:</p>
<blockquote>
<p><b>if</b> <em>test_commands_1</em><br />
<b>then</b><br />
    <em>commands_1</em><br />
<b>elif</b> <em>test_commands_2</em><br />
<b>then</b><br />
    <em>commands_2</em><br />
<b>else</b><br />
    <em>commands_3</em><br />
<b>fi</b></p>
</blockquote>
<p>If <em>test_commands_1</em> returns a status of zero then <em>commands_1</em> will be executed and the <code>if</code> command will terminate. If the status is non-zero then any <code>elif</code> part will be tested, and the associated commands (<em>commands_2</em> in this example) executed if the result is <em>true</em>. There may be zero or more of these <code>elif</code> parts.</p>
<p>Once the <code>if</code> and any <code>elif</code> parts are tested and they all return <em>false</em>, the commands in the <code>else</code> part (<em>commands_3</em> here) will be executed. There may be zero or one <code>else</code> parts.</p>
<p>Note that the <code>then</code> part can be written on the same line as the <code>if</code>/<code>elif</code>, when separated by a semicolon.</p>
<h3 id="case-command"><code>case</code> command</h3>
<p>The syntax of the <code>case</code> command is as follows:</p>
<blockquote>
<p><b>case</b> <em>word</em> <b>in</b><br />
    <em>pattern_list_1</em> ) <em>command_list_1</em> ;;<br />
    <em>pattern_list_2</em> ) <em>command_list_2</em> ;;<br />
<b>esac</b></p>
</blockquote>
<p>The <code>case</code> command will selectively execute the <em>command_list</em> corresponding to the first <em>pattern_list</em> that matches <em>word</em>.</p>
<p>If a <em>pattern_list</em> contains multiple patterns then they are separated by the <code>|</code> character. The patterns are the <em>Glob</em> patterns we have already seen (<a href="http://hackerpublicradio.org/eps/hpr2278" title="Some supplementary Bash tips">show 2278</a>). The <em>pattern_list</em> is terminated by the right parenthesis (and can be preceded by a left parenthesis if desired). The list of <em>patterns</em> and an associated <em>command_list</em> is known as a <em>clause</em>.</p>
<p>There is no limit to the number of <code>case</code> clauses. The first pattern that matches determines the <em>command_list</em> that is executed. There is no default pattern, but making <code>'*'</code> the final one a pattern that will always match achieves the same thing.</p>
<p>The clause terminator must be one of <code>';;'</code>, <code>';&amp;'</code>, or <code>';;&amp;'</code>, as explained below:</p>
<table>
<colgroup>
<col style="width: 14%" />
<col style="width: 85%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Terminator</th>
<th style="text-align: left;">Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;"><code>;;</code></td>
<td style="text-align: left;">no subsequent matches are attempted after the first pattern match</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>;&amp;</code></td>
<td style="text-align: left;">execution continues with the <em>command_list</em> associated with the next clause, if any</td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>;;&amp;</code></td>
<td style="text-align: left;">causes the shell to test the patterns in the next clause, if any, and execute any associated <em>command_list</em> on a successful match</td>
</tr>
</tbody>
</table>
<h3 id="examples-of-if-and-case">Examples of <code>if</code> and <code>case</code></h3>
<h4 id="example-3">Example 3</h4>
<p>In this example shows the full range of this structured command <code>'if'</code> with <code>elif</code> and <code>else</code> branches:</p>
<pre><code>fruit=&quot;apple&quot;
if [ &quot;$fruit&quot; == &quot;banana&quot; ]; then
echo &quot;$fruit: don&#39;t eat the skin&quot;
elif [ &quot;$fruit&quot; == &quot;apple&quot; ]; then
echo &quot;$fruit: eat the skin or not, as you please&quot;
elif [ &quot;$fruit&quot; == &quot;kiwi&quot; ]; then
echo &quot;$fruit: most people remove the skin&quot;
else
echo &quot;$fruit: not sure how to advise&quot;
fi</code></pre>
<p>See the downloadable example script <a href="hpr2649_bash10_ex1.sh">bash10_ex1.sh</a><a href="#fn1" class="footnote-ref" id="fnref1"><sup>1</sup></a> which uses the above <code>if</code> structure in a <code>for</code> loop. Run it yourself to see what it does.</p>
<h4 id="example-4">Example 4</h4>
<p>Here is the same idea using a <code>case</code> command:</p>
<pre><code>fruit=&quot;apple&quot;
case $fruit in
banana) echo &quot;$fruit: don&#39;t eat the skin&quot; ;;
apple) echo &quot;$fruit: eat the skin or not, as you please&quot; ;;
kiwi) echo &quot;$fruit: most people remove the skin&quot;;;
*) echo &quot;$fruit: not sure how to advise&quot;
esac</code></pre>
<p>See the downloadable example script <a href="hpr2649_bash10_ex2.sh">bash10_ex2.sh</a><a href="#fn2" class="footnote-ref" id="fnref2"><sup>2</sup></a> which uses a <code>case</code> command similar to the above in a <code>for</code> loop.</p>
<h4 id="example-5">Example 5</h4>
<p>This example has been added since the audio was recorded to give an example of the use of the <code>;;&amp;</code> clause terminator in a <code>case</code> command.</p>
<p>The following downloadable example (<a href="hpr2649_bash10_ex3.sh">bash10_ex3.sh</a>) demonstrates this:</p>
<pre><code>$ cat bash10_ex3.sh
#!/bin/bash
#
# Further demonstration of the &#39;case&#39; command with alternative clause
# terminators
#
i=704526
echo &quot;Number given is: $i&quot;
case $i in
*0*) echo &quot;it contains a 0&quot; ;;&amp;
*1*) echo &quot;it contains a 1&quot; ;;&amp;
*2*) echo &quot;it contains a 2&quot; ;;&amp;
*3*) echo &quot;it contains a 3&quot; ;;&amp;
*4*) echo &quot;it contains a 4&quot; ;;&amp;
*5*) echo &quot;it contains a 5&quot; ;;&amp;
*6*) echo &quot;it contains a 6&quot; ;;&amp;
*7*) echo &quot;it contains a 7&quot; ;;&amp;
*8*) echo &quot;it contains a 8&quot; ;;&amp;
*9*) echo &quot;it contains a 9&quot; ;;
esac
exit
$ ./bash10_ex3.sh
Number given is: 704526
it contains a 0
it contains a 2
it contains a 4
it contains a 5
it contains a 6
it contains a 7</code></pre>
<p>The script sets variable <code>'i'</code> to a 6-digit number. The number is displayed with an <code>echo</code> command. The <code>case</code> command tests the variable with glob patterns containing all of the digits 0-9. Each case clause (except the last) is terminated with the <code>;;&amp;</code> sequence which means that each clause is invoked regardless of the success or failure of the preceding one.</p>
<p>The end result is that every pattern is tested and those that match generate output. If the case clauses had used the usual <code>;;</code> terminators then the <code>case</code> command would exit after the first match.</p>
<h2 id="lists-of-commands">Lists of Commands</h2>
<p>Bash commands can be typed in lists. The simplest list is just a series of commands (or pipelines - a subject we will look at more in later shows in the <em>Bash Tips</em> series), each separated by a newline.</p>
<p>However, there are other list separators such as <code>';'</code>, <code>'&amp;'</code>, <code>'&amp;&amp;'</code>, and <code>'||'</code>. The first two, <code>';'</code> and <code>'&amp;'</code> are not really relevant to decision making, so we will omit these for now. However so-called <em>AND</em> and <em>OR</em> lists are relevant. These consist of commands or pipelines separated by <code>'&amp;&amp;'</code> (logical <em>AND</em>), and <code>'||'</code> (logical <em>OR</em>).</p>
<h3 id="and-lists">AND Lists</h3>
<p>An AND list has the form:</p>
<pre><code>command1 &amp;&amp; command2</code></pre>
<p><em>command2</em> is executed if, and only if, <em>command1</em> returns an exit status of zero.</p>
<h3 id="or-lists">OR Lists</h3>
<p>An OR list has the form</p>
<pre><code>command1 || command2</code></pre>
<p><em>command2</em> is executed if, and only if, <em>command1</em> returns a non-zero exit status.</p>
<h4 id="an-insight-into-how-these-lists-behave">An insight into how these lists behave</h4>
<p>These operators <em>short circuit</em>:</p>
<ul>
<li>in the case of <code>'&amp;&amp;'</code> an attempt is being made to determine the result of applying a logical <em>AND</em> operation between the two operands. They both need to be <em>true</em> before the overall result is <em>true</em>. If the first operand (<em>command1</em>) is <em>false</em> then there is no need to compute the second result, the overall result <u>must</u> be <em>false</em>, so there is a <em>short circuit</em>.</li>
<li>in the case of <code>'||'</code> either or both of the operands of the logical <em>OR</em> operation can be <em>true</em> to give an overall result of <em>true</em>. Thus if <em>command1</em> returns <em>true</em> nothing else need be done to determine the overall result, whereas if <em>command1</em> is <em>false</em>, then <em>command2</em> must be executed to determine the overall result.</li>
</ul>
<p>I found it useful to consider this when using these types of lists, so I am sharing it with you.</p>
<h4 id="examples">Examples</h4>
<p>It is common to see these used in scripts as a simplified form of decision with an explicit test as <em>command1</em>. For example, you might see:</p>
<pre><code>[ -e /some/file ] || exit 1</code></pre>
<p>Here the script will exit if the named file does not exist (we will look at the <code>-e</code> operator in the next episode). Note that it exits with a non-zero result so that the script itself could be used as <em>command1</em> in an AND or OR list.</p>
<p>It is possible to execute several commands instead of just the <code>exit</code> by grouping them in curly braces (<code>'{}'</code>). For example:</p>
<pre><code>[ -e /home/user1/somefile ] || { echo &quot;Unable to find /home/user1/somefile&quot;; exit 1; }</code></pre>
<p>It is necessary to type a space<a href="#fn3" class="footnote-ref" id="fnref3"><sup>3</sup></a> after <code>'{'</code> and before <code>'}'</code>. Also each command within the braces must end with a semicolon (or a newline).</p>
<p>This example could be written as follows, remembering that <code>test</code> is an alternative to <code>'[...]'</code>:</p>
<pre><code>test -e /home/user1/somefile || {
echo &quot;Unable to find /home/user1/somefile&quot;
exit 1
}</code></pre>
<p>As we have already seen it is possible to use any test or command which returns an exit status of zero or non-zero as <em>command1</em> in a list. So the following command list is equivalent to the <code>'if'</code> example above:</p>
<pre><code>grep -q -e &#39;^banana$&#39; fruit.txt &amp;&amp; echo &quot;Found a banana&quot;</code></pre>
<p>However, it is my opinion that it is clearer and more understandable when the <code>'if'</code> alternative is used.</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#Looping-Constructs">“Bash Looping Constructs”</a></li>
<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#Bourne-Shell-Builtins">“Bourne Shell Builtins”</a></li>
</ul></li>
<li><a href="http://hackerpublicradio.org/series.php?id=42">HPR series: <em>Bash Scripting</em></a></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>
</ol></li>
<li>Resources:
<ul>
<li>Examples: <a href="hpr2649_bash10_ex1.sh">bash10_ex1.sh</a>, <a href="hpr2649_bash10_ex2.sh">bash10_ex2.sh</a>, <a href="hpr2649_bash10_ex3.sh">bash10_ex3.sh</a></li>
</ul></li>
</ul>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p>The audio refers to the examples by the name they had before the one long show was split into two. What was <code>bash9_ex2.sh</code> has become <code>bash10_ex1.sh</code>.<a href="#fnref1" class="footnote-back"></a></p></li>
<li id="fn2"><p>The audio refers to the examples by the name they had before the one long show was split into two. What was <code>bash9_ex3.sh</code> has become <code>bash10_ex2.sh</code>.<a href="#fnref2" class="footnote-back"></a></p></li>
<li id="fn3"><p>Technically this should be <u>whitespace</u> which means one or more spaces, tabs or newlines.<a href="#fnref3" class="footnote-back"></a></p></li>
</ol>
</section>
</article>
</main>
</div>
</body>
</html>