Quickstart#

This page takes you from zero to a recorded command in a few minutes. You will grow a small experiment one command at a time, run each command, and end with a run stored in a database — the foundation everything else in Knit builds on.

An experiment is a script#

A Knit experiment is an ordinary Bash script (we will call it exp.sh hereafter). It sources knit.sh (kept next to the script), describes itself, registers one or more commands, and ends with the single line knit "$@" that hands the command line to Knit. The overall shape is:

source knit.sh

knit_set_program_description "A tiny quickstart experiment."

# ... register your commands here ...

knit "$@"

knit_set_program_description sets the blurb shown at the top of --help. Everything between the source line and the final knit "$@" registers the commands your experiment offers. We fill that middle in below, one command at a time.

Running ./exp.sh --help already works and shows a handful of built-in commands, along with your experiment’s description as set above. The commands you register below join that same listing once you have bootstrapped the experiment (the next section) — before that, only the built-ins that work without a database are shown.

Your first command#

A command is declared with @command <name> <description> and closed with @done; in between go its parameters (none yet) and the Bash function Knit runs for the command. The <name> is the command as typed on the command line, and Knit calls the function it finds defined just below the declaration — so you never repeat the function’s name. Here is a command that just prints a greeting:

@command "hello" "Print a greeting."
_hello() {
    echo "Hello World"
}
@done
knit_register "hello" _hello "Print a greeting."
_hello() {
    echo "Hello World"
}
knit_done

Note

The function is named _hello, not hello. Because Knit binds a command to the function defined just below its declaration, the two names are independent — the function name does not have to match the command name. Giving the function a leading underscore is good practice: it keeps the command (hello) and its Bash function (_hello) visibly distinct and marks the function as an internal helper of the experiment. The rest of this page follows that convention (_say, _greet, _scale, _add).

Before running any command, bootstrap the experiment once. This creates a .knit/ directory holding a small SQLite database (and, if they are not already on your system, installs the tools Knit relies on):

$ ./exp.sh bootstrap

Now run the command:

$ ./exp.sh hello
Hello World

Note

Command names (like many other names in Knit) accept hyphens and underscores interchangeably, so a command registered as db-show can also be invoked as db_show.

Note

@command, @done, and the @with_* decorators you meet below are Knit’s shorthand for its declaration API. Every knit_x declaration function has an @x twin (knit_register is written @command and knit_register_<x> is written @<x>). The shorthand is enabled by default and is what this documentation uses; the canonical knit_* functions remain available and unchanged.

Each code example on this site has two tabs: Shorthand shows the @ form, and Long form shows the equivalent knit_* calls. Select a tab and the site keeps your choice for every other example, on this page and the next. An example with no shorthand shows one block, because both tabs are the same.

Taking a parameter#

Parameters are declared between @command and @done. @with_required "name:type" <description> adds a required one. Inside the body, we can read it back with knit_get_parameter:

@command "say" "Repeat a message."
@with_required "message:string" "The message to repeat."
_say() {
    local message
    message="$(knit_get_parameter "message" "$@")"
    echo "User said '${message}'"
}
@done
knit_register "say" _say "Repeat a message."
knit_with_required "message:string" "The message to repeat."
_say() {
    local message
    message="$(knit_get_parameter "message" "$@")"
    echo "User said '${message}'"
}
knit_done

The parameter message becomes --message on the command line:

$ ./exp.sh say --message "good morning"
User said 'good morning'

Try ./exp.sh say --help to see the parameter, its type, and description laid out for you.

Parameter names accept hyphens and underscores interchangeably, (so my-param and my_param represent the same parameter) and the type vocabulary includes integer, real, string, boolean and uuid, among others.

Optional parameters and flags#

Beyond required parameters, a command can take optional parameters (with a default) and boolean flags. @with_optional "name:type" <default> <description> supplies a default used when the parameter is omitted; @with_flag <name> <description> adds a switch that reads back as true or false:

