args-parser refactor
This commit is contained in:
@@ -1,4 +1,84 @@
|
||||
# Argument parser for bash scripts
|
||||
# Argument parser for bash scripts v1.6
|
||||
|
||||
## Usage
|
||||
|
||||
```shell
|
||||
# 1. add these lines after shebang:
|
||||
|
||||
__RAW_ARGS__=("$@")
|
||||
source args.sh
|
||||
|
||||
# 2. read arguments as flags:
|
||||
|
||||
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)'"
|
||||
|
||||
# 3. and/or read arguments' values:
|
||||
|
||||
arg a 0 arg_a
|
||||
echo "Arg -a has value '$arg_a'"
|
||||
echo "Arg -a has value '$(arg a 0)'"
|
||||
|
||||
arg b 0 arg_b
|
||||
echo "Arg -b has value '$arg_b'"
|
||||
echo "Arg -b has value '$(arg b 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)'"
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
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 -azc "value of z"`
|
||||
|
||||
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. If arg value may contain space then value must be "double-quoted".
|
||||
|
||||
6. You can use arg() or argl() to check presence of any arg, no matter
|
||||
if it has value or not.
|
||||
|
||||
More info:
|
||||
* 🇷🇺 [axenov.dev/bash-args](https://axenov.dev/bash-args/)
|
||||
@@ -7,8 +87,8 @@ More info:
|
||||
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)
|
||||
bash 5.0.17(1)-release (x86_64-pc-linux-gnu) and later
|
||||
zsh 5.8 (x86_64-ubuntu-linux-gnu) and later
|
||||
```
|
||||
|
||||
## Version history
|
||||
@@ -26,4 +106,6 @@ 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
|
||||
v1.6 - removed useless argn()
|
||||
- arg() and argl() refactored and now support values with whitespaces
|
||||
```
|
||||
|
||||
318
helpers/arg-parser/args.sh
Normal file → Executable file
318
helpers/arg-parser/args.sh
Normal file → Executable file
@@ -1,33 +1,11 @@
|
||||
#!/bin/bash
|
||||
#########################################################################
|
||||
# #
|
||||
# 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) #
|
||||
# #
|
||||
#########################################################################
|
||||
#!/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
|
||||
@@ -49,46 +27,46 @@ grep_match() {
|
||||
#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)
|
||||
for ((idx=0; idx<${#__MAIN_ARGS__[@]}; ++idx)) do # going through args
|
||||
local arg=${__MAIN_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 $retvar (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="${__MAIN_ARGS__[$idx+1]}"
|
||||
# check if it is valid value
|
||||
if grep_match "$value" "^[[:graph:]]+$"; then
|
||||
# and return it back back into $retvar (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
|
||||
[ "$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] | tr -d "[:punct:][:blank:]")"
|
||||
|
||||
# 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 - is it flag? 1 if is, otherwise 0 or nothing
|
||||
#argument $3 - variable to return value into
|
||||
#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')
|
||||
@@ -96,171 +74,83 @@ arg() {
|
||||
#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)
|
||||
for ((idx=0; idx<${#__MAIN_ARGS__[@]}; ++idx)) do # going through args
|
||||
local arg=${__MAIN_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 $retvar (if exists) or echo in stdout
|
||||
[ "$retvar" = 0 ] && echo "1" || eval "$retvar=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 $retvar (if exists) or echo in stdout
|
||||
[ "$retvar" = 0 ] && echo "${arg#*=}" || eval "$retvar=${arg#*=}"
|
||||
break
|
||||
fi
|
||||
[ "$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
|
||||
}
|
||||
|
||||
#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"
|
||||
[ "$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"
|
||||
}
|
||||
|
||||
# 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.
|
||||
# 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
|
||||
|
||||
# first we must save the original arguments passed
|
||||
# to the script when it was called:
|
||||
__MAIN_ARGS__=($@)
|
||||
# __RAW_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"
|
||||
# arg a 1 flag_a
|
||||
# echo "Flag -a has value '$flag_a'"
|
||||
# echo "Flag -a has value '$(arg a 1)'"
|
||||
|
||||
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)"
|
||||
# arg b 1 flag_b
|
||||
# echo "Flag -b has value '$flag_b'"
|
||||
# echo "Flag -b has value '$(arg b 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"
|
||||
# arg c 1 flag_c
|
||||
# echo "Flag -c has value '$flag_c'"
|
||||
# echo "Flag -c has value '$(arg c 1)'"
|
||||
|
||||
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)"
|
||||
# arg d 1 flag_d
|
||||
# echo "Flag -d has value '$flag_d'"
|
||||
# echo "Flag -d has value '$(arg d 1)'"
|
||||
|
||||
echo -e "\n5. Args by index:"
|
||||
argn 1 first
|
||||
echo "5.1 arg[1]=$first"
|
||||
echo "5.2 arg[3]=$(argn 3)"
|
||||
# argl flag1 1 flag_1
|
||||
# echo "Flag --flag1 has value '$flag_1'"
|
||||
# echo "Flag --flag1 has value '$(argl flag1 1)'"
|
||||
|
||||
# Well, now we will try to get global args inside different functions
|
||||
# argl flag2 1 flag_2
|
||||
# echo "Flag --flag2 has value '$flag_2'"
|
||||
# echo "Flag --flag2 has value '$(argl flag2 1)'"
|
||||
|
||||
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
|
||||
}
|
||||
# arg e 0 arg_e
|
||||
# echo "Arg -e has value '$arg_e'"
|
||||
# echo "Arg -e has value '$(arg e 0)'"
|
||||
|
||||
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
|
||||
}
|
||||
# arg f 0 arg_f
|
||||
# echo "Arg -f has value '$arg_f'"
|
||||
# echo "Arg -f has value '$(arg f 0)'"
|
||||
|
||||
hello
|
||||
food
|
||||
# argl arg1 0 arg_1
|
||||
# echo "Arg --arg1 has value '$arg_1'"
|
||||
# echo "Arg --arg1 has value '$(argl arg1 0)'"
|
||||
|
||||
### 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
|
||||
# argl arg2 0 arg_2
|
||||
# echo "Arg --arg2 has value '$arg_2'"
|
||||
# echo "Arg --arg2 has value '$(argl arg2 0)'"
|
||||
|
||||
Reference in New Issue
Block a user