<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>The Ops Community ⚙️: Meir Gabay</title>
    <description>The latest articles on The Ops Community ⚙️ by Meir Gabay (@unfor19).</description>
    <link>https://community.ops.io/unfor19</link>
    <image>
      <url>https://community.ops.io/images/ap_Mi6UovtA6E3LMBnYo103f_vnPCSQGuSiodm5Py8U/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL3Vz/ZXIvcHJvZmlsZV9p/bWFnZS8xMDYvMDNl/ZGQzNjMtYmQyNi00/OTdhLWFhNTgtOWFj/NjlkMTc2MDQzLmpw/ZWc</url>
      <title>The Ops Community ⚙️: Meir Gabay</title>
      <link>https://community.ops.io/unfor19</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://community.ops.io/feed/unfor19"/>
    <language>en</language>
    <item>
      <title>How To Recover Secrets From GitHub Actions</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Fri, 01 Jul 2022 12:08:55 +0000</pubDate>
      <link>https://community.ops.io/unfor19/how-to-recover-secrets-from-github-actions-234i</link>
      <guid>https://community.ops.io/unfor19/how-to-recover-secrets-from-github-actions-234i</guid>
      <description>&lt;p&gt;In this blog post, I'll share how to recover a secret from a CI/CD service, such as GitHub Actions.&lt;/p&gt;

&lt;p&gt;If you're here, then you already know that secrets are hidden from CI/CD logs with &lt;code&gt;***&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  openssl:
    name: Recover With OpenSSL
    runs-on: ubuntu-20.04
    steps:
      - env:
          MY_CLIENT_SECRET: ${{ secrets.MY_CLIENT_SECRET }}
        run: |
          echo "MY_CLIENT_SECRET (***)     = ${MY_CLIENT_SECRET}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Output&lt;/span&gt;
MY_CLIENT_SECRET &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;***&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;***&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above isn't very helpful since this is the situation you're probably in right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick And Dirty (Dangerous)
&lt;/h2&gt;

&lt;p&gt;For private repositories, it's possible to use &lt;a href="https://en.wikipedia.org/wiki/Base64"&gt;base64&lt;/a&gt; to &lt;strong&gt;encode&lt;/strong&gt; a secret before printing it to the CI/CD service logs; this way, GitHub Actions won't hide the secret with &lt;code&gt;***&lt;/code&gt;. Then, copy the &lt;strong&gt;encoded&lt;/strong&gt; value and &lt;strong&gt;decode&lt;/strong&gt; it locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recovering secrets&lt;/span&gt;

&lt;span class="c1"&gt;# Assumption:&lt;/span&gt;
&lt;span class="c1"&gt;# You've created the following GitHub secrets in your repository:&lt;/span&gt;
&lt;span class="c1"&gt;# MY_CLIENT_ID - encode/decode with base64 - useful for private repositories&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base64&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recover With Base64&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MY_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_CLIENT_ID }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_ID (***)    = ${MY_CLIENT_ID}"&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_ID (base64) = $(echo ${MY_CLIENT_ID} | base64)"&lt;/span&gt;
          &lt;span class="s"&gt;echo "Copy the above value, and then execute locally:"&lt;/span&gt;
          &lt;span class="s"&gt;echo "echo PASTE_HERE | base64 -D"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/Q2EY11gk5q08I7LAjWRAgsrQwHNNoeJr7OOqdQeES4U/w:880/mb:500000/ar:1/aHR0cHM6Ly9kZXYt/dG8tdXBsb2Fkcy5z/My5hbWF6b25hd3Mu/Y29tL3VwbG9hZHMv/YXJ0aWNsZXMvZTJo/amxtbjc0ejZxZzlq/MDg1ZzYucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/Q2EY11gk5q08I7LAjWRAgsrQwHNNoeJr7OOqdQeES4U/w:880/mb:500000/ar:1/aHR0cHM6Ly9kZXYt/dG8tdXBsb2Fkcy5z/My5hbWF6b25hd3Mu/Y29tL3VwbG9hZHMv/YXJ0aWNsZXMvZTJo/amxtbjc0ejZxZzlq/MDg1ZzYucG5n" alt="recover-github-secret-base64" width="880" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above method is hazardous as anyone can decode the secret, so for public repositories, this is a no-go. And here's proof why it is &lt;strong&gt;super dangerous&lt;/strong&gt;, assuming the printed &lt;strong&gt;encoded&lt;/strong&gt; value is &lt;code&gt;c29tZS1jbGllbnQtaWQtdmFsdWUK&lt;/code&gt; ...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;c29tZS1jbGllbnQtaWQtdmFsdWUK | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;span class="c"&gt;# some-client-id-value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've just exposed &lt;code&gt;MY_CLIENT_ID&lt;/code&gt; to the whole world ... I'm terrified.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Recover A Secret From A CICD Service
