Tutorial
Authoring a Template Runtime
Use run.command for thin wrappers and run.sh only when the template needs real script logic.
Linkar templates should stay small enough to read in one sitting.
The default decision is:
- use
run.commandfor a thin one-command wrapper - use
run.shwhen the template needs real shell logic
Start with the smallest useful contract
The smallest useful template is often just linkar_template.yaml plus a local test:
my_template/
linkar_template.yaml
test.py
Example:
id: simple_echo
version: 0.1.0
description: Write a greeting file.
params:
name:
type: str
required: true
outputs:
greeting_file:
path: greeting_file
run:
command: >-
printf 'hello %s\n' "${param:name}" > "${LINKAR_RESULTS_DIR}/greeting_file"
That is cleaner than creating a run.sh whose only job is to forward one command.
How parameters arrive in run.command and run.sh
The preferred authoring style in run.command is the explicit placeholder form.
If the template declares:
params:
input_fastq:
type: path
required: true
threads:
type: int
default: 4
then a command should normally read:
${param:input_fastq}${param:threads}
Linkar still supports the older implicit shell-variable convention:
input_fastq->${INPUT_FASTQ}threads->${THREADS}
but new templates should prefer ${param:...} because it is clearer to template authors.
Use explicit defaults in the schema whenever possible. That keeps runtime logic small and readable.
When run.sh is the better tool
Use run.sh when the template needs:
- branching
- temp files
- generated config files
- multiple local commands
- traps and cleanup
Typical shape:
my_template/
linkar_template.yaml
run.sh
test.sh or test.py
optional support files...
Example:
run:
entry: run.sh
#!/usr/bin/env bash
set -euo pipefail
if [[ "${PAIRED_END:-true}" == "true" ]]; then
mytool --r1 "${R1}" --r2 "${R2}" --out "${LINKAR_RESULTS_DIR}"
else
mytool --r1 "${R1}" --out "${LINKAR_RESULTS_DIR}"
fi
Render command and launcher generation
linkar render ... stages the template bundle and writes one standalone run.sh without executing the template.
The rendered script does not silently cd for you. It expects to be run from inside the rendered directory, so the artifact stays explicit and easy to inspect.
That is especially useful for templates declared as one command:
run:
command: >-
pixi run python -m demux_pipeline.cli
--outdir "${LINKAR_RESULTS_DIR}"
--bcl_dir "${param:bcl_dir}"
--samplesheet "${param:samplesheet}"
The rendered directory then contains one launcher, not a template-local wrapper plus a second outer wrapper.
If your template entry is already run.sh, Linkar renders that same script in place and injects
the resolved runtime environment there. It does not hide the real entrypoint under .linkar/.
Keep testing local and simple
Use one local test entrypoint:
test.shfor script-oriented templatestest.pyfor contract inspection, filesystem assertions, and more involved mocking
Normal validation path:
linkar test simple_echo --pack ./examples/packs/basic
Rule of thumb
- prefer
run.commandwhen one command is enough - prefer
run.shwhen logic is real and local - switch to
run.pywhen shell stops being clearer