Task organisation for dev projects,
based on a pure shell script.


Get started

(Latest version of task runner)

  1. Download task runner
  2. Make it executable, e.g. chmod +x run
  3. Put into path, e.g. mv run /usr/local/bin/run

Needs Bash 3.2+, tested on Linux and macOS.

How it works

Place a task file called run.sh in your project root and make it executable. Say, for a NodeJS project, it could be like:

# Start web server
run::server() {
PORT=8000 \
CONFIG=/app/config.yml \
node src/server.js

# Install dependencies
run::install() {
npm install \

A run.sh task file is a plain regular shell script. There is no special magic to it, it just adheres to the following convention:

The task definitions are shell commands that carry a run:: prefix. Tasks can optionally be preceded by a descriptive comment. The first comment line is interpreted as title, and the following ones as additional info text.

These notation rules allow the run CLI tool to pick up the tasks, e.g.:

When the task runner runs a task, it evaluates the run.sh task file from top to bottom – so when using third-party projects, please make sure it’s trustworthy.

Without the run CLI tool available (e.g. when in a CI or production environment), you can still access your tasks by sourcing the run.sh task file and then directly calling the task commands by their original names.

That’s all there is.


Shell cheat sheet

Here are a few handy shell scripting snippets.

Variable with default fallback value


“Ternary” assignment

PORT=$([[ "$IS_PROD" ]] && echo 443 || echo 8080)

Task input args

run::hello() {
echo "$1" # First arg
echo "$2" # Second arg

Pass on all task input args

run::print() {
echo "$@"

Call other task

run::hello() {

Check, if…

[[ -f file.txt ]] # … file exists
[[ -d folder/ ]] # … directory exists
[[ -z "$VAR" ]] # … var is empty/unset (“zero”)
[[ -n "$VAR" ]] # … var is not empty

Read file contents into variable


Include other shell file

source otherfile.sh
(The path is relative to the shell’s current working directory.)

Get absolute path of script’s location

THIS_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
(This declaration must be placed at top level.)

Use .env files

source .env
# You can also do that conditionally, e.g.: [[ -f .env ]] && source .env
[[ -f .env ]] && source .env || source .env.dev

Exit immediately on failure

set -o errexit
(Beware, this has gotchas.)