&lt;/h2&gt;

&lt;p&gt;The best way to recover a secret from a CICD system without exposing it to the outside world is to &lt;strong&gt;encrypt&lt;/strong&gt; the secret before printing it to the CI/CD logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recovering secrets&lt;/span&gt;

&lt;span class="c1"&gt;# Assumption:&lt;/span&gt;
&lt;span class="c1"&gt;# You've created the following GitHub secrets in your repository:&lt;/span&gt;
&lt;span class="c1"&gt;# MY_CLIENT_SECRET - encrypt/decrypt with openssl - useful for public and private repositories&lt;/span&gt;
&lt;span class="c1"&gt;# MY_OPENSSL_PASSWORD - used to protect secrets&lt;/span&gt;
&lt;span class="c1"&gt;# MY_OPENSSL_ITER - Use a number of iterations on the password to derive the encryption key.&lt;/span&gt;
&lt;span class="c1"&gt;#                   High values increase the time required to brute-force the resulting file.&lt;/span&gt;
&lt;span class="c1"&gt;#                   This option enables the use of PBKDF2 algorithm to derive the key.&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;openssl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recover With OpenSSL&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MY_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_CLIENT_SECRET }}&lt;/span&gt;
          &lt;span class="na"&gt;MY_OPENSSL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_OPENSSL_PASSWORD }}&lt;/span&gt;
          &lt;span class="na"&gt;MY_OPENSSL_ITER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_OPENSSL_ITER }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_SECRET (***)     = ${MY_CLIENT_SECRET}"&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_SECRET (openssl) = $(echo "${MY_CLIENT_SECRET}" | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter ${MY_OPENSSL_ITER} -k "${MY_OPENSSL_PASSWORD}")"&lt;/span&gt;
          &lt;span class="s"&gt;echo "Copy the above value, and then execute locally:"&lt;/span&gt;
          &lt;span class="s"&gt;echo "echo PASTE_HERE | openssl base64 -d | openssl enc -d -pbkdf2 -iter \$MY_OPENSSL_ITER -aes-256-cbc -k \$MY_OPENSSL_PASSWORD"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/0PLCARVu70tHcBxjwjrbWtykY_G6FZfi2cKjIWNg1Cg/w:880/mb:500000/ar:1/aHR0cHM6Ly9kZXYt/dG8tdXBsb2Fkcy5z/My5hbWF6b25hd3Mu/Y29tL3VwbG9hZHMv/YXJ0aWNsZXMveHZk/amZtYjBlbnNmNXRq/aTVoa3IucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/0PLCARVu70tHcBxjwjrbWtykY_G6FZfi2cKjIWNg1Cg/w:880/mb:500000/ar:1/aHR0cHM6Ly9kZXYt/dG8tdXBsb2Fkcy5z/My5hbWF6b25hd3Mu/Y29tL3VwbG9hZHMv/YXJ0aWNsZXMveHZk/amZtYjBlbnNmNXRq/aTVoa3IucG5n" alt="recover-github-secret-openssl" width="880" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only way to &lt;strong&gt;decrypt&lt;/strong&gt; the above string &lt;code&gt;U2FsdGVkX1+6/+7bvNG/Ga7siAI994FkMUn5Njzn4zyNwvf8qM3MY0MMmd9sCFvz&lt;/code&gt; is to use the right number of &lt;code&gt;iter&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;, otherwise you'll have to use a &lt;a href="https://en.wikipedia.org/wiki/Brute-force_attack"&gt;brute-force attack&lt;/a&gt;, good luck with that :)&lt;/p&gt;

