1

I'm looking for a way to do option parsing in a Bash script (allowing for both short and long arguments as getopt does) that stops parsing at the first unrecognized argument, places a -- before that first unrecognized argument, and then copies the remaining arguments to the output string.

For example, here's the behavior that I want:

% OPTIONS=("-u" "name1" "--username=name2" "-x" "a" -u "b" "c")
% getopt -o u: -l username: -n programname -- "${OPTIONS[@]}"
-u 'name1' --username 'name2' -- -x 'a' -u 'b' 'c'
%

The utility getopt does not work this way, and instead emits the following:

programname: invalid option -- 'x'
-u 'name1' --username 'name2' -u 'b' -- 'a' 'c'

Note that I do not want arguments that follow an unrecognized option to be reordered as if they were recognized, as is demonstrated above with the second -u option. I'm hoping that someone will have a solution that will give the results I demonstrate in the first code block above. Any ideas?

Environment: bash 4.2.46(1), CentOS 7.2 @3.10.0-327.36.1.el7.x86_64.

Requirements: CentOS 7.2 Minimal with no additional software to be installed with all code written in a Bash script. The options passed to the options parser are not required to include -- in them (that is, the termination of parsing should be automatic).

5
  • 1
    can you check this post answer for your question stackoverflow.com/questions/402377/… – Kamaraj Oct 12 '16 at 9:26
  • In the answer provided by Arvid Requate and edited by TomRoche, there is an initial colon in the optspec. What does that do? Also, I like the "trick" of including -: in the optspec. Is there a way to modify the code so that one would not have to duplicate code for both short and long options? – Steve Amerige Oct 12 '16 at 9:49
  • Ah, leading colon turns on silent error reporting – Steve Amerige Oct 12 '16 at 9:58
  • Should this also work for invalid long options? – rudimeier Oct 12 '16 at 10:27
  • Yes, invalid long options should also terminate parsing. For example, the options -u name1 --unknown -u name2 --username name3 should result in the string: -u 'name1' -- --unknown -u 'name2' --username 'name3' – Steve Amerige Oct 12 '16 at 10:44
2

The solution to this problem requires something other than getopt because getopt rearranges options that it finds that do match the option specification and terminates on unrecognized options. The Bash built-in getopts comes to the rescue, but needs to be able to handle long options. In a post by Arvid Requate and TomRoche, there is a "trick" that allows getopts to handle long options: use - as an option specification and use a leading colon in the options specification to silence error reporting. Their solution, however, would require that code be duplicated for handling short and long options.

Here is a solution that I've tested that meets all of my requirements and also does not duplicate handling for short and long options. For clarity and completeness, I've changed username to set and added toggle to demonstrate options not taking values.

parse.sh

#!/bin/bash

Options=("$@")
while getopts ":s:-:" OptChar "${Options[@]}"; do
   case "$OptChar" in
    -)   case "$OPTARG" in
         set|set=*)
            if [[ $OPTARG =~ ^set= ]] ; then
               Value="${Options[$OPTIND-2]#*=}"
            else
               Value="${Options[$OPTIND-1]}"
               ((OPTIND++))
            fi
            echo "Parsed: --$OPTARG, value: '$Value'"
            ;;

         toggle)
            echo "Parsed: --$OPTARG";;
         *) ((OPTIND--)); break;;
         esac
         ;;

   # Redirect short arguments to long arguments
   s)    ((OPTIND-=2)); Options[OPTIND-1]="--set";;
   t)    ((OPTIND--)); Options[OPTIND-1]="--toggle";;

   *)    ((OPTIND--)); break;;
   esac
done
Options=( "${Options[@]:$OPTIND-1}" )

echo "REMAINING Options: ${Options[@]}"

and here is my test code:

% ./parse.sh --set=a  -s b --set= --set c --unknown -s d --set e
Parsed: --set=a, value: 'a'
Parsed: --set, value: 'b'
Parsed: --set=, value: ''
Parsed: --set, value: 'c'
REMAINING Options: --unknown -s d --set e
%
% ./parse.sh -s a -x -s b
Parsed: --set, value: 'a'
REMAINING Options: -x -s b
%
2
  • Have you tested the short options? ./parse.sh -x does not work for me. – rudimeier Oct 12 '16 at 12:44
  • @rudimeier Thanks... I've added the fix for the short options. The * case at the outer layer needed ((OPTIND--)). – Steve Amerige Oct 13 '16 at 8:12
0

This may work if you don't use combined short options like "-ax":

OPTIONS=("-u" "name1" "--username=name2" "-x" "a" -u "b" "c")

for i in `seq ${#OPTIONS[@]} -1 0`; do
  OFIRST=("${OPTIONS[@]:0:$i}")
  getopt -o u: -l username: -n programname -- "${OFIRST[@]}" &>/dev/null && break
done

NEWOPT=("${OPTIONS[@]:0:$i}" "--"  "${OPTIONS[@]:$i}")

getopt -o u: -l username: -n programname -- "${NEWOPT[@]}"

I'm sure that there are issues with this solution. Probably you would better try to add this feature to getopt.c source code from util-linux.

1
  • Thank you rudimeier for your participation in this discussion. I looked at your solution, but instead chose to do the one I've added as an answer to the question. Folks like you make this place great. – Steve Amerige Oct 12 '16 at 12:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.