Skip to content

Scripting

The milk-cli interpreter is designed to provide a seamless scripting experience by transparently integrating with your system's bash shell. Any command that is not a native milk-cli command is automatically evaluated by the underlying OS shell using wordexp().

This means all standard Bash scripting features are fully supported inside milk-cli, including:

  • Variables ($var), Arithmetic ($(( ))), array indexing, and bash-style parameter expansions
  • Flow Control (if, for, while, case), including C-style for ((i=0; i<N; i++)) loops
  • Built-ins (sleep, read, printf, trap, shift)
  • I/O Redirection (>. <. |), Heredocs, and Background Jobs (&)

This page documents the native milk-cli extensions and how to use them alongside standard bash in your scripts.

See also: CLI Syntax · FPS · Streams

Script Files

Running Scripts

You can execute a milk-cli script file by passing the -s flag to the binary or by using the source (or .) command from within an active interactive session:

#!/usr/bin/env milk-cli -s

# setup.milk — initialize processing pipeline
mem.mk2Dim wfs 128 128
echo "Environment initialized."
# Inside milk-cli interactive mode:
source setup.milk

Include Guard

include_once sources a file only once per session, even if called multiple times. This is useful for loading helper function libraries without redundant evaluation:

#!/usr/bin/env milk-cli -s
include_once helpers.milk
include_once helpers.milk   # no-op

Startup Profile

If the ~/.milkrc file exists, it is automatically sourced at interactive startup. Use this file to define persistent aliases, load standard variables, or register helper functions.

Saving State

Export all current variables, aliases, and function definitions to a file so they can be reloaded later:

#!/usr/bin/env milk-cli -s
savescript state.milk

Similarly, write your interactive Readline command history to a replayable script text file:

#!/usr/bin/env milk-cli -s
savehistory replay.milk

milk-cli Native Features

The following commands and syntactic expansions are implemented natively by the milk-cli engine logic and take precedence over standard bash built-ins.

Stream Event Triggers (on_update)

The on_update command blocks execution until a shared-memory stream posts its semaphore (i.e., a new frame arrives), then executes a provided command block.

This is extremely useful for event-driven processing and synchronous scripts:

#!/usr/bin/env milk-cli -s
on_update wfs_cam { echo "New WFS frame received!" }

Enhanced Conditional Tests ([ ])

The native interpreter supports a robust set of file and logic tests inside [ ] that run instantly without invoking test subprocesses:

  • Filesystem: -f (file), -d (directory), -e (exists), -s (non-empty), -r (readable), -w (writable), -x (executable), -L (symlink).
  • Strings: -n (not empty), -z (empty), ==, !=, =~ (POSIX regex match).
  • Numbers: -eq, -ne, -lt, -le, -gt, -ge.
  • Logic: -a (AND), -o (OR), ! (NOT).

Wait for Resources (waitfor_*)

You can tell your script to pause and wait for shared memory streams or FPS parameter blocks to be initialized by other compute units before proceeding:

#!/usr/bin/env milk-cli -s
waitfor_stream wfs_cam 30    # wait up to 30s for the stream
waitfor_fps dmcomb 10        # wait up to 10s for the FPS

This returns 0 on success and 1 on timeout. The default timeout if omitted is 10 seconds. This allows for robust orchestration in startup scripts:

#!/usr/bin/env milk-cli -s
waitfor_stream wfs_cam 60
if [ $? -eq 0 ]; then
    echo "Stream is ready!"
fi

FPS Parameter Expansion

You can effortlessly read FPS (Function Processing System) parameters directly into bash variables using the native @fpsname.param syntax:

#!/usr/bin/env milk-cli -s
echo "The current gain is: @myloop.loopgain"

# Store in a variable
g=@myloop.loopgain

To write FPS parameters programmatically, natively use the fpsset command:

#!/usr/bin/env milk-cli -s
# Note: Do not use the '@' prefix when writing
fpsset myloop loopgain 0.5

Advanced Variable Expansion and Arrays