&lt;p&gt;Here's how I &lt;strong&gt;decrypted&lt;/strong&gt; the above value on my local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;U2FsdGVkX1+CeN0/ScQLZGU8f0ix86fh1oLJg/1M+o2lbCM+pBA8BIUCbkHMCjRZ | openssl &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | openssl enc &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-pbkdf2&lt;/span&gt; &lt;span class="nt"&gt;-iter&lt;/span&gt; &lt;span class="nv"&gt;$MY_OPENSSL_ITER&lt;/span&gt; &lt;span class="nt"&gt;-aes-256-cbc&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nv"&gt;$MY_OPENSSL_PASSWORD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;I suggest creating a separate workflow for recovering CI/CD (GitHub) secrets, like &lt;code&gt;.github/workflows/recover-github-secrets.yml&lt;/code&gt;, followed by running the workflow and then deleting its logs once you're done recovering the secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/xl4ZAsyMOZ-xx1_cOlFKkDRNQ68kVtWnDuZdhHaavQs/w:880/mb:500000/ar:1/aHR0cHM6Ly9kZXYt/dG8tdXBsb2Fkcy5z/My5hbWF6b25hd3Mu/Y29tL3VwbG9hZHMv/YXJ0aWNsZXMvdWgw/ZDVxYjhicWFnand4/NmFjd3MucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/xl4ZAsyMOZ-xx1_cOlFKkDRNQ68kVtWnDuZdhHaavQs/w:880/mb:500000/ar:1/aHR0cHM6Ly9kZXYt/dG8tdXBsb2Fkcy5z/My5hbWF6b25hd3Mu/Y29tL3VwbG9hZHMv/YXJ0aWNsZXMvdWgw/ZDVxYjhicWFnand4/NmFjd3MucG5n" alt="recover-github-secret-delete-logs" width="880" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/unfor19/gha-play/runs/7149023860?check_suite_focus=true"&gt;An example of how the above YAML files are executed in GitHub Actions&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/unfor19/gha-play/blob/master/.github/workflows/recover-github-secrets.yml"&gt;GitHub Actions full YAML example of using &lt;code&gt;base64&lt;/code&gt; and &lt;code&gt;openssl&lt;/code&gt; for recovering GitHub secrets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>githubactions</category>
      <category>secrets</category>
    </item>
    <item>
      <title>Writing Bash Scripts Like A Pro - Part 1 - Styling Guide</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Wed, 25 May 2022 19:41:48 +0000</pubDate>
      <link>https://community.ops.io/unfor19/writing-bash-scripts-like-a-pro-part-1-styling-guide-1c6</link>
      <guid>https://community.ops.io/unfor19/writing-bash-scripts-like-a-pro-part-1-styling-guide-1c6</guid>
      <description>&lt;p&gt;Writing &lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; scripts can be challenging if you don't know the quirks and perks. In my mother tongue, we use the Yiddish word for quirks and perks; it's called "Shtickim" (plural of &lt;a href="https://en.wikipedia.org/wiki/Shtick"&gt;"Shtick"&lt;/a&gt;). Are you ready to learn more about Bash's "Shtickim"?&lt;/p&gt;

&lt;p&gt;This blog post is part of a series that I'm working on to preserve the knowledge for future me that forgets stuff, to assist new colleagues, and indulge programmers like you who wish to love Bash as I do. So let's begin, shall we?&lt;/p&gt;

