This is part five of a series on how to approach bash programming in a way that’s safer and more structured than your basic script.

See part 1 if you want to catch the series from the start.

Last time, we created a failing test. We’ll work on making it work now, but I’m going to jump straight to the structure that a project with tests should take.

The project will be a hello, world! script, so let’s start with:

mkdir hello-world
cd hello-world
mkdir bin
mkdir shpec
echo '#!/usr/bin/env bash' >bin/hello-world
echo 'hello_world () { :;}' >>bin/hello-world
chmod 775 bin/hello-world

The hello_world function exists, but does nothing.

Move hello-world_shpec.bash to the shpec directory.

Open hello-world_shpec.bash in an editor and add the following to the beginning:

source "$(dirname "$(readlink -f "$BASH_SOURCE")")"/../bin/hello-world

That finds the true location of hello-world_shpec.bash via readlink, then trims off the filename from the path. It then adds the relative path to our hello-world source file.

Note that on Mac, you’ll need to install GNU readlink via homebrew, then use greadlink instead of the readlink above. You may simply want to alias readlink to greadlink in your scripts. See the post on aliases for how to create aliases in scripts.

Shpec now outputs the following:

> shpec shpec/hello-world_shpec.bash
  echos 'hello, world!'
  (Expected [hello, world!] to equal [])
1 examples, 1 failures
0m0.004s 0m0.000s
0m0.000s 0m0.000s

Still failing. Excellent!

At this point, all we need to do is update the hello_world function:

#!/usr/bin/env bash

hello_world () {
  echo "hello, world!"

Shpec’s output:

> shpec shpec/hello-world_shpec.bash
  echos 'hello, world!'
1 examples, 0 failures
0m0.000s 0m0.000s
0m0.000s 0m0.000s

Success! Excellent!

Continue with part 6 - outline script