The Original Task Runner
2018-09-07It seems to me that the tools we use are changing so quickly that sometimes we forget to look around at what we already have before we invent something new. Don't get me wrong, specialised build tools and task runners have their place and can make things easier, but they can sometimes unnecessarily overcomplicate things.
Simplicity is prerequisite for reliability. Edsger Dijkstra
Enter Bash; installed on most Linux distributions by default, available on Mac and even Windows. Cross platform!
People have been using Bash scripts to do this sort of thing for quite a while now, but hopefully this will put a slightly different spin on it.
The $@
Parameter
$@
is a special Bash parameter. Here's a snippet from the Bash man pages:
Expands to the positional parameters, starting from one. [...] When there are no positional parameters, "$@" and $@ expand to nothing (i.e., they are removed) "Bash" man pages
Before we jump into why this is relevant, try running the following commands:
bash-4.4$ FOO=ls
bash-4.4$ $FOO -a /tmp/test
. ..
bash-4.4$
Whats happening? Let's take a look with tracing turned on.
bash-4.4$ set -x
bash-4.4$ FOO=ls
+ FOO=ls
bash-4.4$ $FOO -a /tmp/test
+ ls -a /tmp/test
. ..
bash-4.4$
As you can see, the $FOO
variable is expanded into ls
and then it's
executed.
Now, lets get back on track and take a closer look at $@
. We'll do a similar
thing as above, but with $@
instead. This time, we can't directly set $@
,
as it expands to the positional parameters
, so we'll create a simple
script containing the following:
echo $@
$@
Yep, that's it... so lets run it: bash -xv script.sh echo Hello
.
bash-4.4$ bash -xv script.sh echo Hello
echo $@
+ echo echo Hello
echo Hello
$@
+ echo Hello
Hello
bash-4.4$
You can see that our initial echo $@
was expanded to echo echo Hello
(as
$@
contains echo Hello
), but more interestingly, the line $@
on it's own
was expanded to echo Hello
, the first "word" (echo
) was treated as the
command (as nothing preceded it) and everything else was treated as arguments.
Practical Use
With this knowledge, we can do some really cool things, like defining functions within our script that we can call externally:
function speak()
{
echo "Hello :)"
}
$@
Run ./script.sh speak
and we get:
Hello :)
Why? Well, let's see, we'll run it with bash -xv
to find out:
bash-4.4$ bash -xv script.sh speak
function speak()
{
echo "Hello :)"
}
$@
+ speak
+ echo 'Hello :)'
Hello :)
bash-4.4$
Just after the function, you can see the expansion from $@
to speak
, which
in turn runs echo 'Hello :)'
.
Something More Complex?
How about a build tool with set up and tear down functionality?
function set_up()
{
echo "Setting up..."
}
function tear_down()
{
echo "Tearing down..."
}
function something()
{
set_up
echo "Doing something..."
tear_down
}
$@
Let's abstract this further:
function set_up()
{
echo "Setting up..."
}
function tear_down()
{
echo "Tearing down..."
}
function wrap()
{
set_up
$@
tear_down
}
function something()
{
wrap echo "Doing something..."
}
$@
Default Command?
What about having a function execute automatically if no parameters are given on the command line?
function something()
{
echo "Doing something..."
}
function another()
{
echo "Doing another..."
}
${@:-another}
another
is now our default.
Passing Parameters to Functions
How about passing parameters in to functions...
function greet()
{
echo "Hello there $1"
}
$@
Run with ./script.sh greet Alice
to get:
Hello there Alice
Required parameters?
function greet()
{
[[ -z "$1" ]] && echo 'Please provide a name ($1)' && exit 1
echo "Hello there $1"
}
$@
Looking Like a Pro
If you're going to use this on a daily basis, typing ./script.sh
is
probably going to get tiresome quite quickly, so here's a couple of ideas to
help:
- Call the script something short and relevant like
run
, you don't need the.sh
extension. - Add an alias (
alias run=./run
) in your.bashrc
file, so you can just typerun
rather than./run
.