&lt;h2&gt;
  
  
  It's A Scripting Language
&lt;/h2&gt;

&lt;p&gt;It's important to remember that Bash is a &lt;a href="https://en.wikipedia.org/wiki/Scripting_language#:~:text=A%20scripting%20language%20or%20script,at%20runtime%20rather%20than%20compiled."&gt;scripting language&lt;/a&gt;, which means it doesn't offer the standard functionalities that a &lt;a href="https://en.wikipedia.org/wiki/Programming_language#:~:text=A%20programming%20language%20is%20a,consist%20of%20instructions%20for%20computers."&gt;programming language&lt;/a&gt; has to offer, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object-Oriented Programming is not supported natively&lt;/li&gt;
&lt;li&gt;There are no external libraries like Python's &lt;a href="https://docs.python-requests.org/en/latest/"&gt;requests&lt;/a&gt; or Node's &lt;a href="https://www.npmjs.com/package/axios"&gt;axios&lt;/a&gt;, though it is possible to use external applications such as &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.oreilly.com/library/view/learning-php-mysql/9781449337452/ch13s06.html"&gt;Variables Typing&lt;/a&gt; is not supported, and all values are evaluated as &lt;em&gt;strings&lt;/em&gt;. However, it is possible to use numbers by using specific commands, such as &lt;a href="https://tldp.org/LDP/abs/html/comparison-ops.html"&gt;test equality with -eq&lt;/a&gt; and &lt;a href="https://tldp.org/LDP/abs/html/ops.html"&gt;increment a variable with ((VAR_NAME+1))&lt;/a&gt;. Nevertheless, there's a "weak" way of declaring variables type with the &lt;a href="https://linuxhint.com/bash_declare_command"&gt;declare command&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://linuxhint.com/associative_array_bash/"&gt;Bash's Associative array&lt;/a&gt; like Python's &lt;a href="https://www.w3schools.com/python/python_dictionaries.asp"&gt;dict&lt;/a&gt; or JavaScript's &lt;a href="https://www.w3schools.com/js/js_objects.asp"&gt;Object&lt;/a&gt; is supported from version &lt;a href="https://tldp.org/LDP/abs/html/bashver4.html"&gt;Bash v4.4&lt;/a&gt;, and it's important to remember that &lt;a href="https://tldp.org/LDP/abs/html/bashver3.html"&gt;macOS is shipped with Bash v3.2&lt;/a&gt; (we'll get to that in future blog posts of this series)&lt;/li&gt;
&lt;li&gt;There is no "source of truth" for naming convention. For example, how would you name a global variable? &lt;code&gt;Pascal_Case&lt;/code&gt;? &lt;code&gt;snake_case&lt;/code&gt;? &lt;code&gt;SCREAMING_SNAKE_CASE&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you already guessed, "Bash programmers" (if there is such a thing) face many challenges. The above list is merely the tip of the iceberg.&lt;/p&gt;

&lt;p&gt;Here are great blog posts that share the same feelings as I do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.tothenew.com/blog/foolproof-your-bash-script-some-best-practices/"&gt;Foolproof Your Bash Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kvz.io/bash-best-practices.html"&gt;Best Practices for Writing Bash Scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bertvv.github.io/cheat-sheets/Bash.html"&gt;Bash best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we've covered the fact that I'm in love with Bash, I want to share that feeling with you; here goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables Naming Convention
&lt;/h2&gt;

&lt;p&gt;Here's how I name variables in my Bash scripts&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Convention&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;MY_VARIABLE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;_MY_VARIABLE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Function&lt;/td&gt;
&lt;td&gt;my_variable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In my older Bash scripts, the names of the variables were hard to interpret. Changing to this naming convention helped me a lot to understand the scope of variables and their purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good Vibes Application
&lt;/h2&gt;

