Never been to TextSnippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world (or not, you can keep them private!)

cmdparser - parse command line options (See related posts)

cmdparser - parse command line options

Author: jv
License: The MIT License, Copyright (c) 2007 jv
Description: a command line option parser for use in bash scripts (Mac OS X); an alternative to getopts
Usage: /path/to/script_with_cmdparser -a -b -c -f file -d dir (example)
Hints: add security features as needed (such as resetting the $PATH & $IFS variables, using full command paths, etc.; cf. Shell scripting 101: Part 6)



#!/bin/bash

# cmdparser

# define the names of flags as a regular expression
# flags are command line options that require arguments

flags="(flag1|flag2|flag3|flag4|flag5|flag6|flag7|flag8)"


# define the names of switches as a regular expression
# switches are command line options that do not take arguments
# make sure multi-char switches precede single-char switches in the regular expression
# note that the regular expression contains neither the special read-from-stdin switch "-" nor the special end-of-options switch "--"

switches="(cc|zz|a|b|c)"  


usage="usage: $(basename "$0") [-a] [-b] [-c] [-cc] [-zz] [-flag1 arg] [-flag2 'arg1 arg2 ...'] [-flag3=arg] [-flag4=\"arg1 arg2\"] ..."

declare flag1 flag2 flag3 flag4 flag5 flag6 flag7 flag8       # flags
declare cc zz a b c                                           # switches
#declare -i cc=0 zz=0 a=0 b=0 c=0                          

declare optstr flagvar argvar argvar_escaped pipedstr piped
declare -i optid pipedvar


# piped="piped" will be used for variable creation 
# example: piped="piped"; pipedstr="piped arg"; eval $piped='"$(echo "$pipedstr")"'; echo "$piped"

piped="piped"

# default value is set to "no pipe"
pipedvar=0

# if /dev/stdin has a size greater than zero ...
if [[ -s /dev/stdin ]]; then pipedstr="$("; else pipedstr=""; fi 

