#!/usr/bin/env bash # Argument parser for bash scripts # # Author: Anthony Axenov (Антон Аксенов) # Version: 1.6 # License: MIT # Description: https://git.axenov.dev/anthony/shell/src/branch/master/helpers/arg-parser #purpose Little helper to check if string matches PCRE #argument $1 - some string #argument $2 - regex #exitcode 0 - string valid #exitcode 1 - string is not valid grep_match() { printf "%s" "$1" | grep -qE "$2" >/dev/null } #purpose Find short argument or its value #argument $1 - (string) argument (without leading dashes; only first letter will be processed) #argument $2 - (number) is it flag? 1 if is, otherwise 0 or nothing #argument $3 - (string) variable to return value into # (if not specified then it will be echo'ed in stdout) #returns (string) 1 (if $2 == 1), value (if correct and if $2 != 1) or nothing #usage To get value into var: arg v 0 myvar or myvalue=$(arg 'v') #usage To find flag into var: arg f 1 myvar or flag=$(arg 'f') #usage To echo value: arg v #usage To echo 1 if flag exists: arg f arg() { [ "$1" ] || { echo "Argument name is not specified!" >&2 && exit 1; } local arg_name="${1:0:1}" # first character of argument name to find local is_flag="$2" || 0 # 1 if we need just find a flag, 0 to get a value local var_name="$3" || 0 # variable name to return value into or 0 to echo it in stdout local value= # initialize empty value to check if we found one later local arg_found=0 # marker of found argument for idx in "${!__RAW_ARGS__[@]}"; do # going through all args local arg_search=${__RAW_ARGS__[idx]} # get current argument # skip $arg_search if it starts with '--' or letter grep_match "$arg_search" "^(\w|--)" && continue # clear $arg_search from special and duplicate characters, e.g. 'fas-)dfs' will become 'fasd' local arg_chars="$(printf "%s" "$arg_search" \ | tr -s "[$arg_search]" 2>&1 >/dev/null \ | tr -d "[:punct:][:blank:]" 2>&1 >/dev/null)" # if $arg_name is not one of $arg_chars the skip it grep_match "-$arg_name" "^-[$arg_chars]$" || continue arg_found=1 # then return '1'|'0' back into $value if we need flag or next arg value otherwise [ "$is_flag" = 1 ] && value=1 || value="${__RAW_ARGS__[idx+1]}" break done [ "$is_flag" = 1 ] && [ -z "$value" ] && value=0; # if value we found is empty or looks like another argument then exit with error message if [ "$arg_found" = 1 ] && ! grep_match "$value" "^[[:graph:]]+$" || grep_match "$value" "^--?\w+$"; then echo "ERROR: Argument '-$arg_name' must have correct value!" >&2 && exit 1 fi # return '$value' back into $var_name (if exists) or echo in stdout [ "$var_name" ] && eval "$var_name='$value'" || echo "$value" } #purpose Find long argument or its value #argument $1 - argument (without leading dashes) #argument $2 - (number) is it flag? 1 if is, otherwise 0 or nothing #argument $3 - (string) variable to return value into # (if not specified then it will be echo'ed in stdout) #returns (string) 1 (if $2 == 1), value (if correct and if $2 != 1) or nothing #usage To get value into var: arg v 0 myvar or myvalue=$(arg 'v') #usage To find flag into var: arg f 1 myvar or flag=$(arg 'f') #usage To echo value: arg v #usage To echo 1 if flag exists: arg f argl() { [ "$1" ] || { echo "Argument name is not specified!" >&2 && exit 1; } local arg_name="$1" # argument name to find local is_flag="$2" || 0 # 1 if we need just find a flag, 0 to get a value local var_name="$3" || 0 # variable name to return value into or 0 to echo it in stdout local value= # initialize empty value to check if we found one later local arg_found=0 # marker of found argument for idx in "${!__RAW_ARGS__[@]}"; do # going through all args local arg_search="${__RAW_ARGS__[idx]}" # get current argument if [ "$arg_search" = "--$arg_name" ]; then # if current arg begins with two dashes # then return '1' back into $value if we need flag or next arg value otherwise [ "$is_flag" = 1 ] && value=1 || value="${__RAW_ARGS__[idx+1]}" break # stop the loop elif grep_match "$arg_search" "^--$arg_name=.+$"; then # check if $arg like '--foo=bar' # then return '1' back into $value if we need flag or part from '=' to arg's end as value otherwise [ "$is_flag" = 1 ] && value=1 || value="${arg_search#*=}" break # stop the loop fi done [ "$is_flag" = 1 ] && [ -z "$value" ] && value=0; # if value we found is empty or looks like another argument then exit with error message if [ "$arg_found" = 1 ] && ! grep_match "$value" "^[[:graph:]]+$" || grep_match "$value" "^--?\w+$"; then echo "ERROR: Argument '--$arg_name' must have correct value!" >&2 && exit 1; fi # return '$value' back into $var_name (if exists) or echo in stdout [ "$var_name" ] && eval "$var_name='$value'" || echo "$value" } ################################ # This is simple examples which you can play around with. # 1. uncomment code below # 2. call thi sscript to see what happens: # /args.sh -abcd --flag1 --flag2 -e evalue -f fvalue --arg1=value1 --arg2 value2 # __RAW_ARGS__=("$@") # arg a 1 flag_a # echo "Flag -a has value '$flag_a'" # echo "Flag -a has value '$(arg a 1)'" # arg b 1 flag_b # echo "Flag -b has value '$flag_b'" # echo "Flag -b has value '$(arg b 1)'" # arg c 1 flag_c # echo "Flag -c has value '$flag_c'" # echo "Flag -c has value '$(arg c 1)'" # arg d 1 flag_d # echo "Flag -d has value '$flag_d'" # echo "Flag -d has value '$(arg d 1)'" # argl flag1 1 flag_1 # echo "Flag --flag1 has value '$flag_1'" # echo "Flag --flag1 has value '$(argl flag1 1)'" # argl flag2 1 flag_2 # echo "Flag --flag2 has value '$flag_2'" # echo "Flag --flag2 has value '$(argl flag2 1)'" # arg e 0 arg_e # echo "Arg -e has value '$arg_e'" # echo "Arg -e has value '$(arg e 0)'" # arg f 0 arg_f # echo "Arg -f has value '$arg_f'" # echo "Arg -f has value '$(arg f 0)'" # argl arg1 0 arg_1 # echo "Arg --arg1 has value '$arg_1'" # echo "Arg --arg1 has value '$(argl arg1 0)'" # argl arg2 0 arg_2 # echo "Arg --arg2 has value '$arg_2'" # echo "Arg --arg2 has value '$(argl arg2 0)'"