milk-cli's native interpreter includes powerful bash-like variable expansions:

  • Default values: ${var:-default} returns default if var is empty/unset.
  • Assign default: ${var:=default} sets var to default if it was empty/unset.
  • Error if empty: ${var:?message} prints an error if var is empty/unset.
  • Substring: ${var:offset:length} returns a slice of the string. Negative offsets count from the end.
  • String length: ${#var} returns the number of characters in the variable.
  • Array indexing: ${myarray[idx]} or ${myassoc[key]} returns the value at the given element of the respective array.
  • Array splat: ${myarray[@]} expands to all elements in the array joined by a space.
  • Array size: ${#myarray[@]} returns the number of elements in the array. For mathematical expressions, the $(( ... )) expansion natively supports standard arithmetic and bitwise logic:

  • Basic operators: + - * / %

  • Bitwise operators: & | ^ << >> ~
  • Comparisons: == != < > <= >=

Stream & FPS Metadata Expansion

Similar to FPS parameters, you can access properties of shared memory streams via dot-syntax expansion:

#!/usr/bin/env milk-cli -s
echo "Geometry: ${mystream.xsize}x${mystream.ysize}x${mystream.zsize}"
echo "Datatype Code: ${mystream.type}"
echo "Frame Counter: ${mystream.cnt0}"
echo "Number of axes: ${mystream.naxis}"

For FPS compute units, you can check their allocation status:

#!/usr/bin/env milk-cli -s
echo "Status: ${myfps.status}"     # 1 if exists, 0 if unconnected

Scripting Showcase

Here are several examples demonstrating how milk-cli native features combine with standard bash utilities to form powerful automation scripts.

Example 1: Parameter Defaults and String Manipulation

Because milk-cli correctly delegates back to bash, you can safely use standard recursive shell expansions (e.g. ## suffix stripping) to quickly parse paths:

#!/usr/bin/env milk-cli -s
function process_image {
    local img_path=${1:-/data/default.fits}
    local filename=${img_path##*/}   # strip path prefix
    local basename=${filename%.*}    # strip extension

    echo "Processing ${basename}..."
}

process_image
process_image /tmp/test_image.fits

Output:

Processing default...
Processing test_image...

Example 2: Transparent OS Fallback

Combine Linux shell utilities directly with native milk-cli commands. In scripts, there is no need to prefix standard bash commands with ! like inside interactive mode.

#!/usr/bin/env milk-cli -s
prefix="output_"
ext=".fits"

# Run the native milk command to process the file
milk-FITS2shm image.fits wfs_cam

# Use standard shell binaries to print system info
count=$(ls -1 | grep -c "${ext}")
echo "Found $count FITS files."

tag=$(date +%Y%m%d_%H%M%S)
echo "Saving to: ${prefix}${tag}${ext}"
Example 3: Waiting for Streams and reading Metadata

Block until multiple shared-memory streams become available during startup, then dynamically read their geometry properties via native dot-expansion:

#!/usr/bin/env milk-cli -s
function wait_and_monitor {
    local stream=$1
    echo "Waiting for stream ${stream}..."

    waitfor_stream $stream 60
    if [ $? -ne 0 ]; then
        echo "Error: Stream ${stream} timed out."
        return 1
    fi

    # Read stream metadata via dot-expansion
    echo "Ready! Shape: ${${stream}.xsize}x${${stream}.ysize}"
}

wait_and_monitor wfs_cam
wait_and_monitor dm_disp
Example 4: Batch FPS Parameter Manipulation

Read and write FPS parameters from a script to automate configuration changes across multiple compute units, leveraging standard bash for loop integer evaluation to calculate vector indices:

#!/usr/bin/env milk-cli -s

# Retrieve current value and log it
gain=$(milk-fps-set dmcomb.loopgain)
echo "DM combiner gain was: $gain"

# Set the loop gain on the DM combiner FPS native memory
fpsset dmcomb loopgain 1.0

# Apply identical gain to all modal channels using a bash loop
nmodes=50
for m in $(seq 0 $(( nmodes - 1 ))); do
    fpsset dmcomb modesgain[$m] 0.1
done

echo "Set $nmodes modal gains to 0.1"
Example 5: Stream Diagnostic Report

Collect live metadata from multiple streams and natively format a compact diagnostic table using string expansion and alignment flags.

#!/usr/bin/env milk-cli -s
streams=(wfs_cam dm_disp wfs_ref)

echo "--------------------------------------------"
echo "  Stream           XSize  YSize  Frame"
echo "--------------------------------------------"

for s in ${streams[@]}; do
    waitfor_stream $s 5
    if [ $? -ne 0 ]; then
        printf "  %-18s OFFLINE\n" $s
        continue
    fi

    xs=${${s}.xsize}
    ys=${${s}.ysize}
    cnt=${${s}.cnt0}
    printf "  %-18s %-6s %-6s %s\n" $s $xs $ys $cnt
done

echo "--------------------------------------------"
Example 6: AO Loop Startup Orchestration

A complete startup script that initialises an AO loop step by step, verifies each stage using milk-cli conditionals, and aborts cleanly on hardware failure:

#!/usr/bin/env milk-cli -s

# ---------- helpers ----------
function die {
    echo "FATAL: $1"
    exit 1
}

function wait_stream {
    local s=$1 timeout=${2:-30}
    waitfor_stream $s $timeout
    [ $? -ne 0 ] && die "Stream '$s' not available after ${timeout}s"
    echo "  [OK] $s  ${${s}.xsize}x${${s}.ysize}"
}

# ---------- 1. verify hardware streams ----------
echo "=== 1. Checking hardware streams ==="
wait_stream wfs_cam 60
wait_stream dm_volt

# ---------- 2. load reference PSF ----------
echo "=== 2. Loading WFS reference ==="
milk-FITS2shm wfs_ref.fits wfs_ref
wait_stream wfs_ref

# ---------- 3. start modal decomposition FPS ----------
echo "=== 3. Starting modal decomposition ==="
milk-fpsexec-cacaoloop-WFS -n wfs01 -tmux
sleep 2
waitfor_stream wfs_modes 20
[ $? -ne 0 ] && die "Modal decomposition failed to produce wfs_modes"

# ---------- 4. configure loop gains ----------
echo "=== 4. Configuring loop gains ==="
nmodes=100
for m in $(seq 0 $(( nmodes - 1 ))); do
    fpsset dmcomb modesgain[$m] 0.05
done
fpsset dmcomb loopgain 1.0
fpsset dmcomb loopON 1

# ---------- 5. confirm loop is running ----------
echo "=== 5. Loop status ==="
sleep 1
fpsgain=$(milk-fps-set dmcomb.loopgain)
echo "  loopgain = $fpsgain"
echo "AO loop started successfully."
Example 7: Real-Time Process Triggering & Monitoring

Use FPS parameters to configure procinfo settings, binding a compute unit to trigger automatically on a stream and monitoring its health from the script.

#!/usr/bin/env milk-cli -s

# 1. Start the compute unit process
milk-fpsexec-examplefunc2_FPS -tmux &
sleep 1

# 2. Configure it to be triggered by a stream (Mode 3 = SEMAPHORE)
fpsset examplefunc2_FPS procinfo.triggermode 3
fpsset examplefunc2_FPS procinfo.triggersname wfs_cam

# 3. Enable the background loop
fpsset examplefunc2_FPS procinfo.enabled 1

# 4. Monitor its execution natively
for i in {1..5}; do
    # Dot-expansion accesses the live telemetry via procinfo
    status=${examplefunc2_FPS.procinfo.status}
    loopcnt=${examplefunc2_FPS.procinfo.loopcnt}

    echo "Check $i: Status=$status, Iterations=$loopcnt"
    sleep 2
done
Example 8: Advanced Native Math and Image Calculus

You can run mathematical expressions natively, manipulate image values directly with constants, and calculate norms and conditions efficiently without needing external parsing like bc or awk.

#!/usr/bin/env milk-cli -s

# 1. Provide an initial constant to initialize variables
a = 2 + 3 * 4
echo "Variable 'a' evaluates natively to: $a"

# 2. Utilize mathematical functions like abs, max, min natively
e = min(abs(-5), max(3, fmod(17, 5)))
echo "Compound math 'e' limits safely to: $e"

# 3. Quickly generate and fill a virtual flat dummy space.
mem.mk2Dim imtest 10 10 
imtest = imtest * 0.0 + 5.0

# 4. Use vector constraints natively over memory blocks
d_im_im = dot(imtest, imtest)
n_im = norm(imtest)
echo "Dot product: $d_im_im | Norm vector length: $n_im"

# 5. Native Ternary Mask mapping limits logic cleanly:
imtest_plus = imtest + 3
imtest_mask = (imtest_plus == 8)
im_where = where(imtest_mask, imtest, -imtest)
h = imean(im_where)
echo "Filtered conditionally merged mean of masked image: $h"
Example 9: Bi-Directional Environment Variable Sync

milk-cli natively exports all active workspace variables to the underlying POSIX environment when dispatching shell commands (like !cmd or pipes), and can concurrently read standard Linux environment variables without requiring manual exports.

#!/usr/bin/env milk-cli -s

# 1. Read standard OS variables natively (Inward Sync)
echo "Operating as user: $USER in home directory: $HOME"

# 2. Define a milk-cli local variable
PREFIX="WFS_DATA"

# 3. Use it transparently inside a dispatched shell command (Outward Sync)
!echo "Saving to prefix: $PREFIX" > /tmp/${PREFIX}_log.txt
!cat /tmp/${PREFIX}_log.txt
Example 10: Advanced Hybrid Scripting (Bash + milk-cli Native)

Combine the robust argument parsing capabilities of bash with the zero-overhead execution of the milk-cli interpreter. By parsing arguments in bash and then dropping into a milk-cli heredoc, the script gains native access to FPS, streams, and image calculus without sacrificing traditional command-line interfaces.

See scripts/milk-script-advanced for the full template.

#!/usr/bin/env bash

# 1. BASH HEADER: Argument Parsing
MSdescr="Advanced Native milk-cli Script Template"
MSarg+=( "streamname:string:Name of the input stream to wait for" )

# Parse arguments (milk-argparse automatically exports $STREAMNAME)
source milk-argparse

# 2. NATIVE EXECUTION: Zero-Overhead milk-cli Block
milk-cli -s << 'EOF'

echo "Waiting natively for stream: $STREAMNAME..."
# Event-Driven Execution without polling loops
waitfor_stream $STREAMNAME 10

# Fast Native Image Math & FPS
mem.mk2Dim mask_img ${$STREAMNAME.xsize} ${$STREAMNAME.ysize}
mask_img = mask_img * 0.0 + 1.0

on_update $STREAMNAME {
    echo "Frame arrived!"
}
EOF

CLI Syntax · Documentation Index