if [[ $# -eq 0 ]] && [[ -z "$(echo $pipedstr)" ]]; then
  echo "No arguments specified!"
  echo >&2 "$usage"
  exit 1
fi 

if [[ $# -eq 0 ]] && [[ -n "$(echo $pipedstr)" ]]; then
  eval $piped='"$(echo "$pipedstr")"'  
  pipedvar=1
fi 

# if there are command line arguments ...
if [[ $pipedvar -eq 0 ]]; then

   optstr=" "  
   optid=0

   while [[ -n "$optstr" ]]; do     

      # try to extract valid flags or switches from $1
      #echo "\$1:   $1"

      optstr="$(echo "$1"  |  grep -E "^\-\-?$flags$")"

      if [[ -n "$optstr" ]]; then optid=1; fi
      if [[ -z "$optstr" ]]; then optid=2; optstr="$(echo "$1" | grep -E "^\-\-?$switches$")"; fi
      if [[ -z "$optstr" ]]; then optid=3; optstr="$(echo "$1" | grep -E "^\-\-?$switches+$")"; fi
      if [[ -z "$optstr" ]]; then optid=4; optstr="$(echo "$1" | grep -E "^(\-\-?$flags\=.*|\-\-?$flags[^ ]+)$")"; fi
      if [[ -z "$optstr" ]]; then  
         if [[ "$1" = "-" ]] && [[ "$@" = "-" ]]; then  
            optid=5
            optstr="-" 
         fi
      fi

      if [[ -z "$optstr" ]] && ( [[ -n "$(echo "$@" |  grep -Eo "(^ *| )\-\-?$flags" )" ]] || [[ -n "$(echo "$@" |  grep -Eo "(^ *| )\-\-?$switches" )" ]] ); then
         optstr="$(echo $1)"
         echo "illegal option removed: $optstr"
         shift
         continue
         #optstr=" "
         #echo >&2 "$usage"
         #exit 1
      fi 

      #echo -e "optstr:\t$optstr"
      #echo -e "optid:\t$optid"
      #echo -e "piped:\t$piped"

      if [[ "$1" = "--" ]]; then shift; break; fi      # -- marks end of options

      if [[ -z "$optstr" ]]; then break; fi


      # flag followed by space (example: -f file)
      if [[ $optid -eq 1 ]]; then 

         if [[ -z "$2" ]]; then
            echo; echo "flag \"$1\" removed: no argument given!"
            shift
            if [[ -n "$(echo "$@")" ]]; then shift; continue; else shift; break; fi
            #echo >&2 "$usage"
            #exit 1
         fi 

         # make sure flag $1 is not directly followed by yet another flag or switch
         if [[ "$2" = "-" ]] || [[ "$2" = "--" ]] || [[ -n "$(echo "$2" |  grep -E "^\-\-?$flags.*$" )" ]] || [[ -n "$(echo "$2" |  grep -E "^\-\-?$switches+$" )" ]]; then
            echo; echo "flag \"$1\" removed because of illegal argument: \"$2\""
            shift
            continue
            #echo >&2 "$usage"
            #exit 1
         fi

         flagvar="$(expr "$1" : "^\-\{1,2\}\(.*\)$")"
         argvar="$2"
         eval $flagvar='"$(echo "$argvar")"'
         shift 2
         continue

      # single switch (example: -a)
      elif [[ $optid -eq 2 ]]; then
         flagvar="$(expr "$1" : "^\-\{1,2\}\(.*\)$")"
         eval $flagvar='"$(echo "1")"'
         shift
         continue
  
      # combined switch (example: -abcc)
      elif [[ $optid -eq 3 ]]; then
         flagvar="$(expr "$1" : "^\-\{1,2\}\(.*\)$")"
         while [[ -n "$flagvar" ]]; do
            char="$(echo "$flagvar" | sed -E "s/^$switches.*$/\1/")"
            eval $char='"$(echo "1")"'
            flagvar="$(echo "$flagvar" | sed -E "s/^$switches//")"
         done
         shift
         continue

      # flag without following space (example: -ffile)
      elif [[ $optid -eq 4 ]]; then 
         argvar="$(echo "$1" | sed -E "s/^\-\-?$flags\=?//")"
         argvar_escaped="$(echo "$argvar" | sed -E 's/([^[:alnum:]])/\\\1/g')"   # escape special regex metacharacters such as ., ?, *
         #argvar_escaped="$(echo "$argvar" | sed -E 's/([[:punct:]])/\\\1/g')"     
         #echo "argvar_escaped: $argvar_escaped"
         flagvar="$(echo "$1" | sed -E -e 's/^-\-?//' -e "s/\=?$argvar_escaped$//")"
         eval $flagvar='"$(echo "$argvar")"'
         shift
         continue

      # the special read-from-stdin switch "-"
      elif [[ $optid -eq 5 ]]; then 
         pipedvar=1
         eval $piped='"$(echo "$pipedstr")"'
         shift
         break

      fi

      # remove $1 from "$@"
      shift

   done

fi   # if [[$pipedvar -eq 0 ]]; then ...


echo 

echo -e "a:\t$a"
echo -e "b:\t$b"
echo -e "c:\t$c"
echo -e "cc:\t$cc"
echo -e "zz:\t$zz"
echo -e "flag1:\t$flag1"
echo -e "flag2:\t$flag2"
echo -e "flag3:\t$flag3"
echo -e "flag4:\t$flag4"
echo -e "flag5:\t$flag5"
echo -e "flag6:\t$flag6"
echo -e "flag7:\t$flag7"
echo -e "flag8:\t$flag8"

echo

if [[ $pipedvar -eq 1 ]] && [[ -z "$(echo $@)" ]]; then echo "remaining string-piped: $piped"; else echo "remaining string: $@"; fi

echo

exit 0




Test cases:

touch ~/Desktop/cmdparser; chmod +x ~/Desktop/cmdparser


1.

~/Desktop/cmdparser -abcc -c -zz -flag1="" -flag2=arg -flag3="arg" -flag4='arg1=*,arg2=?,arg3=!' -flag5 '(arg1|arg2|arg3)' -flag6 'arg1=ag,arg2=bg,arg3=cg' -flag7 An\ argument\ with\ spaces\! -flag8='Yet another argument with spaces!' filename1 filename2 filename3


Output:

a: 1
b: 1
c: 1
cc: 1
zz: 1
flag1:
flag2: arg
flag3: arg
flag4: arg1=*,arg2=?,arg3=!
flag5: (arg1|arg2|arg3)
flag6: arg1=ag,arg2=bg,arg3=cg
flag7: An argument with spaces!
flag8: Yet another argument with spaces!

remaining string: filename1 filename2 filename3


2.

echo filename | ~/Desktop/cmdparser -abcc -c -zz -flag1 arg -flag2=arg --flag3="arg" -flag4='arg1=*,arg2=?,arg3=!' -flag5 '(arg1|arg2|arg3)' -flag6 'arg1=ag,arg2=bg,arg3=cg' --flag7 An\ argument\ with\ spaces\! -flag8='Yet another argument with spaces!' -


Output:

a: 1
b: 1
c: 1
cc: 1
zz: 1
flag1: arg
flag2: arg
flag3: arg
flag4: arg1=*,arg2=?,arg3=!
flag5: (arg1|arg2|arg3)
flag6: arg1=ag,arg2=bg,arg3=cg
flag7: An argument with spaces!
flag8: Yet another argument with spaces!

remaining string-piped: filename


You need to create an account or log in to post comments to this site.


Related Posts