Last active
December 4, 2025 14:25
-
-
Save colemar/1a2bd0f22413ef671572f83e80153c6b to your computer and use it in GitHub Desktop.
Named parameters processing for bash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This function might seem like an over-engineered approach to the problem of accepting named arguments, | |
| # but it ultimately proves to be a flexible, secure, simple-to-use, and reasonably efficient solution. | |
| # | |
| # Usage Protocol for the get_args helper function: | |
| # | |
| # A generic function func() (or main program) must adhere to the following contract: | |
| # 1. Define Parameter Specifications: The function must declare an associative array named 'params'. | |
| # This array serves two purposes: it acts as a whitelist of accepted parameter names and defines their default values. | |
| # If a parameter has '?' as default value it is deemed as required. Default value '?' is forbidden for optional parameters. | |
| # 2. Consume and shift out all initial fixed positional arguments. | |
| # 3. Invoke the Parser: The function must execute the helper within an eval statement using command substitution: | |
| # eval "$(get_args "$@")". Double quotes around the substitution are mandatory to prevent accidental side effects. | |
| # 4. Variable Instantiation: Upon execution of the eval line, readonly local variables corresponding to the keys in params | |
| # will be automatically initialized within the function's scope. | |
| # These variables will contain either the value passed by the caller or the default value defined in the array. | |
| # 5. The array params will be unaffected, since command substitution creates a copy for get_args(). | |
| # The array cannot be initially declared as readonly (-r), because the copy will be also readonly and get_args() | |
| # needs to manipulate it. | |
| # | |
| # Arguments of func() can be specified as: | |
| # - name=value (Standard form: sets variable 'name' to 'value') | |
| # - name (Flag form: equivalent to name=name, useful for boolean checks like [[ $name ]]) | |
| # | |
| # Error Handling: | |
| # - If unexpected keyword arguments are passed, get_args prints an error for each one, | |
| # completes parsing, then returns 1. The caller should handle this error (e.g., with || return 1). | |
| # - If a required keyword argument is missing, get_args prints an error message and terminates the script with exit 1. | |
| # This is a fatal programming error - the script cannot continue without required parameters. | |
| # | |
| # Canonical Example: | |
| # | |
| # func() { | |
| # local -A params=([tmout]=10 [file]=? [retr]= [verbose]=) | |
| # eval "$(get_args "$@")" || return 1 # Handle unexpected arguments | |
| # echo "Processing '$file' with timeout ${tmout}s (Attempt 1/$retr)..." | |
| # [[ $verbose ]] && date | |
| # } | |
| # | |
| # func file=input.txt # Processing 'input.txt' with timeout 10s (Attempt 1/)... | |
| # func file=input.txt verbose # Same as above + Wed Dec 3 01:41:28 AM CET 2025 | |
| # func retr=2 file=a.txt # Processing 'a.txt' with timeout 10s (Attempt 1/2)... | |
| # func 'file=w s.txt' # Processing 'w s.txt' with timeout 10s (Attempt 1/)... | |
| # func file='w s.txt' # Same as above. | |
| # func fi'le=w s.txt' # Same as above. | |
| # func file=a.txt retr=5 tmout=19 # Processing 'a.txt' with timeout 19s (Attempt 1/5)... | |
| # func file=a.txt retr=2 foo=1 # ERROR: func() got an unexpected keyword argument 'foo' # eval exit code is 1 | |
| # func retr=2 tmout=1 # FATAL ERROR: func() missing required keyword argument 'file' # script terminates | |
| # | |
| get_args() { | |
| local arg key buf= err= caller="${FUNCNAME[1]}()" | |
| local -i n=1 | |
| for arg; do | |
| key=${arg%%=*} | |
| if [[ -v params[$key] ]]; then | |
| buf+=" $key=\${$n#*=}" | |
| unset params[$key] | |
| else | |
| echo "ERROR: $caller got an unexpected keyword argument '$key'" >&2 | |
| err=1 | |
| fi | |
| ((n++)) | |
| done | |
| for key in "${!params[@]}"; do | |
| if [[ ${params[$key]} != \? ]]; then | |
| buf+=" $key=\${params[$key]}" | |
| else | |
| echo "FATAL ERROR: $caller missing required keyword argument '$key'" >&2 | |
| echo "exit 1"; return 1 | |
| fi | |
| done | |
| echo "${buf:+local -r}$buf${err:+$'\n'false}" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Minimalist argument parser helper. | |
| # Maps arguments of the form 'name=value' to readonly local variables prefixed with 'p_'. | |
| # | |
| # Usage Protocol: | |
| # 1. Consume and shift out all initial fixed positional arguments. | |
| # 2. Define default values for optional parameters as local variables with 'p_' prefix. | |
| # 3. Execute: eval "$(get_params "$@")" to parse and overwrite defaults. | |
| # | |
| # Argument Syntax: | |
| # - name=value -> Sets p_name to "value". | |
| # - name -> Sets p_name to "name" (Flag mode, equivalent to name=name). | |
| # | |
| # Constraints & Responsibilities: | |
| # 1. Caller: Must ensure passed argument keys are valid Bash identifiers (alphanumeric/underscore) | |
| # to avoid syntax errors during assignment. | |
| # 2. Function Implementation: Must NOT use local variables starting with 'p_' for internal logic, | |
| # as this namespace is reserved for the generated parameters. | |
| # | |
| # Example: | |
| # func() { | |
| # shift 1 # Remove fixed arguments if any | |
| # local p_timeout=10 p_verbose= # Define defaults (mutable) | |
| # eval "$(get_params "$@")" # Parse arguments, override defaults and lock variables (readonly) | |
| # | |
| # echo "Timeout: $p_timeout" | |
| # [[ $p_verbose ]] && echo "Verbose active" | |
| # } | |
| # func arg1 timeout=30 verbose | |
| # | |
| get_params() { | |
| local arg key buf= | |
| local -i n=1 | |
| for arg; do | |
| key=${arg%%=*} | |
| buf+=" p_$key=\${$n#*=}" | |
| ((n++)) | |
| done | |
| echo "${buf:+local -r}$buf" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment