Production-ready shell startup scripts: The Set Builtin

Know another way to avoid unexpected incidents in production. It will allow you to write production-ready startup shell scripts. Learn about The Set Builtin.

#!/usr/bin/env bashpython manage.py makemigrations
python manage.py migrate
python manage.py seed --create-super-user
python manage.py runserver 0.0.0.0:8000

Exit immediately if any command returns a non-zero status

Let’s suppose the following script:

#!/usr/bin/env bashpython the_set_builtin/sample_1.py
python the_set_builtin/sample_2_force_error.py
python the_set_builtin/sample_3.py
▶ docker-compose up why-use-argument-e-with-bug
Creating the-set-builtin_why-use-argument-e-with-bug_1 ... done
Attaching to the-set-builtin_why-use-argument-e-with-bug_1
why-use-argument-e-with-bug_1 | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-e-with-bug_1 | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-e-with-bug_1 | Let me force an error 👀
why-use-argument-e-with-bug_1 | I am the /app/the_set_builtin/sample_3.py!
the-set-builtin_why-use-argument-e-with-bug_1 exited with code 0
▶ docker-compose up why-use-argument-e-with-fix 
Creating the-set-builtin_why-use-argument-e-with-fix_1 ... done
Attaching to the-set-builtin_why-use-argument-e-with-fix_1
why-use-argument-e-with-fix_1 | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-e-with-fix_1 | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-e-with-fix_1 | Let me force an error 👀
the-set-builtin_why-use-argument-e-with-fix_1 exited with code 1
#!/usr/bin/env bashpython manage.py migrate
python manage.py collectstatic --no-input
gunicorn -cpython:gunicorn_config -b 0.0.0.0:${PORT} aladdin.wsgi

Treat unset variables as an error when substituting

Now we have the following script:

#!/usr/bin/env bashpython the_set_builtin/sample_1.py
python the_set_builtin/sample_2_force_error.py
python the_set_builtin/sample_3.py
python the_set_builtin/sample_4_env_variable.py "$ALADDIN_WISH"
python the_set_builtin/sample_5.py
▶ docker-compose up why-use-argument-u-with-bug
Starting the-set-builtin_why-use-argument-u-with-bug_1 ... done
Attaching to the-set-builtin_why-use-argument-u-with-bug_1
why-use-argument-u-with-bug_1 | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-u-with-bug_1 | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-u-with-bug_1 | Let me force an error 👀
why-use-argument-u-with-bug_1 | I am the /app/the_set_builtin/sample_3.py!
why-use-argument-u-with-bug_1 | I am the /app/the_set_builtin/sample_4_env_variable.py!
why-use-argument-u-with-bug_1 | Look! I received the following arguments: ['the_set_builtin/sample_4_env_variable.py', '']
why-use-argument-u-with-bug_1 | Length: 2
why-use-argument-u-with-bug_1 | I am the /app/the_set_builtin/sample_5.py!
the-set-builtin_why-use-argument-u-with-bug_1 exited with code 0
▶ docker-compose up why-use-argument-u-with-fix
Starting the-set-builtin_why-use-argument-u-with-fix_1 ... done
Attaching to the-set-builtin_why-use-argument-u-with-fix_1
why-use-argument-u-with-fix_1 | I am the /app/the_set_builtin/sample_1.py!
why-use-argument-u-with-fix_1 | I am the /app/the_set_builtin/sample_2_force_error.py!
why-use-argument-u-with-fix_1 | Let me force an error 👀
why-use-argument-u-with-fix_1 | I am the /app/the_set_builtin/sample_3.py!
why-use-argument-u-with-fix_1 | ./scripts/with-argument-u.sh: line 10: ALADDIN_WISH: unbound variable
the-set-builtin_why-use-argument-u-with-fix_1 exited with code 1

A pipeline should produce a failure return code if any command fails

To illustrate, imagine you have the script below. Notice that we’re using set -e argument to make it safer:

#!/usr/bin/env bash# https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin
# • -e: Exit immediately if a command exits with a non-zero status.
set -e
this-is-a-fake-command-my-friend | echo "You...are late."
echo "A thousand apologies, O patient one."
▶ docker-compose up why-use-option-pipefail-with-bug
Starting the-set-builtin_why-use-option-pipefail-with-bug_1 ... done
Attaching to the-set-builtin_why-use-option-pipefail-with-bug_1
why-use-option-pipefail-with-bug_1 | ./scripts/without-option-pipefail.sh: line 7: this-is-a-fake-command-my-friend: command not found
why-use-option-pipefail-with-bug_1 | You...are late.
why-use-option-pipefail-with-bug_1 | A thousand apologies, O patient one.
the-set-builtin_why-use-option-pipefail-with-bug_1 exited with code 0
▶ docker-compose up why-use-option-pipefail-with-fix
Starting the-set-builtin_why-use-option-pipefail-with-fix_1 ... done
Attaching to the-set-builtin_why-use-option-pipefail-with-fix_1
why-use-option-pipefail-with-fix_1 | ./scripts/with-option-pipefail.sh: line 9: this-is-a-fake-command-my-friend: command not found
why-use-option-pipefail-with-fix_1 | You...are late.
the-set-builtin_why-use-option-pipefail-with-fix_1 exited with code 127

Shield you from working outside working hours

Wrapping everything up, this is the entry your bash script should have at the top: set -eu -o pipefail. Always insert it in all of your scripts, always. This simple protection will help you a lot during TSHOOT 🙏. One example is when you're running your application without a migration that should have been applied.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store