Skip to content

Instantly share code, notes, and snippets.

@Decstasy
Last active January 16, 2024 18:42
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Decstasy/19814b80a3551b34d78e8be7f3b5e8d8 to your computer and use it in GitHub Desktop.
Save Decstasy/19814b80a3551b34d78e8be7f3b5e8d8 to your computer and use it in GitHub Desktop.
Parse Bash arguments without getopts or getopt
#!/bin/bash
# Dennis Ullrich
# Version 0.1-0 (2017-06-29)
# request@decstasy.de
# Bash Version 3 required (it also works with ksh)
[[ ${BASH_VERSINFO[0]} -lt 3 ]] && exit 1
# Defaults
stdin=0
csv=0
param=""
# Put all arguments in a new array (because BASH_ARGV is read only)
ARGS=( "$@" )
for i in "${!ARGS[@]}"; do
case "${ARGS[i]}" in
'') # Skip if element is empty (happens when it's unsetted before)
continue
;;
-s|--stdin) stdin=1
;;
--csv) csv=1
;;
-p|--param) # Use +1 to access next array element and unset it
param="${ARGS[i+1]}"
unset 'ARGS[i+1]'
;;
--) # End of arguments
unset 'ARGS[i]'
break
;;
*) # Skip unset if our argument has not been matched
continue
;;
esac
unset 'ARGS[i]'
done
# In case you want to process more, there is nothing lost except the already parsed arguments with options.
# Keep in mind there was not a single shift!
echo "Unused Arguments:"
for i in "${!ARGS[@]}"; do
echo "$i: ${ARGS[i]}"
done
@sopos
Copy link

sopos commented Aug 25, 2017

Nice work but I see one problem which are empty parameters. You simply ignore them because of the first case.
Example

--arg1 --arg2 '' --arg3 -- blah

both options are without value so expected parsing would be

--arg1 --arg2 --arg3 -- '' blah

There must be implemented other way of skipping args. Simply using a skip flag could help, like

skip=''
for i in "${!ARGS[@]}"; do
        [[ -n "$skip" ]] && {
                skip=''
                continue
        }
        case "${ARGS[i]}" in
                -p|--param)     # Use +1 to access next array element and unset it
                                param="${ARGS[i+1]}"
                                unset 'ARGS[i+1]'
                                skip=1
                        ;;

or use other way of iteration:

for (( i=0; i<${#ARGS[@]}; i++ )); do
        case "${ARGS[i]}" in
                -p|--param)     # Use +1 to access next array element and unset it
                                param="${ARGS[i+1]}"
                                unset 'ARGS[i]'
                                let i++
                        ;;

@sopos
Copy link

sopos commented Aug 25, 2017

Also your implementation does not allow short version of short options -abc as -a -b -c
This pre-processing should help

  local ARGS=()
  for i in "$@"; do
    if [[ "${i:0:1}" == "-" && "${i:1:1}" != "-" ]]; then
      for (( j=1; j<${#i}; j++ )); do
        ARGS+=("-${i:$j:1}")
      done
    else
      ARGS+=("$i")
    fi
  done

It is not almighty, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment