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
- ShellScriptor
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/caseargument parsing are emitted. - No logging boilerplate, no
trap, notee. - 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
$LOGFILEis set, the capture is flushed to it on exit via the__cleanup__trap. $DEBUGflag:set -xis enabled whenDEBUG=trueis 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:
- Generates a
__usage__()function that prints a summary table to stderr and exits with code 0. - 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:
- Missing-arg / default assignment
- Enum membership
- Path existence
- Integer range
- Regex pattern
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8f43a1eb5ecad31a25d84cd02cbd1e5623539624c27af3420c1e522259da6d3
|
|
| MD5 |
b937fa3e565bb87a6547af850a76059a
|
|
| BLAKE2b-256 |
3f86c598233c491592dd287f47f5e7e76d0edfe8126adc8475255db9f4038e5b
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b35721a9af8fcac9227b4dc0c33591adda383205555ac33c6a08b3f493f04fd5
|
|
| MD5 |
759441f820fd632e359a9985164f1b01
|
|
| BLAKE2b-256 |
724ffbe72230ef3b23d14960cabe4149e4eb3e01806eabf6c99f604124c70436
|