Skip to content

args.sh

Eugene Lazutkin edited this page Mar 22, 2026 · 3 revisions

args.sh

CLI option and command parsing via getopt. Defines your program's options, commands, and their arguments, then parses "$@" into structured data.

Bash requirement: 4.0+ External requirement: getopt (enhanced version, passes getopt --test) Dependencies: none Include guard: args::program

Usage

source "/path/to/options.bash/args.sh"

args::program "my-tool" "1.0.0" "A useful tool"
args::option "-v, --version" "Show version"
args::option "-h, --help" "Show help"
args::option "--output, -o" "Output file" "file"
args::option "-n" "Dry run"
args::option "build" "Build the project"
args::option "test" "Run tests" "suite"

args::parse "$@"

Setup functions

args::program NAME VERSION [DESCRIPTION] [URL]

Set program metadata. Called once before defining options.

args::program "deploy" "2.0.0" "Deploy to servers" "https://example.com/deploy"

args::option NAMES DESCRIPTION [ARG_NAME] [OPTIONAL]

Register an option or command.

NAMES — comma and/or space-separated list of names:

  • Names starting with -- are long options.
  • Names starting with - (single character after dash) are short options.
  • Bare words (no leading dash) are commands.

DESCRIPTION — help text (may contain \n for multi-line).

ARG_NAME — if non-empty, the option/command takes an argument. The string is used as the argument's display name in help.

OPTIONAL — if non-empty, the argument is optional (only meaningful when ARG_NAME is set).

# Simple flag
args::option "-n, --dry-run" "Preview without executing"

# Option with required argument
args::option "--env, -e" "Target environment" "name"

# Option with optional argument
args::option "--color" "Color output" "when" optional

# Short option with required argument
args::option "-o" "Output file" "path"

# Command
args::option "build" "Build the project"

# Command with argument
args::option "deploy, d" "Deploy to server" "target"

# Multi-line description
args::option "--verbose" "Enable verbose output\nShows detailed progress"

args::immediate OPTIONS...

Add options to the immediate-option list. Immediate options trigger their handler function right after parsing and before returning control.

Default immediate options: -h, --help, -v, --version, --bash-completion.

args::immediate "--license"
# Now --license will trigger args::option::license if defined

args::on_options FUNCTION

Register a function to be called after options are extracted but before immediate options are dispatched. Hooks run in registration order.

my_pre_dispatch() {
  echo "Options parsed"
}
args::on_options my_pre_dispatch

Used by args-completion.sh for auto-registration of completion scripts. Since immediate options (-h, --bash-completion) call exit, this is the only hook that fires in those cases.

args::on_parse FUNCTION

Register a function to be called after every successful args::parse. Hooks run in registration order, after all option processing and command validation. Does not fire when immediate options exit early.

my_post_parse() {
  echo "Parse complete: command=$args_command"
}
args::on_parse my_post_parse

args::try_help

Print a "try --help" hint. Used internally by error handlers. Checks for --help, then -h, then falls back to the program URL.

Parsing

args::parse "$@"

Parse command-line arguments. Must be called after all args::option registrations.

Internally:

  1. Builds getopt option strings from registered options.
  2. Calls getopt to normalize the arguments.
  3. Iterates parsed arguments, populating args_options.
  4. Calls registered args::on_options hooks.
  5. Handles immediate options (calls args::option::NAME functions, which typically exit).
  6. Validates commands if any were registered.
  7. Calls registered args::on_parse hooks.

Result globals

After args::parse, these globals contain the results:

args_options

Associative array keyed by the primary option name (first name in the NAMES list). The value is the option's argument, or empty string for flags.

args::parse "$@"

# Check if an option was provided
if [[ -v args_options["--output"] ]]; then
  echo "Output: ${args_options["--output"]}"
fi

# Shorthand via nameref
declare -n opt=args_options
if [[ -v opt["--dry-run"] ]]; then
  echo "Dry run mode"
fi

args_command

The matched command name (primary name). Empty if no command was matched.

case "$args_command" in
  build) do_build ;;
  test)  do_test ;;
esac

args_cleaned

Array of remaining positional arguments after options and commands are consumed.

# Iterate over remaining arguments
for arg in "${args_cleaned[@]}"; do
  echo "Arg: $arg"
done

# Check if any remain
if [[ "${#args_cleaned[@]}" -gt 0 ]]; then
  echo "Has positional arguments"
fi

Configuration globals

Set these after args::program and before args::parse:

Variable Default Description
args_program_required_command "yes" If "yes", error when no command is given (only when commands are defined)
args_program_help_style "grid" Help layout: "grid" (side-by-side) or "list" (stacked)
args_program_usage "" Custom usage text (overrides auto-generated)
args_program_header "" Text between description and usage section
args_program_footer "" Text at end of help (overrides URL-based footer)
args_program_required_command="no"   # commands are optional
args_program_help_style="list"       # description below option name

# Custom usage (use box::make for multi-line)
source box.sh
args_program_usage="$(box::make \
  "my-tool [options] build [target]" \
  "my-tool [options] test [suite]")"

Error hooks

Define these functions to customize error messages. If not defined, a default message is printed followed by args::try_help.

Hook Called when
args::error::getopt getopt fails to parse
args::error::unknown_option OPTION Unrecognized option
args::error::no_command Required command missing
args::error::unknown_command COMMAND Unrecognized command
args::error::invalid_short_option NAME Malformed short option in definition
args::error::invalid_long_option NAME Malformed long option in definition
args::error::unknown_command() {
  ansi::err "${RED}Unknown command: $1${RESET_ALL}"
  args::try_help
}

Complete example

#!/usr/bin/env bash
set -euo pipefail

LIB_DIR="$(dirname "$(realpath "$0")")/../lib/options.bash"
source "${LIB_DIR}/args.sh"
source "${LIB_DIR}/args-help.sh"
source "${LIB_DIR}/args-version.sh"

args::program "deploy" "2.0.0" "Deploy application to servers"
args::option "-v, --version" "Show version"
args::option "-h, --help" "Show help"
args::option "-n, --dry-run" "Preview without executing"
args::option "--env, -e" "Target environment" "name"
args::option "--timeout, -t" "Timeout in seconds" "secs"
args::option "start" "Start the deployment"
args::option "stop" "Stop the deployment"
args::option "status" "Check deployment status"

args::parse "$@"

declare -n opt=args_options
env="${opt["--env"]:-production}"
timeout="${opt["--timeout"]:-30}"

case "$args_command" in
  start)
    echo "Deploying to ${env} (timeout: ${timeout}s)..."
    if [[ -v opt["--dry-run"] ]]; then
      echo "(dry run — no changes made)"
    fi
    ;;
  stop)
    echo "Stopping deployment on ${env}..."
    ;;
  status)
    echo "Checking status on ${env}..."
    ;;
esac

Clone this wiki locally