From f2d6a21f9cb971c498284e28bb0de952dcbb94c3 Mon Sep 17 00:00:00 2001 From: AnthonyAxenov Date: Tue, 11 Jan 2022 08:49:47 +0800 Subject: [PATCH] args.sh --- shell/args.sh | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 shell/args.sh diff --git a/shell/args.sh b/shell/args.sh new file mode 100644 index 0000000..27a0eee --- /dev/null +++ b/shell/args.sh @@ -0,0 +1,286 @@ +#!/bin/bash +# https://blog.anthonyaxenov.ru/bash-args/ +# https://gist.github.com/anthonyaxenov/d53c4385b7d1466e0affeb56388b1005 +######################################################################### +# # +# Argument parser for bash scripts # +# # +# Author: Anthony Axenov (Антон Аксенов) # +# Version: 1.5 # +# License: MIT # +# # +######################################################################### +# # +# With 'getopt' you cannot combine different # +# arguments for different nested functions. # +# # +# 'getopts' does not support long arguments with # +# values (like '--foo=bar'). # +# # +# These functions supports different arguments and # +# their combinations: # +# -a -b -c # +# -a avalue -b bvalue -c cvalue # +# -cab bvalue # +# --arg # +# --arg=value -ab -c cvalue --foo # +# # +# Tested in Ubuntu 20.04.2 LTS in: # +# bash 5.0.17(1)-release (x86_64-pc-linux-gnu) # +# zsh 5.8 (x86_64-ubuntu-linux-gnu) # +# # +######################################################################### +# # +# Version history: # +# v1.0 - initial # +# v1.1 - arg(): improved skipping uninteresting args # +# - arg(): check next arg to be valid value # +# v1.2 - removed all 'return' statements # +# - arg(): error message corrected # +# - new examples # +# v1.3 - argl(): improved flag check # +# - some text corrections # +# v1.4 - new function argn() # +# - some text corrections # +# v1.5 - arg(), grep_match(): fixed searching for -e argument # +# - grep_match(): redirect output into /dev/null # +# # +######################################################################### + +#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 -qP "$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() { + local need=${1:0:1} # argument to find (only first letter) + [ $need ] || { + echo "Argument is not specified!" >&2 + exit 1 + } + local isflag=$2 || 0 # should we find the value or just the presence of the $need? + local retvar=$3 || 0 # var to return value into (if 0 then value will be echo'ed in stdout) + local args=(${MAIN_ARGS[0]}) # args we need are stored in 1st element of MAIN_ARGS + for ((idx=0; idx<${#args[@]}; ++idx)) do # going through args + local arg=${args[$idx]} # current argument + # skip $arg if it starts with '--', letter or digit + grep_match "$arg" "^(\w{1}|-{2})" && continue + # clear $arg from special and duplicate characters + # e.g. 'fas-)dfs' will become 'fasd' + local chars="$(printf "%s" "${arg}" | tr -s [${arg}] | tr -d "[:punct:][:blank:]")" + # now we can check if $need is one of $chars + if grep_match "-$need" "^-[$chars]$"; then # if it is + if [[ $isflag = 1 ]]; then # and we expect it as a flag + # then return '1' back into $3 (if exists) or echo in stdout + [ $retvar ] && eval "$retvar='1'" || echo "1" + else # but if $arg is not a flag + # then get next argument as value of current one + local value="${args[$idx+1]}" + # check if it is valid value + if grep_match "$value" "^\w+$"; then + # and return it back back into $3 (if exists) or echo in stdout + [ $retvar ] && eval "$retvar='$value'" || echo "$value" + break + else # otherwise throw error message into stderr (just in case) + echo "Argument '$arg' must have a correct value!" >&2 + break + fi + fi + fi + done +} + +#purpose Find long argument or its value +#argument $1 - argument (without leading dashes) +#argument $2 - is it flag? 1 if is, otherwise 0 or nothing +#argument $3 - 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() { + local need=$1 # argument to find + [ $need ] || { + echo "Argument is not specified!" >&2 + exit 1 + } + local isflag=$2 || 0 # should we find the value or just the presence of the $need? + local retvar=$3 || 0 # var to return value into (if 0 then value will be echo'ed in stdout) + local args=(${MAIN_ARGS[0]}) # args we need are stored in 1st element of MAIN_ARGS + for ((idx=0; idx<${#args[@]}; ++idx)) do + local arg=${args[$idx]} # current argument + # if we expect $arg as a flag + if [[ $isflag = 1 ]]; then + # and if $arg has correct format (like '--flag') + if grep_match "$arg" "^--$need"; then + # then return '1' back into $3 (if exists) or echo in stdout + [ ! $retvar = 0 ] && eval "$retvar=1" || echo "1" + break + fi + else # but if $arg is not a flag + # check if $arg has correct format (like '--foo=bar') + if grep_match "$arg" "^--$need=.+$"; then # if it is + # then return part from '=' to arg's end as value back into $3 (if exists) or echo in stdout + [ ! $retvar = 0 ] && eval "$retvar=${arg#*=}" || echo "${arg#*=}" + break + fi + fi + done +} + +#purpose Get argument by its index +#argument $1 - (number) arg index +#argument $2 - (string) variable to return arg's name into +# (if not specified then it will be echo'ed in stdout) +#returns (string) arg name or nothing +#usage To get arg into var: argn 1 myvar or arg=$(argn 1) +#usage To echo in stdout: argn 1 +argn() { + local idx=$1 # argument index + [ $idx ] || { + error "Argument index is not specified!" + exit 1 + } + local retvar=$2 || 0 # var to return value into (if 0 then value will be echo'ed in stdout) + local args=(${MAIN_ARGS[0]}) # args we need are stored in 1st element of MAIN_ARGS + local arg=${args[$idx]} # current argument + if [ $arg ]; then + [ ! $retvar = 0 ] && eval "$retvar=$arg" || echo "$arg" + fi +} + +# Keep in mind: +# 1. Short arguments can be specified contiguously or separately +# and their order does not matter, but before each of them +# (or the first of them) one leading dash must be specified. +# Valid combinations: '-a -b -c', '-cba', '-b -ac' +# 2. Short arguments can have values and if are - value must go +# next to argument itself. +# Valid combinations: '-ab avalue', '-ba avalue', '-a avalue -b' +# 3. Long arguments cannot be combined like short ones and each +# of them must be specified separately with two leading dashes. +# Valid combinations: '--foo --bar', '--bar --foo' +# 4. Long arguments can have a value which must be specified after '='. +# Valid combinations: '--foo=value --bar', '--bar --foo=value' +# 5. Values cannot contain spaces even in quotes both for short and +# long args, otherwise first word will return as value. +# 6. You can use arg() or argl() to check presence of any arg, no matter +# if it has value or not. + +### USAGE ### +# This is simple examples which you can play around with. + +# first we must save the original arguments passed +# to the script when it was called: +MAIN_ARGS=$@ + +echo -e "\n1. Short args (vars):" +arg a 1 a # -a +arg v 0 v # -v v_value +arg c 1 c # -c +arg z 1 z # -z (not exists) +echo "1.1 a=$a" +echo "1.2 v=$v" +echo "1.3 c=$c" +echo "1.4 z=$z" + +echo -e "\n2. Short args (echo):" +echo "2.1 a=$(arg a 1)" +echo "2.2 v=$(arg v 0)" +echo "2.3 c=$(arg c 1)" +echo "2.4 z=$(arg z 1)" + +echo -e "\n3. Long args (vars):" +argl flag 1 flag # --flag +argl param1 0 param1 # --param1=test +argl param2 0 param2 # --param2=password +argl bar 1 bar # --bar (not exists) +echo "3.1 flag=$flag" +echo "3.2 param1=$param1" +echo "3.3 param2=$param2" +echo "3.4 bar=$bar" + +echo -e "\n4. Long args (echo):" +echo "4.1 flag=$(argl flag 1)" +echo "4.2 param1=$(argl param1 0)" +echo "4.3 param2=$(argl param2 0)" +echo "4.4 bar=$(argl bar 1)" + +echo -e "\n5. Args by index:" +argn 1 first +echo "5.1 arg[1]=$first" +echo "5.2 arg[3]=$(argn 3)" + +# Well, now we will try to get global args inside different functions + +food() { + echo -e "\n=== food() ===" + arg f 0 food + argl 'food' 0 food + [ $food ] && echo "Om nom nom! $food is very tasty" || echo "Uh oh" >&2 +} + +hello() { + echo -e "\n=== hello() ===" + arg n 0 name + argl name 0 name + [ $name ] && echo "Hi, $name! How u r doin?" || echo "Hello, stranger..." >&2 +} + +hello +food + +### OUTPUT ### + +# Command to run: +# bash args.sh -va asdf --flag --param1=paramvalue1 -c --param2="somevalue2 sdf" --name="John" -f Seafood + +# 1. Short args (vars): +# 1.1 a=1 +# 1.2 v=v_value +# 1.3 c=1 +# 1.4 z= +# +# 2. Short args (echo): +# 2.1 a=1 +# 2.2 v=v_value +# 2.3 c=1 +# 2.4 z= +# +# 3. Long args (vars): +# 3.1 longflag=1 +# 3.2 param1=test +# 3.3 param2=password +# 3.4 barflag= +# +# 4. Long args (echo): +# 4.1 longflag=1 +# 4.2 param1=test +# 4.3 param2=password +# 4.4 barflag= +# +# 5. Args by index: +# 5.1 arg[1]=asdf +# 5.2 arg[3]=--param1=paramvalue1 +# +# === hello() === +# Hi, John! How u r doin? +# +# === food() === +# Om nom nom! Seafood is very tasty