Skip to main content

Programmatic shell script generator with argparse, logging, validation, and structured output

Project description

ShellScriptor

Programmatic shell script generator with argument parsing, logging, validation, and structured output — driven by Python dicts or YAML/JSON definition files.


Contents


Overview

shellscriptor generates complete, production-quality shell scripts from a structured description. You provide a dict (or a YAML/JSON file) that declares parameters, validation rules, embedded functions, and body code; the library assembles the boilerplate so you don't have to:

Feature shell_src shell_exec
while/case argparse loop
--help / __usage__ ✓ (when descriptions present)
Validation checks
Entry / exit log markers
Argument-read logs
tee log capture to $LOGFILE
trap __cleanup__ EXIT
set -x when $DEBUG=true
Env-var fallback (called with no args)

Installation

pip install shellscriptor

YAML definition files require the optional pyyaml dependency:

pip install "shellscriptor[yaml]"

Quick start

Python

from shellscriptor import create_script

script = create_script(
    name="greet",
    script_type="shell_src",
    data={
        "interpreter": "usr/bin/env bash",
        "parameter": {
            "name": {"type": "string", "description": "Name to greet"},
        },
        "body": 'echo "Hello, $NAME!"',
    },
)
print(script)

CLI

# Write to stdout
shellscriptor greet.yaml

# Write to a file
shellscriptor greet.yaml --output dist/greet.sh

greet.yaml:

name: greet
type: shell_src
script:
  interpreter: usr/bin/env bash
  parameter:
    name:
      type: string
      description: Name to greet
  body: 'echo "Hello, $NAME!"'

Definition format

Script definition (ScriptDef)

Top-level keys accepted by create_script() and by the CLI wrapper format:

Key Type Description
interpreter str | null Shebang path without the leading #!. "usr/bin/env bash"#!/usr/bin/env bash. Omit for no shebang.
flags str | null Arguments passed to set after the shebang, e.g. "-euo pipefail".
import list[str] Names of functions to pull in from the global_functions pool passed to create_script().
function dict[str, FunctionDef] Locally defined functions embedded in the script.
parameter dict[str, ParameterDef] Script-level parameters accepted on the command line (or from env vars in shell_exec mode).
body body form Main script body executed after argument parsing and validation.
return list[ReturnDef] Values written to stdout on exit (shell_exec only).

Function definition (FunctionDef)

Accepted inside function and by create_function():

Key Type Description
parameter dict[str, ParameterDef] Parameters accepted by the function.
body body form Function body.
return list[ReturnDef] Values written to stdout when the function returns.

Parameter definition (ParameterDef)

Key Type Default Description
type "string" | "boolean" | "integer" | "array" required Shell type.
default str | list[str] | null null Default value. null with no explicit required field implies the parameter is required.
required bool | null null Explicit required flag. null → inferred from default.
description str "" Human-readable description for --help output. Triggers --help/__usage__ generation when non-empty on any parameter.
short str "" Single-character short flag alias, e.g. "o" creates -o alongside --<name>.
array_delimiter str " " Delimiter used when reading an array from a single environment variable (env-var fallback, shell_exec only).
validation ValidationDef | null null Optional validation rules.

Required vs optional — rules:

required default Behaviour
null (omitted) null Required — exits if missing.
null (omitted) "value" Optional — assigns default if missing.
true any Required — exits if missing; default ignored.
false "value" Optional — assigns default if missing.
false null Genuinely optional — no check emitted; variable may remain empty.

Validation rules (ValidationDef)

Nested inside ParameterDef.validation:

Key Type Description
enum list[str] Exhaustive list of accepted values. A value not in the list causes exit 1.
path_existence PathExistenceDef Assert the value is a path satisfying an existence condition.
integer_range IntegerRangeDef Assert the value is an integer within an inclusive numeric range.
regex str ERE pattern the full value must match. Non-matching values cause exit 1.
custom list[str] Verbatim shell lines appended after all other checks.

PathExistenceDef:

Key Type Description
must_exist bool true → path must exist; false → path must not exist.
type "dir" | "exec" | "file" | "symlink" | null Kind of filesystem entry to test. null accepts any entry.

IntegerRangeDef:

Key Type Description
min int | null Inclusive lower bound. null for no lower bound.
max int | null Inclusive upper bound. null for no upper bound.

Return value definition (ReturnDef)

