{"id":33913,"date":"2016-05-09T13:59:28","date_gmt":"2016-05-09T08:29:28","guid":{"rendered":"http:\/\/www.tothenew.com\/blog\/?p=33913"},"modified":"2024-01-02T17:47:22","modified_gmt":"2024-01-02T12:17:22","slug":"foolproof-your-bash-script-some-best-practices","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/foolproof-your-bash-script-some-best-practices\/","title":{"rendered":"&#8220;Foolproof Your Bash Script&#8221; &#8211; Some Best Practices"},"content":{"rendered":"<p>I am a <a title=\"devops consultant\" href=\"http:\/\/www.tothenew.com\/devops-automation-consulting\">DevOps practitioner<\/a> and a lazy one too, so whenever we come across any task that needs to be repeated, we create a bash script. I have been\u00a0doing this for a\u00a0long time and after doing a lot\u00a0of mistakes, I figured that if we follow some basic rules we can make our script more portable and less prone to failure when semantics change. These rules and practices have been discussed further in the blog.<\/p>\n<p><img decoding=\"async\" src=\"\/blog\/wp-ttn-blog\/uploads\/2024\/01\/unzip-centos.png\" alt=\"\" \/><\/p>\n<h2>1. Always start with a shebang<\/h2>\n<p>The first rule of shell scripting is that you always start your scripts with a shebang also called sha-bang, hashbang, pound-bang, or hash-pling. While the name might sound funny the shebang line is very important; it tells the system which binary to use as the interpreter for the script. Without the shebang line, the system doesn&#8217;t know what language to use to process the script.<\/p>\n<p>[js]#!\/bin\/bash[\/js]<\/p>\n<p>A typical bash shebang line would look like the following:<\/p>\n<h2>2. Use Built-in Shell Options<\/h2>\n<p>Use set -o errexit (a.k.a. set -e) to make your script exit when a command fails. \u00a0add || true to commands that you allow to fail.<br \/>\nUse set -o nounset (a.k.a. set -u) to exit when your script tries to use undeclared variables.<br \/>\nUse set -o xtrace (a.k.a set -x) to trace what gets executed. Useful for debugging (optional).<br \/>\nUse set -o pipefail in scripts to catch mysqldump fails in e.g. mysqldump |gzip. The exit status of the last command that threw a non-zero exit code is returned.<\/p>\n<p>[js]#!\/bin\/bash<br \/>\nset -o nounset<br \/>\nset -o errexit[\/js]<\/p>\n<h2>3. Naming conventions for Variables<\/h2>\n<ol>\n<li><strong>Strings should have the form name=value.<\/strong><br \/>\nIdeally variable names should only consist uppercase letters, digits, and the &#8216;_&#8217; (underscore).<\/li>\n<li><strong>Variable Names shall not contain the character &#8216;=&#8217; or &#8216;-&#8216;<\/strong><br \/>\n<span style=\"font-family: Consolas, Monaco, 'Lucida Console', monospace; font-size: 0.857143rem; line-height: 2;\"><code>$ running-process-id=`ps -eopid`\\<\/code><\/span><\/li>\n<li><strong>No spaces before or after that equal\u00a0sign<\/strong><br \/>\n<code>$ language = PHP<\/code><br \/>\n-bash: language: command not found<\/li>\n<li><strong>Double quotes around every parameter expansion<\/strong><br \/>\n<span style=\"font-family: Consolas, Monaco, 'Lucida Console', monospace; font-size: 0.857143rem; line-height: 2;\"><code>$ song=\"My song.mp3\"<\/code><br \/>\n<\/span><span style=\"font-family: Consolas, Monaco, 'Lucida Console', monospace; font-size: 0.857143rem; line-height: 2;\"><code>$ rm $song<\/code><br \/>\n<\/span><\/p>\n<p>[js]rm: My: No such file or directory<br \/>\nrm: song.mp3: No such file or directory [\/js]<\/p>\n<p><code>$ rm \"$song\"<\/code><\/li>\n<li><strong>Don&#8217;t start Variable Name with special characters or Numbers<\/strong><br \/>\n<span style=\"font-family: Consolas, Monaco, 'Lucida Console', monospace; font-size: 0.857143rem; line-height: 2;\"><code>$ -song=\"My song.mp3\"<\/code><br \/>\n<\/span><\/p>\n<p>[js]-song=my song.mp3: command not found [\/js]<\/p>\n<p><span style=\"font-family: Consolas, Monaco, 'Lucida Console', monospace; font-size: 0.857143rem; line-height: 2;\"><code>$ 123song=\"My song.mp3\"<\/code><br \/>\n<\/span><\/p>\n<p>[js]123song=my song.mp3: command not found[\/js]<\/p>\n<\/li>\n<\/ol>\n<h2>4. Variable Annotations<\/h2>\n<p>Bash allows for a limited form of variable annotations. The most important ones are:<br \/>\n<strong>local<\/strong> (for local variables inside a function)<br \/>\n<strong>readonly<\/strong> (for read-only variables)<\/p>\n<p>Strive to annotate almost all variables in a bash script with either local or readonly.<\/p>\n<h2>5. Use $() over backticks<\/h2>\n<p>Avoid using backticks &#8220;&#8220;&#8221;, they are hard to read and in some fonts easily confused with single quotes. A lot of quoting needed in nesting.<\/p>\n<p><strong>Example<\/strong>:<\/p>\n<p><code>$ echo \"one-$(echo two-$(echo three-$(echo four)))\"<\/code><\/p>\n<p>[js]one-two-three-four[\/js]<\/p>\n<p><code>$ echo \"one-`echo two-\\`echo three-\\`\\`echo four\\`\\`\\``\"<\/code><\/p>\n<p>[js]one-two-three-four [\/js]<\/p>\n<h2>6. Prefer using Double Brackets<\/h2>\n<p>Let me illustrate how [[ can be used and how it can help you to avoid some of the common mistakes made by using [] or test:<\/p>\n<p><code>$ var=''<br \/>\n$ [ $var = ''\" ] &amp;&amp; echo True<\/code><\/p>\n<p>[js]-bash: [: =: unary operator expected[\/js]<\/p>\n<p><code>$ [ \"$var\" = '' ] &amp;&amp; echo True<\/code><\/p>\n<p>[js]True[\/js]<\/p>\n<p><code>$ [[ $var = '' ]] &amp;&amp; echo True<\/code><\/p>\n<p>[js]True[\/js]<\/p>\n<p>Using quotes, [ &#8220;$var&#8221; = &#8221; ] expands into [ &#8220;&#8221; = &#8221; ] and test has no problem.<\/p>\n<p>Now, [[ can see the whole command before it&#8217;s being expanded. It sees $var, and not the expansion of $var. As a result, there is no need for the quotes at all! [[ is safer.<\/p>\n<p><code>$ var=<br \/>\n$ [ \"$var\" &lt; a ] &amp;&amp; echo True<\/code><\/p>\n<p>[js]-bash: a: No such file or directory[\/js]<\/p>\n<p><code>$ [ \"$var\" \\&lt; a ] &amp;&amp; echo True<br \/>\n<\/code><\/p>\n<p>[js]True[\/js]<\/p>\n<p><code>$ [[ $var &lt; a ]] &amp;&amp; echo True<br \/>\n<\/code><\/p>\n<p>[js]True[\/js]<\/p>\n<p>In this example, we attempted a string comparison between an empty variable and &#8216;a&#8217;. We&#8217;re surprised to see the first attempt does not yield True even though we think it should. Instead, we get some weird error that implies Bash is trying to open a file called &#8216;a&#8217;.<\/p>\n<p>We&#8217;ve been bitten by File Redirection. Since test is just an application, the &lt; character in our command is interpreted (as it should) as a File Redirection operator instead of the string comparison operator of test. Bash is instructed to open a file &#8216;a&#8217; and connect it to stdin for reading. To prevent this, we need to escape &lt; so that test receives the operator rather than Bash. This makes our second attempt work.<\/p>\n<p>Using [[ we can avoid the mess altogether. [[ sees the &lt; operator before Bash gets to use it for Redirection &#8212; problem fixed. Once again, [[ is safer. Even more dangerous is using the &gt; operator instead of our previous example with the &lt; operator. Since &gt; triggers output Redirection it will create a file called &#8216;a&#8217;. As a result, there will be no error message warning us that we&#8217;ve committed a sin! Instead, our script will just break. Even worse, we might overwrite some important file! It&#8217;s up to us to guess where the problem is:<\/p>\n<p><code>$ var=a<br \/>\n$ [ \"$var\" &gt; b ] &amp;&amp; echo True || echo False<\/code><\/p>\n<p>[js]True[\/js]<\/p>\n<p><code>$ [[ \"$var\" &gt; b ]] &amp;&amp; echo True || echo False<br \/>\n<\/code><\/p>\n<p>[js]False[\/js]<\/p>\n<p>Two different results, not good. The lesson is to trust [[ more than [. [ &#8220;$var&#8221; &gt; b ] is expanded into [ &#8220;a&#8221; ] and the output of that is being redirected into a new file called &#8216;b&#8217;. Since [ &#8220;a&#8221; ] is the same as [ -n &#8220;a&#8221; ] and that basically tests whether the &#8220;a&#8221; string is non-empty, the result is a success and the echo True is executed.<\/p>\n<p>Using [[ we get our expected scenario where &#8220;a&#8221; is tested against &#8220;b&#8221; and since we all know &#8220;a&#8221; sorts before &#8220;b&#8221; this triggers the echo False statement. And this is how you can break your script without realizing it. You will however have a suspiciously empty file called &#8216;b&#8217; in your current directory.<\/p>\n<p>Yes it adds a few characters, but [[ is far safer than [. Everybody inevitably makes programming errors. Even if you try to use [ safely and carefully avoid these mistakes, I can assure you that you will make them. And if other people are reading your code, you can be sure that they&#8217;ll absolutely mess things up.<\/p>\n<h2>7. Regular Expressions\/Globbing<\/h2>\n<p><strong>[[<\/strong> provides the following features over <strong>[<\/strong> :-<\/p>\n<p>t=&#8221;abc123&#8243;<br \/>\n[[ &#8220;$t&#8221; == abc* ]] # true (globbing)<br \/>\n[[ &#8220;$t&#8221; == &#8220;abc*&#8221; ]] # false (literal matching)<br \/>\n[[ &#8220;$t&#8221; =~ [abc]+[123]+ ]] # true (regular expression)<br \/>\n[[ &#8220;$t&#8221; =~ &#8220;abc*&#8221; ]] # false (literal matching)<br \/>\nNote, that starting with bash version 3.2 the regular or globbing expression<br \/>\nmust not be quoted. If your expression contains whitespace you can store it in a variable:<br \/>\nr=&#8221;a b+&#8221;<br \/>\n[[ &#8220;a bbb&#8221; =~ $r ]] # true<\/p>\n<p>Globbing based string matching is also available via the case statement:<\/p>\n<p>[js]case $t in<br \/>\nabc*) ;;<br \/>\nesac[\/js]<\/p>\n<h2>8. Avoiding Temporary Files<\/h2>\n<p>Some commands expect filenames as parameters so straightforward pipelining does not work.<br \/>\nThis is where &lt;() operator comes in handy as it takes a command and transforms it into something<br \/>\nwhich can be used as a filename:<\/p>\n<p><code>diff &lt;(wget -O - url1) &lt;(wget -O - url2)<\/code><\/p>\n<p>Also useful are &#8220;here documents&#8221; which allow arbitrary multi-line string to be passed<br \/>\nin on stdin. The two occurrences of &#8216;EOF&#8217; brackets the document.<\/p>\n<p># DELIMITER is an arbitrary string<\/p>\n<p>[js]command &lt;&lt; EOF<br \/>\nSome importent text<br \/>\n$(cmd)<br \/>\nEOF[\/js]<\/p>\n<p>&#8216;EOF&#8217; can be any text.<\/p>\n<h2>9. Debugging<\/h2>\n<p><strong>To perform a syntax check\/dry run of your bash script run:<\/strong><\/p>\n<p><code>bash -n myscript.sh<\/code><\/p>\n<p><strong>To produce a trace of every command executed run:<\/strong><\/p>\n<p><code>bash -v myscripts.sh<\/code><\/p>\n<p><strong>To produce a trace of the expanded command use:<\/strong><\/p>\n<p><code>bash -x myscript.sh<\/code><\/p>\n<p>-v and -x can also be made permanent by adding<br \/>\nset -o verbose and set -o xtrace to the script prolog.<br \/>\nThis might be useful if the script is run on a remote machine, e.g.<br \/>\na build-bot and you are logging the output for remote inspection.<\/p>\n<h2>10. Trap forced exit of script<\/h2>\n<p>Don&#8217;t let your script exit unexpectedly, trap when someone update press ctrl+c and exit from your script gracefully.<\/p>\n<p>[js]# trap ctrl-c and call ctrl_c()<br \/>\ntrap ctrl_c INT<\/p>\n<p>function ctrl_c() {<br \/>\n  echo &quot;** Trapped CTRL-C&quot;<br \/>\n}<\/p>\n<p>for i in `seq 1 5`; do<br \/>\n  sleep 1<br \/>\n  echo -n &quot;.&quot;<br \/>\ndone[\/js]<\/p>\n<p>This is how simple it is to Foolproof\u00a0\u00a0your bash scripts. In my next blog, I will talk about more interesting features available in Linux OS.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I am a DevOps practitioner and a lazy one too, so whenever we come across any task that needs to be repeated, we create a bash script. I have been\u00a0doing this for a\u00a0long time and after doing a lot\u00a0of mistakes, I figured that if we follow some basic rules we can make our script more [&hellip;]<\/p>\n","protected":false},"author":747,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":256},"categories":[2348],"tags":[475,519,3315,3317,3318,3319,3313,260,474,3312,3316,448,3314],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/33913"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/747"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=33913"}],"version-history":[{"count":1,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/33913\/revisions"}],"predecessor-version":[{"id":59861,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/33913\/revisions\/59861"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=33913"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=33913"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=33913"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}