#!/bin/bash # ============================================================================= # Bash CLI Argument Parser — Greedy Grouping, Flag-Agnostic # ============================================================================= # # Overview: # Parses command-line arguments into key/value groups. # Every flag (long or short) collects values until the next flag. # You do NOT need to define which flags expect values. # Short flags can be grouped (-abc) and are treated as booleans. # # Supported input formats: # --key value1 value2 → key = [value1, value2] # --key=value → key = value # -k value → k = value # -k=value → k = value # -abc → a=true, b=true, c=true # # Not supported: # -kvalue → this is parsed as combined short flags (k, v, a…) # Global positionals not linked to any flag (can be added separately if needed) # # Example usage: # ./arg_analyse.sh --mode fast --files input1.txt input2.txt -vx --output=result.txt # # Output: # --mode: # [0]: fast # --files: # [0]: input1.txt # [1]: input2.txt # --v: # [boolean flag] # --x: # [boolean flag] # --output: # [0]: result.txt # # ============================================================================= # STORAGE: key = flag name, value = string or boolean declare -A FLAG_GROUPS # ----------------------------------------------------------------------------- # FUNCTION: parse_grouped_args # Description: # Parses input arguments and fills the FLAG_GROUPS associative array. # ----------------------------------------------------------------------------- parse_grouped_args() { local current_flag="" local -a current_values=() while [[ $# -gt 0 ]]; do local token="$1" # ------------------------------------------------------------------------- # Case 1: --key=value or -k=value (explicit assignment form) # This handles both long and short flags passed as --flag=value or -f=value # ------------------------------------------------------------------------- # Breakdown of regex: # ^ → start of the string # --? → match one or two dashes (e.g. "-" or "--") # ([^=]+) → capture group 1: one or more characters that are NOT "=" # → this becomes the flag name (e.g. "output") # = → literal equals sign # (.*) → capture group 2: any number of characters (can be empty) # → this becomes the first value for the flag # # Examples: # --file=abc.txt → key = "file", val = "abc.txt" # -x=1 → key = "x", val = "1" if [[ "$token" =~ ^--?([^=]+)=(.*)$ ]]; then # Before processing this --flag=value, we must flush any previous group if [[ -n "$current_flag" ]]; then if [[ ${#current_values[@]} -eq 0 ]]; then FLAG_GROUPS["$current_flag"]="true" else FLAG_GROUPS["$current_flag"]="${current_values[*]}" fi fi # Capture the key and value from the current --key=value or -k=value local key="${BASH_REMATCH[1]}" # group 1 from regex (flag name) local val="${BASH_REMATCH[2]}" # group 2 from regex (value after =) # Set current flag context to continue collecting values if any follow current_flag="$key" current_values=("$val") # start a new value group with first value from assignment # ------------------------------------------------------------------------- # Case 2: --long flag (starts a new group) # ------------------------------------------------------------------------- elif [[ "$token" == --* ]]; then # Save previous flag (if any) if [[ -n "$current_flag" ]]; then if [[ ${#current_values[@]} -eq 0 ]]; then FLAG_GROUPS["$current_flag"]="true" else FLAG_GROUPS["$current_flag"]="${current_values[*]}" fi fi current_flag="${token#--}" # Remove leading "--" current_values=() # ------------------------------------------------------------------------- # Case 3: -short or -grouped flags (-x or -abc) # ------------------------------------------------------------------------- elif [[ "$token" == -* && "$token" != "--" ]]; then # Save previous group if [[ -n "$current_flag" ]]; then if [[ ${#current_values[@]} -eq 0 ]]; then FLAG_GROUPS["$current_flag"]="true" else FLAG_GROUPS["$current_flag"]="${current_values[*]}" fi current_flag="" current_values=() fi local chars="${token#-}" if [[ "${#chars}" -gt 1 ]]; then # -abc → treat each letter as a boolean flag for ((i = 0; i < ${#chars}; i++)); do FLAG_GROUPS["${chars:i:1}"]="true" done else # Single short flag (-x) may collect values current_flag="$chars" current_values=() fi # ------------------------------------------------------------------------- # Case 4: Value (non-flag) # ------------------------------------------------------------------------- else current_values+=("$token") fi shift done # Final flush (for last group) if [[ -n "$current_flag" ]]; then if [[ ${#current_values[@]} -eq 0 ]]; then FLAG_GROUPS["$current_flag"]="true" else FLAG_GROUPS["$current_flag"]="${current_values[*]}" fi fi } # ----------------------------------------------------------------------------- # FUNCTION: print_grouped_args # Description: # Pretty-prints the FLAG_GROUPS key/value structure. # ----------------------------------------------------------------------------- print_grouped_args() { echo "Parsed Arguments:" for key in "${!FLAG_GROUPS[@]}"; do IFS=' ' read -r -a values <<<"${FLAG_GROUPS[$key]}" echo "--$key:" if [[ "${FLAG_GROUPS[$key]}" == "true" ]]; then echo " [boolean flag]" elif [[ ${#values[@]} -eq 0 ]]; then echo " [empty]" else for i in "${!values[@]}"; do echo " [$i]: ${values[$i]}" done fi done } # ----------------------------------------------------------------------------- # TEST CASE (for development) # Comment this out to use real CLI args # ----------------------------------------------------------------------------- #set -- --name Kerem yollu siki --age=33 --flag -vx --output result.txt -t alpha beta # Run parser and print results #parse_grouped_args "$@" #print_grouped_args