@command "greet" "Greet someone by name."
@with_required "name:string" "Who to greet."
@with_optional "title:string" "" "An optional title (Mr, Mrs, Prof., ...)."
@with_flag "capitalize" "Upper-case the whole greeting."
_greet() {
    local name title capitalize greeting
    name="$(knit_get_parameter "name" "$@")"
    title="$(knit_get_parameter "title" "$@")"
    capitalize="$(knit_get_parameter "capitalize" "$@")"
    if [[ -n "${title}" ]]; then
        greeting="Hello, ${title} ${name}!"
    else
        greeting="Hello, ${name}!"
    fi
    if [[ "${capitalize}" == "true" ]]; then
        greeting="${greeting^^}"
    fi
    echo "${greeting}"
}
@done
knit_register "greet" _greet "Greet someone by name."
knit_with_required "name:string" "Who to greet."
knit_with_optional "title:string" "" "An optional title (Mr, Mrs, Prof., ...)."
knit_with_flag "capitalize" "Upper-case the whole greeting."
_greet() {
    local name title capitalize greeting
    name="$(knit_get_parameter "name" "$@")"
    title="$(knit_get_parameter "title" "$@")"
    capitalize="$(knit_get_parameter "capitalize" "$@")"
    if [[ -n "${title}" ]]; then
        greeting="Hello, ${title} ${name}!"
    else
        greeting="Hello, ${name}!"
    fi
    if [[ "${capitalize}" == "true" ]]; then
        greeting="${greeting^^}"
    fi
    echo "${greeting}"
}
knit_done

--name is required, --title defaults to empty, and --capitalize is off unless given:

$ ./exp.sh greet --name Alice
Hello, Alice!
$ ./exp.sh greet --name Curie --title Prof.
Hello, Prof. Curie!
$ ./exp.sh greet --name Curie --title Prof. --capitalize
HELLO, PROF. CURIE!

Emitting an output#

A command can declare one or more outputs: a named, typed result it computes. @with_output "name:type" <default> <description> declares it (the default is used if the command exits before setting it), and knit_output <name> <value> emits it from the body:

@command "scale" "Multiply an integer by a factor."
@with_required "value:integer" "The value to scale."
@with_optional "factor:integer" "2" "The multiplier (defaults to 2)."
@with_output "result:integer" "0" "value * factor."
_scale() {
    local value factor
    value="$(knit_get_parameter "value" "$@")"
    factor="$(knit_get_parameter "factor" "$@")"
    knit_output "result" "$((value * factor))"
    printf 'result=%s\n' "$((value * factor))"
}
@done
knit_register "scale" _scale "Multiply an integer by a factor."
knit_with_required "value:integer" "The value to scale."
knit_with_optional "factor:integer" "2" "The multiplier (defaults to 2)."
knit_with_output "result:integer" "0" "value * factor."
_scale() {
    local value factor
    value="$(knit_get_parameter "value" "$@")"
    factor="$(knit_get_parameter "factor" "$@")"
    knit_output "result" "$((value * factor))"
    printf 'result=%s\n' "$((value * factor))"
}
knit_done
$ ./exp.sh scale --value 21
result=42
$ ./exp.sh scale --value 10 --factor 5
result=50

Recording calls in a table#

Declaring an output gives a run a result, but as written above, this result is not stored anywhere. Adding @with_table tells Knit to record every invocation — its parameters and outputs, plus some informations — as a row in a per-command table. Here is a command with two required parameters, an output, and a table:

@command "add" "Add two integers and record the run."
@with_required "x:integer" "First value."
@with_required "y:integer" "Second value."
@with_output "total:integer" "0" "x + y."
@with_table
_add() {
    local x y
    x="$(knit_get_parameter "x" "$@")"
    y="$(knit_get_parameter "y" "$@")"
    knit_output "total" "$((x + y))"
    printf 'total=%s\n' "$((x + y))"
}
@done
knit_register "add" _add "Add two integers and record the run."
knit_with_required "x:integer" "First value."
knit_with_required "y:integer" "Second value."
knit_with_output "total:integer" "0" "x + y."
knit_with_table
_add() {
    local x y
    x="$(knit_get_parameter "x" "$@")"
    y="$(knit_get_parameter "y" "$@")"
    knit_output "total" "$((x + y))"
    printf 'total=%s\n' "$((x + y))"
}
knit_done

Each time add runs, Knit writes one row into an add table:

$ ./exp.sh add --x 2 --y 3
total=5

Read the recorded rows straight back out with SQL:

$ ./exp.sh query sql --format column --header \
    --exec 'SELECT x, y, total FROM "add"'

Note

Recording is the first step of Knit’s full model (bootstrap → setup → submit → run → aggregate). The Tutorial picks up here and shows how to build environments, submit jobs, launch parallel apps, and aggregate their recorded results.

The complete experiment#

Here is the whole experiment in one file. Save it as exp.sh next to a copy of knit.sh, make it executable (chmod +x exp.sh), bootstrap once, and every command from this page is available:

#!/bin/bash

# The complete Quickstart experiment: the final state of the script built up one
# command at a time over the Quickstart page (hello, say, greet, scale, and the
# recorded add). Kept to the local backend so it runs anywhere with just bash and
# sqlite3. Functions are given a leading underscore so their names need not match
# the command names.

