You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
6.5 KiB

#!/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