19

There are a number of questions about this sort of thing but lets imagine we are targeting a generic Linux system with both getopt and getopts installed (not that we'll use either, but they seem popular)

How do I parse both long (--example | --example simple-option) and short argruments (-e | -esimple-example | -e simple-example)

Peter Coulton
  • 54,789
  • 12
  • 54
  • 72

1 Answers1

37

You want to use getopt with long and short options. An example from working code:

# Parse arguments
TEMP=$(getopt -n $PROGRAM_NAME -o p:P:cCkhnvVS \
--long domain-password:,pop3-password:\         
,create,cron,kill,help,no-sync-passwords,version,verbose,skip-pop3 \
-- "$@")                                                            

# Die if they fat finger arguments, this program will be run as root
[ $? = 0 ] || die "Error parsing arguments. Try $PROGRAM_NAME --help"       

eval set -- "$TEMP"
while true; do     
        case $1 in 
                -c|--create)
                        MODE="CREATE"; shift; continue
                ;;                                    
                -C|--cron)                            
                        MODE="CRON"; shift; continue  
                ;;                                    
                -k|--kill)                            
                        MODE="KILL"; shift; continue  
                ;;                                    
                -h|--help)                            
                        usage                         
                        exit 0                        
                ;;                                    
                -n|--no-sync-passwords)               
                        SYNC_VHOST=0; shift; continue 
                ;;                                    
                -p|--domain-password)                 
                        DOMAIN_PASS="$2"; shift; shift; continue
                ;;                                              
                -P|--pop3-password)                             
                        POP3_PASS="$2"; shift; shift; continue  
                ;;                                              
                -v|--version)                                   
                        printf "%s, version %s\n" "$PROGRAM_NAME" "$PROGRAM_VERSION"
                        exit 0                                                      
                ;;                                                                  
                -v|--verbose)                                                       
                        VERBOSE=1; shift; continue                                  
                ;;                                                                  
                -S|--skip-pop3)                                                     
                        SKIP_POP=1; shift; continue                                 
                ;;                                                                  
                --)                                                                 
                        # no more arguments to parse                                
                        break                                                       
                ;;                                                                  
                *)                                                                  
                        printf "Unknown option %s\n" "$1"                           
                        exit 1                                                      
                ;;                                                                  
        esac                                                                        
done     

Note, die is a function that was defined previously (not shown).

The -n option tells getopt to report errors as the name of my program, not as getopt. -o defines a list of short options (: after an option indicates a needed argument) and --long specifies the list of long options (corresponding in order to the short options).

The rest is just a simple switch, calling shift appropriately to advance the argument pointer. Note, calling shift; shift; is just a die hard habit. In the currently modern world, shift 2 would probably suffice.

The modern getopt is pretty consistent over newer platforms, however you may encounter some portability problems on older (circa pre Redhat 9) systems. See man getopt for information about backwards compatibility. However it's unlikely that you'll run into the need for it.

Finally, after parsing options, you can once again call:

eval set -- "$@"

This will move the argument pointer to anything else left on the command line after getopt was done parsing options. You can then just shift to keep reading them. For instance, if a command looked like this:

./foo --option bar file1.txt file2.txt file3.txt

Don't forget to make a handy -h / --help option to print your new fancy options once you're done. :) If you make that output help2man friendly, you have an instant man page to go with your new tool.

Edit

On most distributions, you can find more example getopt code in /usr/share/doc/util-linux/examples, which should have been installed by default.

Expedito
  • 7,771
  • 5
  • 30
  • 43
Tim Post
  • 33,371
  • 15
  • 110
  • 174
  • I do realize I'm doing no sanity here to check for multiple commands setting MODE. In this instance, it was acceptable that the last option simply took precedence, and the paste was meant only to be informational about one way to use `getopt`. – Tim Post Apr 15 '10 at 07:55
  • I've been referred to using `getopt()` several times now. Call me stupid, but I just can't understand how it works. Since my script needed a windows equivalent, I [used this](http://stackoverflow.com/a/3981086/314056). In my opinion, that's a much cleaner and less magical approach. Can I do something similar on linux? – Christian Oct 21 '12 at 09:46
  • @Christian You could so something similar with BASH, `%1` becoming `$1` (and `$2` respectively if `$1` expects an argument). In fact, many people just do it that way, keep cycling through the arguments via `shift` and running the current argument through a switch. `getopt` just handles all of this magic, plus mandatory argument checking for you, so many like that convenience. – Tim Post Oct 21 '12 at 11:21
  • Thanks Tim. I understand why `getop` is better, I just couldn't figure out how it works in less than 10 minutes, which for my task is too much compared to a plain 1:1 translation. Plus, having both scripts follow up the same code pattern makes it easier to maintain. Anyway, thanks for the insight into this. – Christian Oct 21 '12 at 12:25
  • `eval set -- "$@"` after the while loop is unnecessary and dangerous. It is unnecessary because even without it, "$@" will already contain the positional argument, making it a no-op. It is dangerous because with the eval you negate the benefits of the `"$@"` array containing elements with whitespace in them. If before one of the arguments was `"foo bar"`, after the eval you will have one argument `foo` and the next `bar`. – josch Mar 06 '22 at 09:08