Key Type Description
name str Human-readable label used in log messages.
variable str Shell variable name whose value is written to stdout.
type "string" | "boolean" | "integer" | "array" Type of the return value; drives encoding.

Script types

shell_src — sourced library

Generates a lean script intended to be sourced (. script.sh).

  • Functions and top-level while/case argument parsing are emitted.
  • No logging boilerplate, no trap, no tee.
  • Good for utility libraries and helper functions.

shell_exec — executable script

Generates a self-contained, directly executable script with:

  • Entry/exit log markers for the script and every function.
  • Tee log capture: all output is mirrored to a temp file; when $LOGFILE is set, the capture is flushed to it on exit via the __cleanup__ trap.
  • $DEBUG flag: set -x is enabled when DEBUG=true is passed.
  • Env-var fallback: when the script is called with no arguments, each parameter is read from the corresponding environment variable instead.

Parameter types

Type Shell representation Argparse Env-var fallback
string VAR="" --name) shift; VAR="$1"; shift;; ${VAR+defined} one-liner
integer VAR="" Same as string (validation handles numeric check) Same as string
boolean VAR="" --flag) shift; VAR=true;; (no value consumed) ${VAR+defined} one-liner
array VAR=() Reads all following non--- words into VAR IFS="<delim>" read -ra VAR

Variable names are derived from parameter names: hyphens become underscores, and for script-level parameters (global scope) names are uppercased. Function-local parameters stay lowercase.


Argument parsing

Every script or function with parameters gets a while [[ $# -gt 0 ]]; do … done loop. Unknown flags emit ⛔ Unknown option: '<flag>' to stderr and exit 1. Positional (non-flag) arguments emit ⛔ Unexpected argument: '<arg>' and exit 1.

Required vs optional parameters

See the parameter definition table for the full matrix. The generated check for a required parameter:

[ -z "${OUTPUT_DIR-}" ] && { echo "⛔ Missing required argument 'OUTPUT_DIR'." >&2; exit 1; }

For an optional parameter with a default value:

[ -z "${COUNT-}" ] && { echo "ℹ️ Argument 'COUNT' set to default value '1'." >&2; COUNT="1"; }

Short flag aliases

Set short: "o" to generate a -o arm alongside --output-dir:

--output-dir|-o) shift; OUTPUT_DIR="$1"; ...;;

Help / usage output

When any parameter in a parameter set carries a non-empty description, shellscriptor:

  1. Generates a __usage__() function that prints a summary table to stderr and exits with code 0.
  2. Adds a --help|-h) arm to the argparse loop that calls __usage__.

For script-level parameters __usage__ is defined at the top of the script (before all other functions). For function parameters it is defined at the start of the function body, before the argparse loop.

Example output:

Usage:
  -o, --output-dir (string): Directory to write results into

Environment variable fallback (shell_exec)

When a shell_exec script is invoked with no arguments it reads parameters from environment variables. Variable names mirror the script-level variable name (uppercase, hyphens → underscores). The env-var block only imports a variable when it is already set in the environment (${VAR+defined} test), so unset variables are not silently turned into empty strings.

For array parameters the env var is split on array_delimiter using IFS + read -ra.


Validation

Validation checks are applied after argument parsing, in this order:

  1. Missing-arg / default assignment
  2. Enum membership
  3. Path existence
  4. Integer range
  5. Regex pattern
  6. Custom lines

For array parameters, checks 2–5 are run per element inside a for elem in "${VAR[@]}"; do … done loop.

Enum (allowed values)

validation:
  enum: [read, write, delete]

Generated shell:

case "${MODE}" in
  "read"|"write"|"delete");;
  *) echo "⛔ Invalid value for argument '--MODE': '${MODE}'" >&2; exit 1;;
esac

Path existence

validation:
  path_existence:
    must_exist: true
    type: file

Generated shell (must_exist: true, type: file):

[ ! -f "${INPUT_FILE}" ] && { echo "⛔ File argument to parameter 'INPUT_FILE' not found: '${INPUT_FILE}'" >&2; exit 1; }

Available type values: "file" (-f), "dir" (-d), "symlink" (-L), "exec" (-x), null (uses -e, accepts any entry).

must_exist: false inverts the check — the path must not exist.

Integer range

validation:
  integer_range:
    min: 1
    max: 100

Generates checks for both bounds independently (omit either for an open-ended range). min and max must satisfy min <= max when both are given.

