Files
hpr_website/eps/hpr2649/hpr2649_full_shownotes.html
2025-10-28 18:39:57 +01:00

291 lines
20 KiB
HTML
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>