Parameters#
Declaring parameters, flags, extras, and reusable parameter sets.
Add a required parameter#
Declare a parameter the caller must supply and read it in the body.
APIs: knit_with_required, knit_get_parameter
knit_with_required <name:type> <description>, placed between
knit_register and knit_done, declares a parameter the caller must supply
as --name value. The body reads it back with knit_get_parameter:
# A required parameter must be supplied; the body reads it with knit_get_parameter.
@command "greet" "Greet someone."
@with_required "name:string" "Who to greet."
_greet() {
local name
name=$(knit_get_parameter "name" "$@")
echo "Hello ${name}"
}
@done
# A required parameter must be supplied; the body reads it with knit_get_parameter.
knit_register "greet" _greet "Greet someone."
knit_with_required "name:string" "Who to greet."
_greet() {
local name
name=$(knit_get_parameter "name" "$@")
echo "Hello ${name}"
}
knit_done
The type annotation is required — there is no default type, so name alone
is rejected and you must write name:string, width:integer, and so on (see
the Types category for the full list). knit_get_parameter <name> "$@" prints
the value, accepting both --name value and --name=value.
Positional arguments#
Why Knit has no positional parameters, and what to write instead.
APIs: knit_with_required
Knit deliberately has no positional parameters: every value is passed by name
(--width 3), never by position (box 3 4). This is a design choice, not a
limitation — forcing callers to name every argument keeps experiment scripts
explicit and self-documenting, and rules out the classic positional mistakes of
swapping two arguments or silently dropping one.
A bare token where a command name would go is treated as a subcommand, so
box 3 4 fails with an “Unknown command” error rather than being quietly
misread as --width 3 --height 4. Write box --width 3 --height 4 instead:
the order no longer matters, and a forgotten argument is reported as a missing
required parameter rather than shifting every value by one.
Optional parameters and flags#
Give a parameter a default, or declare a boolean flag.
APIs: knit_with_optional, knit_with_flag, knit_get_parameter
knit_with_optional <name:type> <default> <description> declares a parameter
that falls back to a default when omitted. knit_with_flag <name> <description>
declares a boolean flag that is either present or not:
# An optional parameter has a default; a flag is either present or not and reads
# back as "true"/"false".
@command "shout" "Greet someone, optionally louder."
@with_optional "name:string" "World" "Who to greet."
@with_flag "excited" "Add an exclamation mark."
_shout() {
local name excited greeting
name=$(knit_get_parameter "name" "$@")
excited=$(knit_get_parameter "excited" "$@")
greeting="Hello ${name}"
[[ "${excited}" == "true" ]] && greeting="${greeting}!"
echo "${greeting}"
}
@done
# An optional parameter has a default; a flag is either present or not and reads
# back as "true"/"false".
knit_register "shout" _shout "Greet someone, optionally louder."
knit_with_optional "name:string" "World" "Who to greet."
knit_with_flag "excited" "Add an exclamation mark."
_shout() {
local name excited greeting
name=$(knit_get_parameter "name" "$@")
excited=$(knit_get_parameter "excited" "$@")
greeting="Hello ${name}"
[[ "${excited}" == "true" ]] && greeting="${greeting}!"
echo "${greeting}"
}
knit_done
As with knit_with_required, the type annotation is mandatory (name:string,
count:integer, …). A flag is implicitly boolean, so it takes no type; it reads
back through knit_get_parameter as the string true (when the flag was
passed) or false (when it was not), so test it with [[ "${flag}" == "true"
]].
Default a parameter from the environment#
Fall back to an environment variable when a parameter is omitted.
APIs: knit_with_optional
Write an optional parameter’s default as ENV[NAME] to fall back to the
NAME environment variable when the caller does not pass the parameter (empty
when NAME is unset):
# An "ENV[NAME]" default falls back to the NAME environment variable when the
# parameter is not passed (empty if NAME is unset). Resolved when the parameter
# is filled in, so a job picks up a value exported by its setup.
@command "roll" "Print the random seed in use."
@with_optional "seed:integer" "ENV[SEED]" "Random seed."
_roll() {
echo "seed=$(knit_get_parameter "seed" "$@")"
}
@done
# An "ENV[NAME]" default falls back to the NAME environment variable when the
# parameter is not passed (empty if NAME is unset). Resolved when the parameter
# is filled in, so a job picks up a value exported by its setup.
knit_register "roll" _roll "Print the random seed in use."
knit_with_optional "seed:integer" "ENV[SEED]" "Random seed."
_roll() {
echo "seed=$(knit_get_parameter "seed" "$@")"
}
knit_done
The fallback is resolved when the parameter is filled in, not at registration, so
a job picks up a value exported by its setup’s environment. An explicit --seed
on the command line always wins over the environment, so ./exp.sh roll --seed
42 and SEED=42 ./exp.sh roll are equivalent — the flag is just a way to set
the same value inline.
Reuse parameters with a parameter set#
Declare a group of parameters once and import it into many commands.
APIs: knit_parameter_set, knit_with_parameter_set, knit_with_required
When several commands share the same parameters, declare them once in a named
parameter set and import the set wherever you need it. A set is opened with
knit_parameter_set, populated with the usual knit_with_* calls, and
closed with knit_done:
# Define a reusable set of parameters once...
@parameter_set "grid"
@with_required "width:integer" "Grid width."
@with_required "height:integer" "Grid height."
@done
# ...then import it into any command with @with_parameter_set.
@command "area" "Compute a grid area."
@with_parameter_set "grid"
_area() {
local width height
width=$(knit_get_parameter "width" "$@")
height=$(knit_get_parameter "height" "$@")
echo $(( width * height ))
}
@done
# Define a reusable set of parameters once...
knit_parameter_set "grid"
knit_with_required "width:integer" "Grid width."
knit_with_required "height:integer" "Grid height."
knit_done
# ...then import it into any command with @with_parameter_set.
knit_register "area" _area "Compute a grid area."
knit_with_parameter_set "grid"
_area() {
local width height
width=$(knit_get_parameter "width" "$@")
height=$(knit_get_parameter "height" "$@")
echo $(( width * height ))
}
knit_done
knit_with_parameter_set <name> copies the set’s parameters into the command
being registered; call it as many times as you like. A parameter from the set
that collides with one already declared on the command is a fatal error.
Import only some parameters of a set#
Import just a few of a parameter set’s parameters with –only.
APIs: knit_with_parameter_set, knit_parameter_set
By default knit_with_parameter_set imports every parameter a set declares.
Pass --only with a comma-separated allow-list to import just those
parameters and leave the rest behind:
# --only imports just the named parameters of a set (an allow-list); the rest are
# left out.
@command "width-only" "Report just the grid width."
@with_parameter_set "grid" --only "width"
_width_only() {
echo "$(knit_get_parameter "width" "$@")"
}
@done
# --only imports just the named parameters of a set (an allow-list); the rest are
# left out.
knit_register "width-only" _width_only "Report just the grid width."
knit_with_parameter_set "grid" --only "width"
_width_only() {
echo "$(knit_get_parameter "width" "$@")"
}
knit_done
Every name in the list must exist in the set, else the call is fatal — this catches a typo that would otherwise silently import the whole set.
Exclude parameters when importing a set#
Import a parameter set minus a few parameters, freeing those names to re-declare.
APIs: knit_with_parameter_set, knit_parameter_set, knit_with_optional
Pass --exclude with a comma-separated deny-list to import every parameter of
a set but the named ones. Excluding a name also frees it, so the command can
declare it itself with a different kind or default — here a parameter that is
required in the set becomes optional on this command:
# --exclude imports every parameter but the named ones, freeing a name so the
# command can re-declare it differently (here "height" becomes optional).
@command "flat-area" "Compute a grid area with an optional height."
@with_parameter_set "grid" --exclude "height"
@with_optional "height:integer" "1" "Grid height (defaults to 1)."
_flat_area() {
local width height
width=$(knit_get_parameter "width" "$@")
height=$(knit_get_parameter "height" "$@")
echo $(( width * height ))
}
@done
# --exclude imports every parameter but the named ones, freeing a name so the
# command can re-declare it differently (here "height" becomes optional).
knit_register "flat-area" _flat_area "Compute a grid area with an optional height."
knit_with_parameter_set "grid" --exclude "height"
knit_with_optional "height:integer" "1" "Grid height (defaults to 1)."
_flat_area() {
local width height
width=$(knit_get_parameter "width" "$@")
height=$(knit_get_parameter "height" "$@")
echo $(( width * height ))
}
knit_done
Every excluded name must exist in the set, else the call is fatal.
--exclude and --only are mutually exclusive.
Pass opaque trailing arguments#
Accept arbitrary arguments after – and read them in the body.
APIs: knit_with_extra, knit_extra_index
To let a command accept arbitrary arguments after -- (for example to forward
them to another program), document them with knit_with_extra and read them in
the body starting at knit_extra_index:
# @with_extra documents the arguments accepted after "--"; the body reads them
# starting at knit_extra_index.
@command "forward" "Echo the arguments given after --."
@with_extra "The arguments to echo."
_forward() {
local args=("$@") extra_index extra
extra_index=$(knit_extra_index "${args[@]}")
extra=("${args[@]:extra_index}")
printf '%s\n' "${extra[*]}"
}
@done
# @with_extra documents the arguments accepted after "--"; the body reads them
# starting at knit_extra_index.
knit_register "forward" _forward "Echo the arguments given after --."
knit_with_extra "The arguments to echo."
_forward() {
local args=("$@") extra_index extra
extra_index=$(knit_extra_index "${args[@]}")
extra=("${args[@]:extra_index}")
printf '%s\n' "${extra[*]}"
}
knit_done
knit_extra_index returns the index of the first argument after -- (or the
argument count when there is no --), so "${args[@]:extra_index}" is
exactly the trailing arguments. They are passed through verbatim and are not
validated against the command’s declared parameters.
Warning
Use trailing arguments sparingly. Unlike declared parameters, they are not
broken out into their own database columns — they are recorded as one opaque
blob — so you cannot cleanly filter or group runs by them afterwards. When a
value has a known meaning, prefer a named parameter with knit_with_required
/ knit_with_optional so it lands in its own column.
Validate args in a plain function#
Reject unexpected arguments in a helper that is not a registered command.
APIs: knit_check_arguments, knit_get_parameter
Registered commands validate their arguments automatically, but a plain helper
that parses its own "$@" does not. knit_check_arguments <options> <flags>
"$@" gives such a helper the same check: the first list names options that take
a value, the second names flags, and it returns 1 on the first unexpected
argument (logging an error attributed to the caller):
# A plain helper (not registered) can validate its own "$@" with
# knit_check_arguments: the first list names options that take a value, the second
# names flags. It errors on the first unexpected argument and returns 1.
_render() {
local args=("$@")
knit_check_arguments "size" "verbose" "${args[@]}" || return 1
echo "size=$(knit_get_parameter "size" "${args[@]}")"
}
@command "render" "Render at a given size."
@with_optional "size:integer" "8" "Image size."
_render_cmd() {
_render --size "$(knit_get_parameter "size" "$@")"
}
@done
# A plain helper (not registered) can validate its own "$@" with
# knit_check_arguments: the first list names options that take a value, the second
# names flags. It errors on the first unexpected argument and returns 1.
_render() {
local args=("$@")
knit_check_arguments "size" "verbose" "${args[@]}" || return 1
echo "size=$(knit_get_parameter "size" "${args[@]}")"
}
knit_register "render" _render_cmd "Render at a given size."
knit_with_optional "size:integer" "8" "Image size."
_render_cmd() {
_render --size "$(knit_get_parameter "size" "$@")"
}
knit_done
Names use hyphens or underscores interchangeably, and everything from a literal
-- onwards is treated as extra and left unchecked.