Regex pattern

validation:
  regex: "^[a-z][a-z0-9_-]{2,31}$"

Uses [[ "$VAR" =~ <pattern> ]]:

[[ ! "${SLUG}" =~ ^[a-z][a-z0-9_-]{2,31}$ ]] && { echo "⛔ ..." >&2; exit 1; }

Custom shell lines

validation:
  custom:
    - '[ -w "${OUTPUT_DIR}" ] || { echo "⛔ Directory is not writable." >&2; exit 1; }'

Custom lines are appended verbatim after all other validation checks.


Return values / structured output

return (or return_ in Python) accepts a list of ReturnDef entries. shellscriptor encodes return values so that callers can unpack them unambiguously:

Situation Encoding Caller idiom
Single scalar echo "$VAR" val=$(fn)
Single array printf '%s\0' "${VAR[@]}" mapfile -d '' -t arr < <(fn)
Multiple values (scalars and/or arrays) Each value followed by \0; array elements delimited by \x1F internally IFS= read -r -d $'\0' per value

In shell_exec mode each return value also emits a write-log line.


Embedded functions

Functions defined under function are emitted before the script body. Each function is generated by create_function() and follows the same rules as the top-level script: parameters get an argparse loop, validation checks are emitted, and in shell_exec mode entry/exit log markers are added.

function:
  greet:
    parameter:
      person:
        type: string
        required: true
        description: Name to greet
    body: 'echo "Hello, ${person}!"'

Generated:

greet() {
  echo "↪️ Function entry: greet" >&2
  __usage__() {
    echo "Usage:" >&2
    echo "  --person (string): Name to greet" >&2
    exit 0
  }
  local person=""
  while [[ $# -gt 0 ]]; do
    case $1 in
      --person) shift; person="$1"; ...; shift;;
      --help|-h) __usage__;;
      ...
    esac
  done
  [ -z "${person-}" ] && { echo "⛔ Missing required argument 'person'." >&2; exit 1; }
  echo "Hello, ${person}!"
  echo "↩️ Function exit: greet" >&2
}

Global function pool

create_script() accepts an optional global_functions argument — a dict of FunctionDef-compatible dicts keyed by function name. Only functions listed in import (or import_) are pulled in:

GLOBALS = {
    "check_root": {"body": 'if [ "$(id -u)" -ne 0 ]; then echo "must be root" >&2; exit 1; fi'},
}

script = create_script(
    name="setup",
    script_type="shell_exec",
    data={"import": ["check_root"], "body": "check_root"},
    global_functions=GLOBALS,
)

Logging

The log() function and related helpers generate consistent echo … >&2 statements.

Function Output
log(msg) echo "<msg>" >&2
log(msg, "info") echo "ℹ️ <msg>" >&2
log(msg, "warn") echo "⚠️ <msg>" >&2
log(msg, "error") echo "❌ <msg>" >&2; return 1
log(msg, "critical") echo "⛔ <msg>" >&2; exit 1
log_endpoint("fn", "function", "entry") echo "↪️ Function entry: fn" >&2
log_arg_read("param", "VAR") echo "📩 Read argument 'param': '${VAR}'" >&2
log_arg_write("result", "RESULT") echo "📤 Write output 'result': '${RESULT}'" >&2

Body sections

The body field is flexible. All three forms are equivalent:

# Plain string
body = 'echo hello\necho world'

# List of strings
body = ["echo hello", "echo world"]

# List of section dicts (YAML-friendly; only "content" is used)
body = [
    {"summary": "Greet", "content": "echo hello"},
    {"content": "echo world"},
]