&lt;p&gt;And of course, we gotta' see some practical example, so here's how I implement the above naming convention in my &lt;code&gt;good_vibes.sh&lt;/code&gt; application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;good_vibes.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ^ This is called a Shebang&lt;/span&gt;
&lt;span class="c"&gt;# I'll cover it in future blog posts&lt;/span&gt;


&lt;span class="c"&gt;# Global variables are initialized by Env Vars.&lt;/span&gt;
&lt;span class="c"&gt;# I'm setting a default value with "${VAR_NAME:-"DEFAULT_VALUE"}"&lt;/span&gt;
&lt;span class="nv"&gt;_USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;_USER_AGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_AGE&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


complement_name&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Wow, &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, you have a beautiful name!"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


complement_age&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$age&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"30"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Seriously &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;? I thought you were &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;age-7&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Such a weird age, are you sure it's a number?"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


main&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="c"&gt;# The only function that is not "pure"&lt;/span&gt;
  &lt;span class="c"&gt;# This function is tightly coupled to the script&lt;/span&gt;
  complement_name &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  complement_age &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_AGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;# Invokes the main function&lt;/span&gt;
main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;good_vibes.sh - Execution and output&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Julia"&lt;/span&gt; &lt;span class="nv"&gt;USER_AGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"36"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bash good_vibes.sh

&lt;span class="c"&gt;# Output&lt;/span&gt;
Wow, Julia, you have a beautiful name!
Seriously Julia? I thought you were 29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the &lt;code&gt;good_vibes.sh&lt;/code&gt; application to a "set of rules" that can be implemented in your scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code block spacing
&lt;/h3&gt;

&lt;p&gt;Two (2) blank rows between each block of code make the script more readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Indentation
&lt;/h3&gt;

&lt;p&gt;I'm using two (2) spaces, though it's totally fine to use four (4) spaces for indentation. Just make sure you're not mixing between the two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Curly braces
&lt;/h3&gt;

&lt;p&gt;If it's a &lt;code&gt;${VARIABLE} concatenated with string&lt;/code&gt;, use curly braces as it makes it easier to read.&lt;/p&gt;

&lt;p&gt;In case it's a &lt;code&gt;"$LONELY_VARIABLE"&lt;/code&gt; there's no need for that, as it will help you realize faster if it's "lonely" or not.&lt;/p&gt;

&lt;p&gt;The primary purpose for curly braces is for performing a &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html"&gt;Shell Parameter Expansion&lt;/a&gt;, as demonstrated in the Global variables initialization part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Squared brackets
&lt;/h3&gt;

&lt;p&gt;Using &lt;strong&gt;double&lt;/strong&gt; &lt;code&gt;[[ ]]&lt;/code&gt; squared brackets makes it easier to read conditional code blocks. However, do note that using double squared brackets is not supported in &lt;a href="https://stackoverflow.com/a/5725402/5285732"&gt;Shell sh&lt;/a&gt;; instead, you should use single brackets &lt;code&gt;[ ]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To demonstrate the readability, here's a "complex" conditional code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Julia"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Willy"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_AGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"30"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Easy to read right?"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Mind that `||` is replaced with `-o`, see https://acloudguru.com/blog/engineering/conditions-in-bash-scripting-if-statements&lt;/span&gt;
&lt;span class="c"&gt;# Thank you William Pursell&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Julia"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Willy"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_AGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"30"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No idea why but I feel lost with single brackets."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you didn't notice, you've just learned that &lt;code&gt;||&lt;/code&gt; stands for &lt;code&gt;OR&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; stands for &lt;code&gt;AND&lt;/code&gt;. And the short &lt;a href="https://tldp.org/LDP/abs/html/comparison-ops.html"&gt;-gt&lt;/a&gt; expression means &lt;code&gt;greater than&lt;/code&gt; when using numbers. Finally, the &lt;code&gt;\&lt;/code&gt; character allows &lt;a href="https://unix.stackexchange.com/questions/281309/shell-syntax-how-to-correctly-use-to-break-lines"&gt;breaking rows&lt;/a&gt; in favor of making the code more readable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Shtick&lt;/strong&gt;: Using &lt;code&gt;\&lt;/code&gt; with an extra space &lt;code&gt;\ &amp;lt;- extra space&lt;/code&gt; can lead to weird errors. Make sure there are no trailing spaces after &lt;code&gt;\&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I assume that using &lt;code&gt;[[ ]]&lt;/code&gt; feels more intuitive since most conditional commands are doubled &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; &lt;code&gt;||&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variable initialization
&lt;/h3&gt;

