[Bash] [Tutorial] Parsing Command-Line Options - Programming On Unix

Users browsing this thread: 2 Guest(s)
crshd
Registered
After a while, hard-coding options into your shell scripts just doesn't cut it anymore. You need to be able to dynamically pass options to your program.

There are a few different ways to do this, ranging from _very_ simple to somewhat complicated. Just hang in there with me.

Let's start with a simple _Hello World!_ script:

PHP Code:
#!/usr/bin/env bash

echo "Hello World!" 

Now what if we wanted the output to read "Goodbye World!" instead? You could go and edit your code, but do you really want to do that every time you want a different output? I didn't think so.

Bash has a bunch of special variables for this case. The variable _$@_ contains _all_ parameters, separated by spaces. To access one specific parameter, numbered variables are used. _$0_ is the script name, _$1_ is the first argument, _$2_ the second, and so on.

So we can change the script to read like this

PHP Code:
#!/usr/bin/env bash

echo "${1} World!" 

So if we call the script with _hello.sh "Goodbye"_, it will output "Goodbye World!" Neat!

For this (very simple) example script, this works fine. But sometimes something more complex is needed.

Let's say we have a script that prints out a greeting to the user (Don't ask me for the point in this. I just need an example). Depending on the time of day, you want it to say "Good morning", "Good evening" and whatnot.

### GETOPTS

PHP Code:
#!/usr/bin/env bash

while getopts :c:emn opt; do
    case ${
optin
        c
)
            
greet=${OPTARG}
            ;;
        
e)
            
greet="Good evening"
           
;;
        
m)
            
greet="Good morning"
           
;;
        
n)
            
greet="Mahlzeit"
           
;;
        :)
            echo 
"Option -${OPTARG} requires an argument"
            
exit 1
            
;;
        \?)
            echo 
"What's the time?"
            
exit 1
           
;;
    
esac
done

echo "${greet} ${USER}!" 

Here we use _getopts_ to parse the parameters that live in _$@_. We need to use it in a _while_ loop, so we can access each option individually. Getopts' syntax is as follows:

Code:
getopts <options> <variable>

Where _options_ are the options getopts will accept, and _variable_ is the variable name we will be using in the _while_ loop.

**Let's tear apart the _options_ part.**
Getopts accepts two kinds of options; with and without arguments. To tell getopts that an option will have an argument, a colon (:) is added after the option letter. So in the above example, _c_ will require an argument, while _e_, _m_ and _n_ will not. Arguments can be accessed with the _$OPTARG_ variable.

Special attention needs to be given to the first colon in the _options_ list. This tells getopts to work in quiet mode, otherwise the screen would get spammed by debug messages.

**Ok, got that. What about the _case_?**
If getopts finds an option that does it was not expecting (so anything other than _c_, _e_, _m_, or _n_), it will set _$opt_ to "?". This is useful for handling errors. In our case, we are letting the script exit with status _1_.

If getopts expects an argument but is not provided one, it will set _$opt_ to ":" instead. This can be used to notify the user that an argument is required.

-----

####Some notes:
- _getopts_ is a Bash builtin. It only supports single-dash, single-character options. For the double-dashed long options, the external program _getopt_ is needed. More on that later.
- The example above is unfitting. With this script, you don't need to be able to use more than one option at a time, that would be completely illogical. It's just the best I could come up with right now.

-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCA/IT d-(---)@ s+: a-- C+++(++++)$ UBL*+++ P+++>++++ L++ E W+++$ !N !o K !w !O M+>++ !V PS+++ PE !Y PGP+ !t-- !5 !X R@ tv- b+ DI D+ G e h r++ y+
------END GEEK CODE BLOCK------

jmbi
Long time nixers
Very useful and well written tutorial! Thanks!
desyncr
Members
Thanks for the tutorial! Here is a little script I made as I followed your instructions:

PHP Code:
#!/bin/bash
preview=0           # Make a preview only
start=0:00:00       # Start position
endpos=5            # End position
fps=13              # Frame per second. The more the better.
output=movie.gif    # Gif file name
scale=240:180       # Gif scale
video=              # Video file name
test=0              # Wether output the command or run it

# mplayer filename [Name of video file]-ao null
#       -ss 0:04:15 [Startposition h:mm:ss]
#       -endpos 15 [duration of gif]
#       -vo gif89a:fps=13 [frames per second]:output=animated.gif [Name of ouput file]
#       -vf scale=240:180 [Dimensions of gif file]
#
# mplayer -ao null -ss 0:03:33 [Startposition h:mm:ss]
#       -endpos 15 [duration of gif] filename.mpg [Name of video file]
#
while getopts v:s:e:f:o:c:pt opt; do
    case ${
optin
        v
)
            
video=${OPTARG}
            if [[ ! -
e $video ]]; then
                
echo "Can\'t access video: ${video}"
                
exit 1
            fi
            
;;
        
s)
            
start=${OPTARG}
            ;;
        
e)
            
endpos=${OPTARG}
            ;;
        
f)
            
fps=${OPTARG}
            ;;
        
o)
            
out=${OPTARG}
            ;;
        
c)
            
scale=${OPTARG}
            ;;
        
p)
            
preview=1
            
;;
        
t)
            
test=1
            
;;
        \?)
            echo 
'Missing argument'
            
exit 1
            
;;
    
esac
done
if [[ $video == '' ]]; then
    
echo "Usage ${0} -v movie.avi"
    
exit 1
fi

cmd
="mplayer ${video} -ao null -ss ${start} -endpos ${endpos}"
if (($preview == 0)); then
    cmd
="$cmd -vo gif89a:fps=${fps}:output=${output} -vf scale=${scale}"
fi

if (($test)); then
    
echo $cmd
else
    
exec $cmd
fi