Comments (#) and blank lines are stripped by default by sanitize_code().


CLI usage

shellscriptor DEFINITION_FILE [--output PATH] [--type {shell_src,shell_exec}] [--name NAME]
Option Description
DEFINITION_FILE Path to a YAML or JSON definition file.
--output, -o Write the generated script to this path (default: stdout).
--type, -t Override the script type (shell_src or shell_exec). Required when the file does not contain a top-level type key.
--name, -n Script name used in log messages. Defaults to the file stem.

Definition file formats

Wrapper format (recommended for YAML):

name: deploy
type: shell_exec
script:
  interpreter: usr/bin/env bash
  flags: "-euo pipefail"
  parameter:
    env:
      type: string
      required: true
      description: Target environment
  body: echo "Deploying to $ENV"

Bare format (must pass --type and optionally --name on the CLI):

{
  "interpreter": "usr/bin/env bash",
  "parameter": {
    "env": {"type": "string", "required": true}
  },
  "body": "echo \"Deploying to $ENV\""
}

Python API

create_script(name, data, script_type, global_functions=None) -> str

Generate a complete shell script string.

from shellscriptor import create_script

code = create_script(
    name="deploy",
    data={...},           # dict or ScriptDef
    script_type="shell_exec",
    global_functions={},  # optional pool
)

create_function(name, data, script_type) -> list[str]

Generate lines for a single shell function definition.

from shellscriptor import create_function

lines = create_function("greet", {"body": ['echo "hi"']}, script_type="shell_src")

Low-level generators

These are exported from shellscriptor and can be combined freely to build custom script snippets:

Function Returns
create_validation_block(parameters, *, local, script_type) Validation lines for all parameters
validate_variable(var_name, var_type, default, required, validations, script_type) Validation lines for one variable
validate_missing_arg(var_name, var_type, default, script_type) One-liner: required-or-default check
validate_enum(var_name, enum) case block for allowed values
validate_path_existence(var_name, must_exist, path_type) [ -f/-d/… ] check
validate_integer_range(var_name, min_val, max_val) Numeric bound checks
validate_regex(var_name, pattern) [[ =~ ]] check
create_output(returns, script_type) Output/return encoding lines
log(msg, level, code, indent_level) echo … >&2 command string
log_arg_read(param_name, var_name) Read-log string
log_arg_write(param_name, var_name) Write-log string
log_endpoint(name, typ, stage) Entry/exit-log string
indent(code, level) Indent a string or list of lines
sanitize_code(code, ...) Strip comments, blank lines, trailing whitespace

Examples

Required string parameter with short flag

create_script(
    name="build",
    script_type="shell_exec",
    data={
        "interpreter": "usr/bin/env bash",
        "flags": "-euo pipefail",
        "parameter": {
            "output-dir": {
                "type": "string",
                "required": True,
                "description": "Directory to write build artefacts into",
                "short": "o",
            },
        },
        "body": 'mkdir -p "$OUTPUT_DIR"',
    },
)

All four parameter types

parameter:
  name:
    type: string
    required: true
    description: Name to greet
  count:
    type: integer
    default: "1"
    description: How many times to greet
  verbose:
    type: boolean
    description: Enable verbose output
  tags:
    type: array
    description: Additional tags
    array_delimiter: ","

Enum + path validation

parameter:
  action:
    type: string
    required: true
    validation:
      enum: [copy, move, delete]
  source:
    type: string
    required: true
    validation:
      path_existence:
        must_exist: true
        type: file

Integer range

parameter:
  workers:
    type: integer
    default: "4"
    validation:
      integer_range:
        min: 1
        max: 32

Regex pattern

parameter:
  slug:
    type: string
    required: true
    validation:
      regex: "^[a-z][a-z0-9_-]{2,31}$"

Return values

data = {
    "body": 'result="hello"',
    "return": [{"name": "result", "variable": "result", "type": "string"}],
}

Caller:

output=$(my_function --arg value)

Genuinely optional parameter (no default, no required check)

parameter:
  comment:
    type: string
    required: false   # variable may remain empty; no exit-1 check emitted

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shellscriptor-0.1.0.tar.gz (29.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shellscriptor-0.1.0-py3-none-any.whl (26.5 kB view details)

Uploaded Python 3

File details

Details for the file shellscriptor-0.1.0.tar.gz.

File metadata

  • Download URL: shellscriptor-0.1.0.tar.gz
  • Upload date:
  • Size: 29.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for shellscriptor-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c8f43a1eb5ecad31a25d84cd02cbd1e5623539624c27af3420c1e522259da6d3
MD5 b937fa3e565bb87a6547af850a76059a
BLAKE2b-256 3f86c598233c491592dd287f47f5e7e76d0edfe8126adc8475255db9f4038e5b

See more details on using hashes here.

File details

Details for the file shellscriptor-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: shellscriptor-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for shellscriptor-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b35721a9af8fcac9227b4dc0c33591adda383205555ac33c6a08b3f493f04fd5
MD5 759441f820fd632e359a9985164f1b01
BLAKE2b-256 724ffbe72230ef3b23d14960cabe4149e4eb3e01806eabf6c99f604124c70436

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page