&lt;p&gt;Global variables are initialized with Environment Variables and are set with default values in case of empty Environment variables.&lt;/p&gt;

&lt;p&gt;As mentioned in the &lt;code&gt;good_vibes.sh&lt;/code&gt; comments, I'm setting a default value with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VAR_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"DEFAULT_VALUE"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, the text &lt;code&gt;DEFAULT_VALUE&lt;/code&gt; is hardcoded, and it's possible to replace it with a variable. For example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functions and local function variables
&lt;/h3&gt;

&lt;p&gt;Functions names and &lt;code&gt;local&lt;/code&gt; function variables names are &lt;code&gt;snake_cased&lt;/code&gt;. You might want to change functions names to &lt;code&gt;lowerCamelCase&lt;/code&gt;, and of course, it's your call.&lt;/p&gt;

&lt;p&gt;Coupling a function to the script is a common mistake, though I do sin from time to time, and you'll see Global/Environment variables in my functions, but that happens when I know that "this piece of code won't change a lot". &lt;/p&gt;

&lt;p&gt;Oh, and make sure you don't use &lt;code&gt;$1&lt;/code&gt; or any other argument directly; always use &lt;code&gt;local var_name="$1"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Bad - coupled&lt;/span&gt;
coupled_username&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"_USER_NAME = &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Good - decoupled&lt;/span&gt;
decoupled_username&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"name = &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Usage&lt;/span&gt;
coupled_username  
decoupled_username &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functional Programming
&lt;/h3&gt;

&lt;p&gt;This topic relates to &lt;strong&gt;Functions and local function variables&lt;/strong&gt;, where functions are as "pure" as possible. As you can see in &lt;code&gt;good_vibes.sh&lt;/code&gt;, almost everything is wrapped in a function, except for &lt;strong&gt;Initializing Global variables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't see the point of writing the &lt;code&gt;init_vars&lt;/code&gt; function, whose purpose is to deal with Global variables. However, I do find myself adding a &lt;code&gt;validate_vars&lt;/code&gt; function from time to time, which goes over the Global variables and validates their values. I'm sure there's room for debate here, so feel free to comment with your thoughts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;The "Good Vibes Application" mostly covered how to write a readable Bash script following the &lt;a href="https://en.wikipedia.org/wiki/Functional_programming"&gt;Functional Programming&lt;/a&gt; paradigm.&lt;/p&gt;

&lt;p&gt;If you feel that there's a need to change how you name variables and functions, go for it! As long as it's easy to understand your code, you're on the right track.&lt;/p&gt;

&lt;p&gt;The next blog posts in this series will cover the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Retrieving JSON data from an HTTP endpoint&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tldp.org/LDP/abs/html/x9644.html"&gt;Background jobs&lt;/a&gt; and watching file for changes with &lt;a href="https://github.com/emcrisostomo/fswatch"&gt;fswatch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Git Repository structure - adding Bash scripts to existing repositories or creating a new repository with a Bash CLI application&lt;/li&gt;
&lt;li&gt;Publishing a Bash CLI as a &lt;a href="https://www.docker.com/why-docker"&gt;Docker&lt;/a&gt; image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more, and more ... I'm just going to spit it all out to blog posts. Feel free to comment with questions or suggestions for my next blog posts.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>programming</category>
      <category>tutorials</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
