Friday, December 28, 2012

getopts in shell programming


getopts is shell builtin command is used to parse command line arguments.

getopts takes an option string as an argument. The option string is as follows
  • Simple flag with no arguments - Just the letter of the flag like say for example, "-f
  • Flag with arguments - Letter of the flag followed by a colon. Say for example, if something like "-f filename", we would add "f:" to the option string.
getopts also supports detection of unknown flags and missing arguments. To enable this option, a colon(:) should be added as the first character of the option string.

The getopts returns a colon when it encounters a flag with missing argument.
getopts routine returns a question mark when it encounters an unknown flag.

getopts will set an exit status of FALSE when there is nothing left to parse. So it is easier to use getopts in a while loop

while getopts …; do
   …
done

getopts will parse options and their possible arguments. It will stop parsing on the first non-option argument (a string that doesn't begin with a hyphen (-), a string that isn't an argument for an option in front of it). It will also stop parsing when it sees the -- (double-hyphen), which stands for ends of options.

Variables associated with getopts
  • OPTIND - It holds the index of the next argument to be processed. OPTIND is initially set to 1, and should be reset to 1 if we need to parse anything again with getopts.
  • OPTARG - The argument value associated with the current flag(if any).
  • OPTERR - It takes values 0 or 1. It indicates if bash shell should display error messages generated by getopts built-in.  If this variable is set to 1, error reporting by the underlying getopts function is enabled. If set to 0, error reporting is disabled. This variable is ignored if the first character of the option string is a colon(:), which tells getopts that the script can handle and report errors.

Let us demonstrate with following example


#!/bin/bash

while getopts ":f" opt
do
   if [ $opt = "f" ]
   then
         echo "-f was triggered"
   fi
   if [ $opt = "?" ]
   then
         echo "Invalid option flag: -$OPTARG"
         #exit 1
   fi
done 

Calling it without arguments

$ ./opt.sh

getopts did not see any valid or invalid options

Calling with non option arguments

$ ./opt.sh /etc/passwd
$

Again, getopts did not see any valid or invalid options

Calling with option arguments

$ ./opt.sh -f
-f was triggered

The "f" was put into variable $opt

Suppose an invalid argument is passed as an argument
$ ./opt.sh -a
Invalid option flag: -a

getopts did not accept the option "a" and acted by putting ? into $opt and the invalid option character(a) into $OPTARG.

It is possible to mix valid and invalid options

$ ./opt.sh -a -b -f -c
Invalid option flag: -a
Invalid option flag: -b
-f was triggered
Invalid option flag: -c

It is also possible to give valid option multiple times

$ ./opt.sh -f -f -f
-f was triggered
-f was triggered
-f was triggered

Now let us consider passing arguments for flags


Arguments can be passed to flags by placing a colon(:) after the flag

#!/bin/bash

while getopts ":f:" opt
do
   if [ $opt = "f" ]
   then
         echo "-f was triggered with parameter $OPTARG"
   fi
   if [ $opt = "?" ]
   then
         echo "Invalid option flag: -$OPTARG"
         #exit 1
   fi
   if [ $opt = ":" ]
   then 
         echo "Option flag $OPTARG requires an argument"
   fi
done 

Calling without an argument for the flag

$ ./optarg.sh -f
Option flag f requires an argument

Calling with a valid argument for the flag

$ ./optarg.sh -f /etc/passwd
-f was triggered with parameter /etc/passwd

Tuesday, December 25, 2012

Double Parantheses (( )) and Single Parantheses () in Bash shell


Let us see what is the difference between double parantheses - (( )) and single parantheses - $() in Bash shell.

Double parantheses is used as math operator in Bash shell. Double parantheses can be used as an extension for numerical for loops. This can be demonstrated as follows

for (( i = 1; i <= 10; i++ ))
do
      echo "$i"
done

$ abc=5
$ echo $((abc-1))
$ 4

Let us come to single parantheses now. What does $() operator stand for - For inline execution. The $() operator inserts the output of one command into the middle of a statement.The Bourne shell provides two operators for executing a command and placing its output in the middle of another command or string. These operators are the $() operator and the backtick (`) operator middle of a statement. This is demonstrated as

$ xyz=$(date)
$ echo $xyz
Tue Dec 25 20:56:30 GMT 2012

$ abc=`date`
$ echo $abc
Tue Dec 25 20:56:15 GMT 2012

Bash Shell : .bash_profile and .bashrc

Whenever a new shell is run, to set environment variables, aliases and export them automatically, the following files come handy

  • ~/.bash_profile - executed automatically for all login shells
  • ~/.bashrc - executed automatically for all non-login shells ( when bash is explicitly typed on the command line or when a script that starts with #!/bin/bash is run )
We may also find .bashrc file which sources .bash_profile, as follows

. $HOME/.bash_profile

Shell Arithmetic using expr


  • shell treats all shell variables as strings.
  • shell uses the expr command  to  perform integer arithmetic.
  • expr command cannot do real or non-integer arithmetic.
  • expr takes two integer arguments and an operand and writes the result to the standard output. If the output need to be stored in a variable, command substitution(`) need to be used.

$ expr 1 + 1
2

$ a=`expr 1 + 1`
$ echo $a
2

There must be spaces between the operand and the arguments.

Note: * is the multiplication operand. But * has special meaning to the shell. Hence while using *, it should be shielded using \ character.

$ expr 2 \* 2
4

Monday, December 24, 2012

Positional parameters and Accessing all positional parameters in Shell


The positional parameters in shell are specified by parameters from $1 to $9.
To list all the positional parameters shell provides two variables : $* and $@
When not quoted, these two variables are equivalent.

However, when these variables are quoted, "$*" and "$@", they behave differently. Let us illustrate with following example

Suppose, I have two files named 'first file' and 'second file'.
I want to cat the output of two files.
These two files have space in their names.

"$*" when quoted, does not take into account any special quoting in the file names when the files are passed as parameters(here, it does not take into account the space in the file names). So $* when quoted produces a single string of all positional parameters.

But "$@" will preserve the special quoting(space in the file names, in this example). "$@" when quoted, produces a list of positional parameters.

Let us see what happens when we use $*, $@, "$*", "$@" to specify the positional parameters first file and second file

#/bin/bash
#pos.sh
cat $*

$ ./pos.sh first\ file second\ file
cat: first: No such file or directory
cat: file: No such file or directory
cat: second: No such file or directory
cat: file: No such file or directory

#/bin/bash
#pos.sh
cat $@

$ ./pos.sh first\ file second\ file
cat: first: No such file or directory
cat: file: No such file or directory
cat: second: No such file or directory
cat: file: No such file or directory

#!/bin/bash
#pos.sh
cat "$*"

$ ./pos.sh first\ file second\ file
cat: first file second file: No such file or directory

#!/bin/bash
#pos.sh
cat "$@"

$ ./pos.sh first\ file second\ file
abc
xyz

In this case, the output of first file and second file get concatenated  successfully.