source knit.sh

knit_set_program_description "A tiny quickstart experiment."

@command "hello" "Print a greeting."
_hello() {
    echo "Hello World"
}
@done

@command "say" "Repeat a message."
@with_required "message:string" "The message to repeat."
_say() {
    local message
    message="$(knit_get_parameter "message" "$@")"
    echo "User said '${message}'"
}
@done

@command "greet" "Greet someone by name."
@with_required "name:string" "Who to greet."
@with_optional "title:string" "" "An optional title (Mr, Mrs, Prof., ...)."
@with_flag "capitalize" "Upper-case the whole greeting."
_greet() {
    local name title capitalize greeting
    name="$(knit_get_parameter "name" "$@")"
    title="$(knit_get_parameter "title" "$@")"
    capitalize="$(knit_get_parameter "capitalize" "$@")"
    if [[ -n "${title}" ]]; then
        greeting="Hello, ${title} ${name}!"
    else
        greeting="Hello, ${name}!"
    fi
    if [[ "${capitalize}" == "true" ]]; then
        greeting="${greeting^^}"
    fi
    echo "${greeting}"
}
@done

@command "scale" "Multiply an integer by a factor."
@with_required "value:integer" "The value to scale."
@with_optional "factor:integer" "2" "The multiplier (defaults to 2)."
@with_output "result:integer" "0" "value * factor."
_scale() {
    local value factor
    value="$(knit_get_parameter "value" "$@")"
    factor="$(knit_get_parameter "factor" "$@")"
    knit_output "result" "$((value * factor))"
    printf 'result=%s\n' "$((value * factor))"
}
@done

@command "add" "Add two integers and record the run."
@with_required "x:integer" "First value."
@with_required "y:integer" "Second value."
@with_output "total:integer" "0" "x + y."
@with_table
_add() {
    local x y
    x="$(knit_get_parameter "x" "$@")"
    y="$(knit_get_parameter "y" "$@")"
    knit_output "total" "$((x + y))"
    printf 'total=%s\n' "$((x + y))"
}
@done

knit "$@"
#!/bin/bash

# The complete Quickstart experiment: the final state of the script built up one
# command at a time over the Quickstart page (hello, say, greet, scale, and the
# recorded add). Kept to the local backend so it runs anywhere with just bash and
# sqlite3. Functions are given a leading underscore so their names need not match
# the command names.

source knit.sh

knit_set_program_description "A tiny quickstart experiment."

knit_register "hello" _hello "Print a greeting."
_hello() {
    echo "Hello World"
}
knit_done

knit_register "say" _say "Repeat a message."
knit_with_required "message:string" "The message to repeat."
_say() {
    local message
    message="$(knit_get_parameter "message" "$@")"
    echo "User said '${message}'"
}
knit_done

knit_register "greet" _greet "Greet someone by name."
knit_with_required "name:string" "Who to greet."
knit_with_optional "title:string" "" "An optional title (Mr, Mrs, Prof., ...)."
knit_with_flag "capitalize" "Upper-case the whole greeting."
_greet() {
    local name title capitalize greeting
    name="$(knit_get_parameter "name" "$@")"
    title="$(knit_get_parameter "title" "$@")"
    capitalize="$(knit_get_parameter "capitalize" "$@")"
    if [[ -n "${title}" ]]; then
        greeting="Hello, ${title} ${name}!"
    else
        greeting="Hello, ${name}!"
    fi
    if [[ "${capitalize}" == "true" ]]; then
        greeting="${greeting^^}"
    fi
    echo "${greeting}"
}
knit_done

knit_register "scale" _scale "Multiply an integer by a factor."
knit_with_required "value:integer" "The value to scale."
knit_with_optional "factor:integer" "2" "The multiplier (defaults to 2)."
knit_with_output "result:integer" "0" "value * factor."
_scale() {
    local value factor
    value="$(knit_get_parameter "value" "$@")"
    factor="$(knit_get_parameter "factor" "$@")"
    knit_output "result" "$((value * factor))"
    printf 'result=%s\n' "$((value * factor))"
}
knit_done

knit_register "add" _add "Add two integers and record the run."
knit_with_required "x:integer" "First value."
knit_with_required "y:integer" "Second value."
knit_with_output "total:integer" "0" "x + y."
knit_with_table
_add() {
    local x y
    x="$(knit_get_parameter "x" "$@")"
    y="$(knit_get_parameter "y" "$@")"
    knit_output "total" "$((x + y))"
    printf 'total=%s\n' "$((x + y))"
}
knit_done

knit "$@"