Skip to content

Instantly share code, notes, and snippets.

@webb
Created October 2, 2020 15:18
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save webb/ff380b0eee96dafe1c20c2a136d85ef0 to your computer and use it in GitHub Desktop.
Save webb/ff380b0eee96dafe1c20c2a136d85ef0 to your computer and use it in GitHub Desktop.
A simple CLI parser in Bash
#!/usr/bin/env bash
print_help () { echo "Option -f \${file}: Set file"; exit 0; }
fail () { echo "Error: $*" >&2; exit 1; }
unset file
OPTIND=1
while getopts :f:h-: option
do case $option in
h ) print_help;;
f ) file=$OPTARG;;
- ) case $OPTARG in
file ) fail "Option \"$OPTARG\" missing argument";;
file=* ) file=${OPTARG#*=};;
help ) print_help;;
help=* ) fail "Option \"${OPTARG%%=*}\" has unexpected argument";;
* ) fail "Unknown long option \"${OPTARG%%=*}\"";;
esac;;
'?' ) fail "Unknown short option \"$OPTARG\"";;
: ) fail "Short option \"$OPTARG\" missing argument";;
* ) fail "Bad state in getopts (OPTARG=\"$OPTARG\")";;
esac
done
shift $((OPTIND-1))
echo "File is ${file-unset}"
for (( i=1; i<=$#; ++i ))
do printf "\$@[%d]=\"%s\"\n" $i "${@:i:1}"
done
@Artoria2e5
Copy link

Artoria2e5 commented Oct 3, 2020

To parse stuff like --file foo, it should be possible to do a lookahead and advance OPTIND accordingly.

I think I can write this into a function, but gosh is it painful to type on a phone… anyway, it should take a long arg name, a specification for whether the arg should be there, and return results as its own option/optarg. Something like:

case $option in
(-)
  long_opt='' 
  try_arg file required "${@:$OPTIND}"
  try_arg help forbidden "${@:$OPTIND}"
  case long_opt in
    (file) ;;
    (help) ;;
    ('') fail unknown long;; # should only match the empty case
  esac;;
esac

lookahead() {
  if (($# > 1)); then
    long_arg=$1
    ((OPTIND++))
  else return 1
  fi
}

try_arg() {
  if [[ -n $long_opt ]]; then return; fi
  case $OPTARG in
    ($1=*)
      long_opt=$1 long_arg=${OPTARG#*=}
      [[ $2 != forbidden ]] || puke ;;
    ($1)
      long_opt=$1
      if [[ $2 != forbidden ]]; then lookahead "${@:2}" || [[ $2 == optional ]] || puke ; fi
    (*) return;;
  esac
}

(Of course you would not want all the unnecessary string copies.)

@police999a
Copy link

@Honor1234560

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