<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="https://pekonen.cc/feeds/software.xml" rel="self" type="application/rss+xml" />
<title>Jussi Pekonen - Software</title>
<link>https://pekonen.cc/software/</link>
<description>The RSS feed for stuff published at https://pekonen.cc/software/, written by Jussi Pekonen</description>
<pubDate>Sat, 05 Sep 2020 16:54:50 +0300</pubDate>
<lastBuildDate>Tue, 25 Jan 2022 10:54:27 +0200</lastBuildDate>

<item>
<title>Running automated checks on your code on commits</title>
<description>
<![CDATA[<section>

<p>Now that I have been working on this site and its different components<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:1" id="fnref:1" title="see footnote" class="footnote"><sup>1</sup></a>, I was thinking about the quality of the code that runs this site. As I have ”exposed” earlier, this site is, more or less, just <a href="https://pekonen.cc/b/20160218-SiteTech2/">static HTML files that are enhanced with Javascript</a> and that <a href="https://pekonen.cc/b/20151222-SiteTech1/">those files are served from a Git repository</a>. Those Javascript components just read certain files and generate the HTML code from them. Therefore, I was just wondering if the Javascript code I have written is any good<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:2" id="fnref:2" title="see footnote" class="footnote"><sup>2</sup></a>. How would I check that? I could write tests, but there is a light-weight alternative that can point out potential issues and thus help writing better code.</p>

<h3 id="staticcodeanalysis">Static code analysis</h3>

<p>I am obviously talking about static code analysis, also known as linting. Like I mentioned in <a href="https://pekonen.cc/s/20200905-Bash/">the safe(r) Bash coding post</a>, a linter can help you to write <em>safer</em> code. The linter(s) can spot some problematic code that might cause some unintentional side effects. Obviously, a linter cannot spot all issues (more on this later), but it is a good starting point. Also, running the linter often enough can &#8220;teach&#8221; you to write better code, even though it might <em>feel</em> wrong at first. But after a while, you start to spot the potential issues yourself and write the code in a &#8220;linter-approved&#8221; way.</p>

<p>Like I said above, running a linter is in no way a perfect shortcut to bug-free code. It cannot spot potential code flow issues because it just checks how the programming language features are applied. Also, as there might be numerous linters available for the language in question, some issues might not even be spotted by one alternative while another linter considers them as errors. For example, for Javascript there are 3 fairly popular linters: <a href="https://www.jslint.com/"><code>jslint</code></a>, <a href="https://jshint.com/"><code>jshint</code></a>, and <a href="https://eslint.org/"><code>eslint</code></a><a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:3" id="fnref:3" title="see footnote" class="footnote"><sup>3</sup></a>. The first, <code>jslint</code>, is fairly superficial and will not spot all the potential issues. The second, <code>jshint</code> has a broader rule set than <code>jslint</code>, which means that Javascript code that passes <code>jslint</code> might not pass <code>jshint</code>. <code>eslint</code> is the most advanced linter of this group and it <em>will</em> fail code that passes the other two alternatives<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:4" id="fnref:4" title="see footnote" class="footnote"><sup>4</sup></a>.</p>

<p>Oftentimes a linter just applies checks on the code, but some of them can also check code styling. Of the linters listed above, <code>eslint</code> has this feature as a built-in, <em>if</em> requested by the <code>eslint</code> configuration. If code styling should be enforced, it will also fail the linter check. However, usually that feature comes together with an option to <em>fix</em> the issues. Moreover, it is often possible to configure <em>how</em> that linter should be run and what checks it would execute for each project separately.</p>

<h3 id="runninglintersinagitpre-commithook">Running linters in a Git pre-commit hook</h3>

<p>The obvious question is, <em>when</em> these linters should be run? One could run them manually every now and then, but that would not enforce the check when writing new code. One option, which I would recommend, is to run the linters in a Git <em>pre-commit hook</em>. A pre-commit hook is a script that is run <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">before the actual commit object is created</a> to the repository. If the pre-commit hook exits with a non-zero value, the commit is aborted and it will not be created. Therefore, it a good candidate for running the linter checks so that only good code gets committed to the repository.</p>

<p>In practice, the Git repository should have an <em>executable</em> file <code>.git/hooks/pre-commit</code> in the Git repository that runs the desired linters and that exits with exit code 0 when they pass or with exit code 1 when they fail. For example, the Javascript hooks (see above) could be run with the following pre-commit hook:</p>

<pre><code class="microlight">#!/bin/bash

function runJavascriptLinter() {
	local linter=&quot;$1&quot;
	shift
	local files=&quot;$*&quot;
	local result
	&quot;${linter}&quot; &quot;${files}&quot;
	result=&quot;$?&quot;
	if [ &quot;${result}&quot; -gt &quot;0&quot; ]; then
		exit 1
	fi
}

function main() {
	local files
	files=$(git ls-files | grep -E &quot;\.js$&quot;)
	# Run jslint
	runJavascriptLinter &quot;jslint&quot; &quot;${files}&quot;
	# Run jshint
	runJavascriptLinter &quot;jshint&quot; &quot;${files}&quot;
	# Run eslint
	runJavascriptLinter &quot;eslint&quot; &quot;${files}&quot;
}

main
</code></pre>

<p>Should any of the checks fail, the script would exit with a non-zero code and the pre-commit check would fail.</p>

<p>That example runs the Javascript checks on <em>all</em> files currently in the repository (<code>git ls-files</code>) ending with &#8220;.js&#8221; (<code>grep -E &quot;\.js$&quot;</code>)<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:5" id="fnref:5" title="see footnote" class="footnote"><sup>5</sup></a>. If you would like to check only those files that are being committed that have been either added or modified, change the command for the <code>files</code> variable of the <code>main</code> function to this:</p>

<pre><code class="microlight">files=$(git diff --staged HEAD --name-status | awk '/^(M|A)/ {print $2}' | grep -E &quot;\.js$&quot;)
</code></pre>

<p>This command will check which files have been staged for the commit (<code>git diff --staged HEAD --name-status</code>) and filters those that are added or modified <em>but not deleted</em> (<code>awk '/^(M|A)/ { print $2 }'</code>) before it picks only the Javascript files.</p>

<h3 id="runningthelinterchecksonallgitrepositoriesandforallprogramminglanguagesyouuse">Running the linter checks on <em>all</em> Git repositories and for <em>all</em> programming languages you use</h3>

<p>The example given above runs three different linters on Javascript code. How about a scenario where you are coding in multiple programming languages? How could one run linters on all of those? Does one need to write a separate pre-commit hook for every repository? Well, there is an easy solution: handling the linter runs as library functionality<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:6" id="fnref:6" title="see footnote" class="footnote"><sup>6</sup></a> and using Git&#8217;s <a href="https://git-scm.com/docs/git-init#_template_directory">repository template functionality</a>.</p>

<p>In Git, if you add lines</p>

<pre><code class="microlight">[init]
	templatedir = &lt;template_directory&gt;
</code></pre>

<p>to your <code>~/.gitconfig</code> file, all new repository clones and inits will copy data from the directory defined by <code>templatedir</code> to the repository&#8217;s <code>.git</code> directory. Alternatively, you can define the template directory in the <code>git clone</code> command or in the <code>git init</code> command using a parameter <code>--template=&lt;template_directory&gt;</code>. If that directory contains a subdirectory <code>hooks</code> that contains the executable pre-commit script, it will be copied to that repository and it will be run when you execute a commit.</p>

<p>Using this approach together with library functions will allow you to run the linter checks on every commit on every repository. In practice, this would mean that the pre-commit hook defined in the template directory is like this:</p>

<pre><code class="microlight">#!/bin/bash

HOOK_FUNCTIONS_PATH=&quot;${HOME}/hook-functions&quot;
# shellcheck source=&lt;path_to_hook_functions&gt;/git-files-for-linting.bash
source &quot;${HOOK_FUNCTIONS_PATH}/git-files-for-linting.bash&quot; # Defines function getGitFilesForLinting
# shellcheck source=&lt;path_to_hook_functions&gt;/linters.bash
source &quot;${HOOK_FUNCTIONS_PATH}/linters.bash&quot; # Defines function runLinters

function main() {
	local files
	files=$(getGitFilesForLinting)
	# Run linters
	runLinters &quot;${files}&quot;
}

main
</code></pre>

<p>In this example, the <code>getGitFilesForLinting</code> function will pick the files that the linters are to be run on (all files in the repository or the ones that got modified or added) while the <code>runLinters</code> function would run the desired linters on those files:</p>

<pre><code class="microlight">#!/bin/bash

function runJavascriptLinter() {
	local linter=&quot;$1&quot;
	shift
	local files=&quot;$*&quot;
	local result
	&quot;${linter}&quot; &quot;${files}&quot;
	result=&quot;$?&quot;
	if [ &quot;${result}&quot; -gt &quot;0&quot; ]; then
		exit 1
	fi
}

function runJavascriptLinters() {
	local files
	files=$(echo &quot;$*&quot; | grep -E &quot;\.js$&quot;)
	if [ -z &quot;${files}&quot; ]; then
		# No files to lint, return
		return 0
	fi
	# Run jslint
	runJavascriptLinter &quot;jslint&quot; &quot;${files}&quot;
	# Run jshint
	runJavascriptLinter &quot;jshint&quot; &quot;${files}&quot;
	# Run eslint
	runJavascriptLinter &quot;eslint&quot; &quot;${files}&quot;
}

# Define other linter functions here

function runLinters() {
	local files=&quot;$*&quot;
	# Run Javascript linters
	runJavascriptLinters &quot;${files}&quot;
	# Add other linters here
}

</code></pre>

<p>One can obviously move those Javascript linting functions to a different library file, so that they can be modified independent of the <code>linters.bash</code> file. When these are stored separate from the <code>~/.git/templates</code> directory and they are referred to as library files in the pre-commit script, one can modify and extend the checks without the need of copying the updated pre-commit hook to all repositories after the update. For example, one could add HTML and/or CSS linting to the flow after Javascript linter run, the script could install the needed linters if they are not installed yet, or one could add option to skip certain linters if defined in the Git configuration.</p>

<h3 id="testrunsinthepre-commitstagearethereanypotentialpitfalls">Test runs in the pre-commit stage? Are there any potential pitfalls?</h3>

<p>You might have been asking, why one should not run tests on the pre-commit hook. That is a totally valid question, but the reason is faily simple: running a linter (or few of them) is relatively fast process whereas running a test suite can take a lot of time. Especially when you are dealing with tests on programming languages or platforms that are not quick to execute. For example, while a simple unit test written in Python might take a fraction of a second, running an Android UI test (on an emulator) might take several minutes to complete. Also, tests are commonly run <em>after</em> a push to the remote repository by a continuous integration (CI) system so that it does not consume the resources of the developer machine.</p>

<p>Therefore, it is recommended that the pre-commit check should be as lightweight as possible. Running a linter is relatively quick and when the checks are applied to only those files that got modified or added, the check will not make the commit process significantly slower than without it. One can, obviously, add unit test run to the pre-commit hook, but in that case I would recommend that it should be disabled by default and enabled only on per repository basis. In other words, there could be a Git configuration flag, for example, <code>user.pre-commit-hook.runtests</code>, that is false in your global Git configuration (that is, in your <code>~/.gitconfig</code>) and that could be overridden in the repository-specific configuration (that is, in the <code>.git/config</code> file)<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:7" id="fnref:7" title="see footnote" class="footnote"><sup>7</sup></a>.</p>

<p>The pre-commit hook comes with caveats, though. As these pre-commit hooks are not tied to the repository itself, they are not shared among the different developers of that repository. They are <em>personal</em>, and only you manage and maintain them<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:8" id="fnref:8" title="see footnote" class="footnote"><sup>8</sup></a>. If the project wants to enforce certain linters, they should be an integral part of the repository and they should be then run as part of the CI process.</p>

<p>Also, one can skip running the pre-commit script by using the <code>--no-verify</code> option of the <code>git commit</code> command. But, as the pre-commit script is personal, it is <em>your</em> responsibility to know <em>when</em> you can use it. Furthermore, one can obviously overwrite the copied hook template in the repository&#8217;s configuration. Again, that is <em>your</em> responsibility to know <em>what</em> you are doing if you are willing to do that.</p>

<h3 id="howaboutotherversioncontrolsystems">How about other version control systems?</h3>

<p>The description above refers to <a href="https://git-scm.com/">Git</a> everywhere. While Git is the <em>de facto</em> standard of source control management (SCM) systems, some repositories are managed by some other system. How does one implement a similar solution on those, like <a href="https://www.mercurial-scm.org/">Mercurial</a> or <a href="https://subversion.apache.org/">Subversion</a>?</p>

<p>Mercurial is a SCM fairly similar to Git. It also has hooks, and one can run a pre-commit hook before the actual commit takes place in a similar fashion to Git. However, with Mercurial, there is no automatic hook template copying functionality. Instead, the hooks to be executed need be defined in the <a href="https://www.mercurial-scm.org/doc/hgrc.5.html#hooks">repository config separately</a> (in the repository&#8217;s <code>.hg/hgrc</code>) instead of being enabled in a certain directory <em>or</em> one can define them <a href="https://www.mercurial-scm.org/doc/hgrc.5.html#files">globally</a> (in <code>${HOME}/.hgrc</code>). Like with Git, these rules are not part of the code stored in the repository, so the configuration is your personal<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:9" id="fnref:9" title="see footnote" class="footnote"><sup>9</sup></a>. With Mercurial, command <code>hg files</code> will return all the tracked files in the repository and command <code>hg status | awk '/^(M|A)/ {print $2}'</code> will return the files that are being modified or added in the upcoming commit.</p>

<p>Also Subversion has hooks, but whereas Git and Mercurial has &#8220;client-side&#8221; hooks, with Subversion they all are <a href="https://svnbook.red-bean.com/en/1.8/svn.ref.reposhooks.html">run on the server</a>. That means that all Subversion repository users will have the same script executed on all their commits. There are 3 different commit-related hooks available on Subversion: start-commit, pre-commit, and post-commit. The first is executed when the commit transaction is created, the second is executed just before the commit transaction is promoted to the new revision, and the last is executed after the new revision is created. In the start-commit hook, there is no information what files have been changed by the commit, thus making it not suitable for this purpose. The pre-commit hook, on the other hand, can fetch information about the changed files (using <code>svnlook changed --transaction &lt;id&gt; | awk '/^(U|A)/ {print $2}'</code> where <code>&lt;id&gt;</code> is the ID of the transaction created at this stage), but as the script is run on the server before the new revision is promoted it cannot fetch the updated file contents yet. That can be done in the post-commit hook, but, as said, it will be executed <em>after</em> the new revision has been created<a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fn:10" id="fnref:10" title="see footnote" class="footnote"><sup>10</sup></a>. Therefore, the commit will not be aborted as it has already been completed.</p>

<section class="footnotes">
<hr />
<ol>

<li id="fn:1">
<p>I managed to break one crucial component which I then obviously had to fix. You bet I felt like an idiot when I spotted that. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:1" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:2">
<p>Answer: It is not great, passable maybe… <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:2" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:3">
<p>I am deliberately excluding <a href="https://prettier.io">prettier</a>, which does more than just Javascript linting. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:3" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:4">
<p>Unless you are very proficient Javascript developer who has used <code>eslint</code> before. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:4" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:5">
<p>This could be achieved with <code>git ls-files **/*.js</code> as well, but there are reasons why I did it like this. Please continue reading. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:5" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:6">
<p>See my <a href="https://pekonen.cc/s/20200905-Bash/">Bash programming post</a> on the reason <em>why</em> one should use library functions. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:6" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:7">
<p>To set such a configuration parameter, one can either add a section <code>[user &quot;pre-commit-hook&quot;]</code> with a parameter <code>runtests = &lt;value&gt;</code> to the config file or use a command <code>git config [--global] --add user.pre-commit-hook.runtests &lt;value&gt;</code>. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:7" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:8">
<p><em>Unless</em> there is a pre-commit hook defined on the Git installation. This might be a case where the development happens on a shared server resource. And yes, this might still be happening. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:8" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:9">
<p>Unless, like with Git, the Mercurial installation has the hook configuration defined. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:9" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

<li id="fn:10">
<p>In other words, it acts like a CI check. <a href="https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html#fnref:10" title="return to body" class="reversefootnote">&#160;&#8617;&#xfe0e;</a></p>
</li>

</ol>
</section>

<p id="tags">
<strong>Tags:</strong> <a href="https://pekonen.cc/software/tags/Code-quality/">Code quality</a>, <a href="https://pekonen.cc/software/tags/Static-code-analysis/">Static code analysis</a>, <a href="https://pekonen.cc/software/tags/Automatic-commit-checks/">Automatic commit checks</a>
</p>
</section>]]>
</description>
<link>https://pekonen.cc/software/2022/01/25/Running-automated-checks-on-your-code-on-commits.html</link>
<pubDate>Tue, 25 Jan 2022 10:54:27 +0200</pubDate>
<guid isPermaLink="true">https://pekonen.cc/s/20220125-precommit/</guid>
</item>

<item>
<title>On writing reusable, testable, and safe Bash code</title>
<description>
<![CDATA[<section>

<p>First, a disclaimer. You might ask why Bash and not something &#8220;better&#8221; like Python? Well, as I
mentioned in (a footnote of) <a href="https://pekonen.cc/blog/2015/12/22/The-technical-implementation-of-this-site-Part-1.html#fn:2">my (fairly old yet still relevant) blog post about the technologies
I use for this website</a>,
it is a pain in the ass to ensure that a) you have the right programming environment installed
b) the right way so that you can use it and c) that all the dependencies you need are also installed
the right way. That is why there are still cases where writing the script in Bash makes sense.</p>

<p>This blog post is hugely inspired by the <a href="https://kfirlavi.herokuapp.com/blog/2012/11/14/defensive-bash-programming/">Defensive BASH programming</a>
blog post by Kfir Lavi. If you have not read that post yet, please do it now. Haven&#8217;t read it yet?
Shame on you. Go read it now! Now you have read it? Good, now we can continue!</p>

<p>The summarize the &#8220;best practices&#8221; from that blog post:</p>

<ol>
<li>Use functions to split the functionality into clear and coherent components.</li>
<li>Use local variables and minimize the use of (immutable) global variables.</li>
<li>Write the tasks in a function one per line so that code is easier to read.</li>
</ol>

<p>This is list a good start. However, I have noted that the following things make the Bash code even
more re-usable, testable, and safe.</p>

<h3 id="applytheunixphilosophytothefunctions">Apply the UNIX philosophy to the functions</h3>

<p>In other words, let a function do only one thing and do it well. This is a rephrasing of the point #1
on the list above. Of course, that is not to mean that you could not have functions that call a set of
other functions. That is actually advisable as it enables splitting complex tasks into smaller components
that can be tested. In case you have not done any Bash (unit) testing, I recommend that you take a look at
<a href="https://github.com/kward/shunit2">shunit2</a> that makes writing the tests pretty nice and easy.</p>

<h3 id="expecttheunexpectedandexitasearlyaspossible">Expect the unexpected and exit as early as possible</h3>

<p>When processing some data in a function, do not expect the input and output always be what you are
expecting it to be. Should something change in the input, the script can start running wild, thus
potentially causing irreversible damage to the underlying (file) system. Similarly, never expect the
task the function is calling to run smoothly. Therefore, always check the <em>result</em> of the task(s) the
function calls and exit as early as possible:</p>

<pre><code class="microlight">doAThing() {
	local output
	local result
	# Get the output of a task
	output=$(run_a_task)
	# Get the result of the call
	result=&quot;$?&quot;
	# If the call failed, exit!
	if [ &quot;${result}&quot; -gt 0 ]; then
		exit 1
	fi
	# Other tasks
}

main() {
	doAThing
	# Other function calls
}
</code></pre>

<p>If the script creates some temporary files that you would like to clean up also when the script fails,
then the functions should not exit but <em>return</em> with a non-zero value. Then, the calling function should
handle the return value of the function and do the necessary steps to clear the mess the script has created:</p>

<pre><code class="microlight">doAThing() {
	local output
	local result
	# Get the output of a task
	output=$(run_a_task)
	# Get the result of the call
	result=&quot;$?&quot;
	# If the call failed, return a failure
	if [ &quot;${result}&quot; -gt 0 ]; then
		return 1
	fi
	# Other tasks
	return 0
}

cleanUp() {
	# Do whatever is needed to clean up the mess
	# Exit after the clean-up is complete
	exit 1
}

main() {
	local result
	doAThing
	result=&quot;$?&quot;
	if [ &quot;${result}&quot; -gt 0 ]; then
		cleanUp
	fi
	# Other function calls
}
</code></pre>

<h3 id="movelibraryfunctionstoaseparatefileandloadthemusingsource.">Move &#8220;library&#8221; functions to a separate file and load them using <code>source</code>/<code>.</code></h3>

<p>Bash (as well as any other shell) has the very same import/include paradigm that many higher-level
programming languages. This makes it possible to have a set of (library) functions in a separate file
and then include them to the actual script you are writing. For example, when single-purpose functions
are defined in a separate file (for example, <code>library.bash</code>):</p>

<pre><code class="microlight"># Contents of library.bash

# Constants
export readonly A_THING=&quot;foo&quot;
export readonly ANOTHER_THING=&quot;bar&quot;

doAThing() {
	# Do your thing here, for example
	echo &quot;${A_THING}&quot;
}

doAnotherThing() {
	# Do another thing here, for example
	echo &quot;${ANOTHER_THING}&quot;
}

combinedFunction() {
	# Do both a thing and another thing
	doAThing
	doAnotherThing
}
</code></pre>

<p>Then they can be used in the main script file like this:</p>

<pre><code class="microlight"># Contents of script file
source &quot;path/to/library.bash&quot;
# &quot;source&quot; can be replaced with a period (.) to have the same effect

main() {
	# You can call…
	combinedFunction
	# …any function from that sourced file…
	doAThing
	# …like they would be defined in this file
	doAnotherThing
}

main
</code></pre>

<p>This functionality is handy when you have a set of functions that can be used as library functions
in other scripts. Furthermore, having the functions in a separate file makes writing (unit) tests
for them a lot easier. If all your functions of the script are in one file, including the <code>main</code>
function, as per the guidelines given by Kfir&#8217;s blog post, testing the individual functions of the
script requires rewriting them as test code.</p>

<p>But if (some of) the functions are in a file separate from the <code>main</code> function, you can test them
directly using <code>source</code>:</p>

<pre><code class="microlight"># Load the functions from the library.bash
source &quot;path/to/library.bash&quot;

testDoAThing() {
	# Call the function and store its output
	local output
	output=$(doAThing)
	# Check the output
	assertEquals &quot;${A_THING}&quot; &quot;${output}&quot;
	assertNotEquals &quot;${ANOTHER_THING}&quot; &quot;${output}&quot;
}

testDoAnotherThing() {
	# Call the function and store its output
	local output
	output=$(doAnotherThing)
	# Check the output
	assertNotEquals &quot;${A_THING}&quot; &quot;${output}&quot;
	assertEquals &quot;${ANOTHER_THING}&quot; &quot;${output}&quot;	
}

# Load the shunit2 unit testing functionality
source &quot;path/to/shunit2&quot;
</code></pre>

<p>Obviously, any change in the tested function will automagically reflected in the test and any
breaking changes will result in failing test cases, thus making them easier to spot.</p>

<h3 id="useshellcheck">Use shellcheck</h3>

<p><a href="https://github.com/koalaman/shellcheck">Shellcheck</a> is a static analysis tool (aka a linter) for
shell scripts. It can point out potential issues that could lead to issues. Furthermore, its
suggestions tend to make the code even more clearer and readable (points 1 and 3 above).</p>

<p>I have learned a lot from the errors shellcheck has pointed out. Sometimes the Bash &#8220;feature&#8221; you
are (ab)using does the thing you wish it to do but shellcheck complains about it. Rewriting the
solution to make shellcheck happy actually makes the code safer as the &#8220;feature&#8221; might have some
funky side effects if the input for that feature is not exactly what you expect it to be. That is,
trust shellcheck to point out the stupid ideas you had when writing that code.</p>

<h3 id="summary">Summary</h3>

<p>To write safe, re-usable, and testable Bash code, you should</p>

<ol>
<li>Use functions to split the code into small do-one-thing-well components,</li>
<li>Use local variables and minimize the use of (immutable) global variables,</li>
<li>Handle task return values and exit as early as possible,</li>
<li>Have library-like utility functions in a separate file or files and <code>source</code> them, and</li>
<li>Use shellcheck to point out potential issues.</li>
</ol>

<p>Happy Bashing!</p>

<p id="tags">
<strong>Tags:</strong> <a href="https://pekonen.cc/software/tags/Bash/">Bash</a>, <a href="https://pekonen.cc/software/tags/Test-driven-development-(TDD)/">Test driven development (TDD)</a>, <a href="https://pekonen.cc/software/tags/Code-safety/">Code safety</a>
</p>
</section>]]>
</description>
<link>https://pekonen.cc/software/2020/09/05/On-writing-reusable-testable-and-safe-Bash-code.html</link>
<pubDate>Sat, 05 Sep 2020 16:54:50 +0300</pubDate>
<guid isPermaLink="true">https://pekonen.cc/s/20200905-Bash/</guid>
</item>

</channel>
</rss>
