Writing shell scripts

Writing shell scripts

When you are using your terminal a lot during the day make sure to improve your workflow and reduce the number of keystrokes you have to type to perform your daily tasks.

This post will only outline the options and not go to deep into details. There are great posts out there that describe all the details way better than I could.

The focus of this post is a basic overview what you can do and why you want to invest time in your tooling on a shell level.

What can shell scripts do for you and your team?

It is nice to optimize your own workflow - enabling a whole team to be faster and deliver software with better quality is just amazing.

Decrease onboarding time

When you enter a new team with there development processes and tools might be intimidating at times. Having great tools and scripts makes this easy and even fun for new engineers joining a team. They can focus on finishing the first tasks early and actually contribute from day one without having to learn nifty little details.

Streamline tooling

In case updates to the flow (having extra steps, parameters) can be handled centrally.

Start small

To get used to writing shell scripts that you actually use and make your daily work easier and faster start small.

What does that mean?

Let's assume you have some command that you use for deploying your application. In my case that is a serverless backend on AWS. We use the serverless framework in a multi-repo managed with lerna

npx lerna exec --scope @some/package \
  --stream sls -- deploy -v \
  --param1 value1 \
  --param2 value2

You can always look it up in your history or wrap it in a little script called deploy. Do not forget to give the script the permission to be executed (chmod +x deploy)

#!/bin/bash

npx lerna exec --scope @some/package
  --stream sls -- deploy -v \
  --param1 value1 \
  --param2 value2

Now you only have to remember the command deploy. Later you might notice that you can only deploy a specific package because it is hard coded in the script.

Let's change that!

Simple parameters

You can access simple parameters by using $[number].

#!/bin/bash

PACKAGE_NAME=$1

npx lerna exec --scope "@some/${PACKAGE_NAME}" \
  --stream sls -- deploy -v \
  --param1 value1 \
  --param2 value2

Now you can deploy whatever package you want by running

deploy package
deploy otherpackage

An optional parameter to deploy a single function

A lot of times I just change one function and just want to test that in Lambda. So let's extend our little script to include the option to deploy a single function.

#!/bin/bash

PACKAGE_NAME=$1
FUNCTION_NAME=$2

# Do we have a function name?
if [ "${FUNCTION_NAME}" != "" ]; then
  # Seems like it, so let's create the correct parameter
  FUNCTION_PARAM="--function ${FUNCTION_NAME}"
fi

npx lerna exec --scope "@some/${PACKAGE_NAME}" \
  --stream sls -- deploy -v \
  --param1 value1 \
  --param2 value2 \
  # Append the parameter, if it is empty - nothing gets added
  "${FUNCTION_PARAM}"

Next steps

The next steps are a bit more advanced and might be frustrating at times to make them work. Keep at it, the result is usually amazing for you and your team.

Autocompletion

How to enable autocompletion depends on the shell you are using. Refer to your manual. I use the zsh on a Mac with oh-my-zsh.

An example to autocomplete our deployment script with a single function to deploy

#compdef

_deploy_package() {
  local -a functions
  # Extract functions from serverless.yml
  functions=$(grep '^[^[:blank:]]\(.*\):$' ./path/to/serverless.yml | tr -d ':')

  _alternative "argument:group:($functions)"
}

...

_deploy() {
  local line

  # Autocompletion for our commands
  _commands() {
    local -a commands
    commands=(
      'package:Deploy @some/package'
      'otherpackage:Deploy @some/otherpackage'
    )
    _describe 'command' commands
  }

  _arguments -C \
    "1: :_commands" \
    "*::arg:->args"

  # Autocompletion for the commands - in this case to list all functions
  # from our serverles.yml
  case $line[1] in
    package)       _deploy_package ;;
    otherpackage)  _deploy_otherpackage ;;
  esac
}

Resources that might come handy