Files
hpr_website/www/eps/hpr1757/hpr1757_full_shownotes.html

219 lines
14 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>Useful Bash functions (HPR Show 1757)</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">
<style type="text/css">
pre { background: #eee;
border: 1px solid #ddd;
overflow: auto;
clear: both;
}
</style>
</head>
<body id="home">
<div id="container" class="shadow">
<header>
<h1 class="title">Useful Bash functions (HPR Show 1757)</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="#overview">Overview</a></li>
<li><a href="#example-functions">Example Functions</a><ul>
<li><a href="#the-pad-function">The pad function</a><ul>
<li><a href="#code">Code</a></li>
<li><a href="#explanation">Explanation</a></li>
</ul></li>
<li><a href="#the-yes_no-function">The yes_no function</a><ul>
<li><a href="#code-1">Code</a></li>
<li><a href="#explanation-1">Explanation</a></li>
</ul></li>
</ul></li>
<li><a href="#links">Links</a></li>
</ul>
</nav>
</header>
<h2 id="overview">Overview</h2>
<p>I enjoy writing Bash scripts to solve various problems. In particular I have a number of scripts I use to manage the process of preparing a show for HPR, which I am developing at the moment.</p>
<p>My more complex Bash scripts use a lot of functions to perform the various tasks, and, in the nature of things, some of these functions can be of use in other scripts and are shared between them.</p>
<p>I thought I would share some of these functions with HPR listeners in the hopes that they might be useful. It would also be interesting to receive feedback on these functions and would be great if other Bash users contributed ideas of their own.</p>
<h2 id="example-functions">Example Functions</h2>
<p>The following functions are designed to be used in shell scripts. I have a few other functions, some of which I use from the command line, but I will leave discussing them for another time.</p>
<p>The way I usually include functions in scripts is to keep them all in a file I call <code>function_lib.sh</code> in the project directory. I then add the following to my script (the variable <code>BASEDIR</code> holds the project directory):</p>
<pre><code>#
# Load library functions
#
LIB=&quot;$BASEDIR/function_lib.sh&quot;
[ -e $LIB ] || { echo &quot;Unable to load functions&quot;; exit; }
source $LIB</code></pre>
<h3 id="the-pad-function">The pad function</h3>
<p>This is a simple function whose purpose is to write formatted lines to the screen. It outputs some text, padded to a chosen length using a chosen character. It adds the padding on the right, left or both sides to make the text centred in the width.</p>
<p>The arguments it requires are:</p>
<ol type="1">
<li>The text to display</li>
<li>The desired length of the padded string (default 80)</li>
<li>The character to pad with (default '-')</li>
<li>The side the padding is to be added: <em>L</em>, <em>R</em> or <em>C</em> (centre) (default <em>R</em>)</li>
</ol>
<p>The function might be called as follows to achieve the results shown:</p>
<pre><code>pad &#39;Title &#39; 40 &#39;=&#39;
Title ==================================
pad &#39; Title&#39; 40 &#39;=&#39; L
================================== Title
pad &#39; Title &#39; 40 &#39;=&#39; C
================ Title =================</code></pre>
<p>It can also be used to output a line of 80 hyphens with the call:</p>
<pre><code>pad &#39;-&#39;</code></pre>
<p>I often use this function to generate lines and headers in reports I display on the terminal.</p>
<h4 id="code">Code</h4>
<pre><code> 1 #=== FUNCTION ================================================================
2 # NAME: pad
3 # DESCRIPTION: Pad $text on the $side with $char characters to length $length
4 # PARAMETERS: 1 - the text string to pad (no default)
5 # 2 - how long the padded string is to be (default 80)
6 # 3 - the character to pad with (default &#39;-&#39;)
7 # 4 - the side to pad on, L or R or C for centre (default R)
8 # RETURNS: Nothing
9 #===============================================================================
10 pad () {
11 local text=${1?Usage: pad text [length] [character] [L|R|C]}
12 local length=${2:-80}
13 local char=${3:--}
14 local side=${4:-R}
15 local line l2
16
17 [ ${#text} -ge $length ] &amp;&amp; { echo &quot;$text&quot;; return; }
18
19 char=${char:0:1}
20 side=${side^^}
21
22 printf -v line &quot;%*s&quot; $(($length - ${#text})) &#39; &#39;
23 line=${line// /$char}
24
25 if [[ $side == &quot;R&quot; ]]; then
26 echo &quot;${text}${line}&quot;
27 elif [[ $side == &quot;L&quot; ]]; then
28 echo &quot;${line}${text}&quot;
29 elif [[ $side == &quot;C&quot; ]]; then
30 l2=$((${#line}/2))
31 echo &quot;${line:0:$l2}${text}${line:$l2}&quot;
32 fi
33 }</code></pre>
<h4 id="explanation">Explanation</h4>
<ul>
<li><p>I use a Vim plugin called <em>Bash Support</em> which can generate a standard comment template and function boilerplate, and I have used this here to generate this function and the comment template in lines 1-9.</p></li>
<li><p>The function starts (lines 11-14) by declaring a number of <code>local</code> variables to hold the arguments. Only one argument, the text to output, is mandatory. The declaration of variable <code>text</code> uses the Bash parameter manipulation feature <em>Display Error if Null or Unset</em> which will abort the function and the calling script with an error message if no value is provided.</p></li>
<li><p>The other variable declarations supply default values using the Bash feature <em>Use default values</em>.</p></li>
<li><p>One of the instances where the function needs to take special action is if the supplied text is as long or longer than the total length. The expression on line 17 tests for this, and if found to be true it simply displays the text and returns from the function.</p></li>
<li><p>Next (lines 19 and 20) the variable <code>char</code> is processed, ensuring it's only one character long, and <code>side</code> is forced to upper-case.</p></li>
<li><p>The next part (line 22) uses <code>printf</code> to create the padding characters. The <code>-v</code> option to <code>printf</code> writes the result to a variable. The format string just consists of a <code>%s</code> specifier, used for writing a string. The asterisk (<code>*</code>) after the percent sign (<code>%</code>) causes <code>printf</code> to get the width of the string from the argument list.</p></li>
<li><p>The first argument to <code>printf</code> (after the format string) is the result of an arithmetic expression where the length of the text is subtracted from the desired length. The second argument is a space. So, the <code>printf</code> generates a space-filled string of the required length and stores it in the variable <code>line</code>.</p></li>
<li><p>The next statement (line 23) replaces all the spaces with the padding character. This uses the Bash feature <em>Pattern substitution</em>.</p></li>
<li><p>Finally, the function uses an <code>if</code> statement (lines 25-32) to determine how to display the text and the padding. If <code>side</code> is <em>R</em> or <em>L</em> the padding is on the right or left respectively. If it is <em>C</em> then half of the padding is placed on one side and half on the other. Parts of the padding string are selected with the Bash feature <em>Substring Expansion</em> (line 31).</p></li>
</ul>
<p>The function does a good enough job for my needs. It does not deal with the case where the padding character is a space, but that is not a problem as far as I am concerned. It may be a little too simplistic for your tastes.</p>
<h3 id="the-yes_no-function">The yes_no function</h3>
<p>This another simple function which asks a question and waits for a yes/no reply. It returns a true/false result so it can be used thus:</p>
<pre><code>if ! yes_no &#39;Do you want to continue? &#39; &#39;No&#39;; then
return
fi</code></pre>
<p>It takes two arguments:</p>
<ol type="1">
<li>The prompt string</li>
<li>An optional default value.</li>
</ol>
<p>It returns <code>true</code> (0) if the response is either <em>Y</em> or <em>Yes</em>, regardless of case, and <code>false</code> (1) otherwise.</p>
<h4 id="code-1">Code</h4>
<pre><code> 1 #=== FUNCTION ================================================================
2 # NAME: yes_no
3 # DESCRIPTION: Read a Yes or No response from STDIN and return a suitable
4 # numeric value
5 # PARAMETERS: 1 - Prompt string for the read
6 # 2 - Default value (optional)
7 # RETURNS: 0 for a response of Y or YES, 1 otherwise
8 #===============================================================================
9 yes_no () {
10 local prompt=&quot;${1:?Usage: yes_no prompt [default]}&quot;
11 local default=&quot;${2// /}&quot;
12 local ans res
13
14 if [[ -n $default ]]; then
15 default=&quot;-i $default&quot;
16 fi
17
18 #
19 # Read and handle CTRL-D (EOF)
20 #
21 read -e $default -p &quot;$prompt&quot; ans
22 res=&quot;$?&quot;
23 if [[ $res -ne 0 ]]; then
24 echo &quot;Read aborted&quot;
25 return 1
26 fi
27
28 ans=${ans^^}
29 ans=${ans//[^YESNO]/}
30 if [[ $ans =~ ^Y(E|ES)?$ ]]; then
31 return 0
32 else
33 return 1
34 fi
35 }</code></pre>
<h4 id="explanation-1">Explanation</h4>
<ul>
<li><p>The function starts (lines 10 and 11) by declaring a number of <code>local</code> variables to hold the arguments. Only one argument, the prompt, is mandatory. The declaration of variable <code>prompt</code> uses the Bash parameter manipulation feature <em>Display Error if Null or Unset</em> which will abort the function and the calling script with an error message if no value is provided.</p></li>
<li><p>The declaration of the <code>default</code> variable (line 11) copies the second argument and strips any spaces from it. This is because the answers catered for must not contain spaces.</p></li>
<li><p>If the <code>default</code> variable is not empty (lines 14-16) the string &quot;<code>-i</code>&quot; is prepended to it. This is going to be used as an option in the following <code>read</code> command. Note that the substitution of <code>default</code> should really be enclosed in double quotes if it contained spaces, but since we have stripped them out previously we can do this.</p></li>
<li>Next (line 21) a <code>read</code> command is issued to obtain input from the user.
<ul>
<li>The &quot;<code>-e</code>&quot; option ensures that the <em>readline</em> library is used to read the value. This permits line editing in the same way as on the command line.</li>
<li>If there is a default value then this is passed through the &quot;<code>-i</code>&quot; option which we have already added to the <code>default</code> variable. If there is no default value then nothing will be substituted here.</li>
<li>The &quot;<code>-p</code>&quot; option specifies the prompt string.</li>
<li>The result of the <code>read</code> is written to the variable <code>ans</code>.</li>
</ul></li>
<li><p>The <code>read</code> command returns a true or false result (as do all Bash commands), and this can be found in the special variable &quot;<code>?</code>&quot;. This is stored in the local variable <code>res</code> (line 22).</p></li>
<li><p>If the <code>res</code> variable is not true (0) then the following <code>if</code> statement (lines 23-26) will display &quot;Read aborted&quot; and exit the function with a false result. This false result from the <code>read</code> will result from the user pressing <em>CTRL-D</em> (meaning <em>end of file</em>) to abort the script.</p></li>
<li><p>The variable <code>ans</code> contains the answer the user typed (or accepted) and this is then processed in various ways (lines 28 and 29). First it is forced to upper case, then any letters other than &quot;<em>YESNO</em>&quot; are removed.</p></li>
<li><p>Finally, an <code>if</code> statement (lines 30-34) compares <code>ans</code> to the regular expression <code>^Y(E|ES)?$</code>. This matches if the answer begins with a Y and is optionally followed by an E or by ES. If there is a match the function returns true (0), otherwise it returns false (1).</p></li>
</ul>
<p>This way of doing things means that the reply '<code>Yup great</code>' is stripped down to <code>YE</code> which is a match. Many other words that reduce to Y, YE or YES like '<code>Yeast</code>' also match. This might not be a good idea in your particular case.</p>
<p>The other aspect of this function you might find slightly undesirable is the way the default is provided. If given, the default value will be on the input line and to override it you will need to delete it (<em>CTRL-W</em> is what I use). I am happy with this but you might not be!</p>
<h2 id="links">Links</h2>
<ol type="1">
<li><em>Bash Support</em> Vim plugin: <a href="http://www.vim.org/scripts/script.php?script_id=365">http://www.vim.org/scripts/script.php?script_id=365</a></li>
<li>HPR episode <em>Bash parameter manipulation</em>: <a href="http://hackerpublicradio.org/eps/hpr1648">http://hackerpublicradio.org/eps/hpr1648</a></li>
<li>How to write functions (from <em>The Linux Documentation Project</em>):
<ul>
<li><em>Functions</em>: <a href="http://tldp.org/LDP/abs/html/functions.html">http://tldp.org/LDP/abs/html/functions.html</a></li>
<li><em>Complex Functions and Function Complexities</em>: <a href="http://tldp.org/LDP/abs/html/complexfunct.html">http://tldp.org/LDP/abs/html/complexfunct.html</a></li>
<li><em>Examples of functions in scripts</em>: <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_11_02.html">http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_11_02.html</a></li>
</ul></li>
<li>Download the <em>pad</em> and <em>yes_no</em> functions: <a href="hpr1757_functions.sh">hpr1757_functions.sh</a></li>
</ol>
<!--
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
-->
</article>
</main>
</div>
</body>
</html>