1.1 Doing Arithmetic in Your Shell Script
Use $(()) or let for integer arithmetic expressions
[maxwell@MaxwellDBA test]$ COUNT=$((COUNT + 5 + MAX * 2))
[maxwell@MaxwellDBA test]$ let COUNT+=5+MAX*2
These assignment operators are also available with $(()) provided they occur inside the double parentheses.
1.2 Braching on Conditions
The if statement in bash is similar in appearance to that in other programming languages:
if [ $# -lt 3 ]
then
printf "%b" "Error. Not enough arguments.\n"
printf "%b" "usage: myscript file1 op file2\n"
exit 1
fi
# or alternatively:
if (( $# < 3 ))
then
printf "%b" "Error. Not enough arguments.\n"
printf "%b" "usage: myscript file1 op file2\n"
exit 1
fi
# a full-blown if with an elif and an else clause:
if (( $# < 3 ))
then
printf "%b" "Error.Not enough arguments.\n"
printf "%b" "usage: myscript file1 op file2\n"
exit1
elif (( $# > 3 ))
then
printf "%b" "Error. Too many arguments.\n"
printf "%b" "usage: myscript file1 op file2\n"
exit2
else
printf "%b" "Argument count correct. Proceeding...\n"
fi
[maxwell@MaxwellDBA test]$ cat trythis.sh
if ls;pwd;cd $1;
then
echo success;
else
echo failed;
fi
pwd
[maxwell@MaxwellDBA test]$ bash ./trythis.sh /tmp
another.file chmod_all2.sh embedded_documentation func_choose_man1.sh nohup.out sample tricky.sh
a.out chmod_all.sh ext.sh func_choose.sh 'Oh the Waste' select_dir.sh trythis.sh
asdfwednnrw2rs2esff.txt cong.txt file.txt lines one.file simplest.sh use_up_option.sh
check_arg_count.sh def.conf func_choice1.sh more.txt quoted.sh simpls.sh zebra.list
check_unset_parms.sh donors.sh func_choose.html myscript.sh readability.sh suffixer.sh
/home/maxwell/shelllearning/test
success
/tmp
[maxwell@MaxwellDBA test]$
1.3 Testing for File Characteristics
Use the various file characteristic tests in the test command as part of your if statements.
[maxwell@MaxwellDBA test]$ cat checkfile.sh
#!/usr/bin/env bash
# cookbook filename: checkfile
#
DIRPLACE=/tmp
INFILE=/home/maxwell/shelllearning/test/amazing.data
OUTFILE=/home/maxwell/shelllearning/test/more.results
if [ -d "$DIRPLACE" ]
then
cd $DIRPLACE
if [ -e "$INFILE" ]
then
if [ -w "$OUTFILE" ]
then
doscience < "$INFILE" >> "$OUTFILE"
else
echo "can not write to $OUTFILE"
fi
else
echo "can not read from $INFILE"
fi
else
echo "can not cd into $DIRPLACE"
fi
[maxwell@MaxwellDBA test]$
Three of them are tested using binary operators,each taking two filenames:
1.4 Testing for More Than One Thing
Use the operators for logical AND(-a) and OR(-o) to combine more than one test in an expression.
if [ -r $FILE -a -w $FILE ]
# Will test to see that the file is both readable and writable.
1.5 Testing for String Characteristics
Using the single bracket if statements.
[maxwell@MaxwellDBA test]$ cat checkstr.sh
#!/usr/bin/env bash
#cookbook filename: checkstr
#
# if statement
# test a string to see if it has any length
#
# use the command line argument
VAR="$1"
#
if [ "$VAR" ]
then
echo has text
else
echo zero length
fi
#
if [ -z "$VAR" ]
then
echo zero length
else
echo has text
fi
[maxwell@MaxwellDBA test]$
1.6 Testing for Equal
The type of comparison you need determines which operator you should use.
Use the -eq operator for numeric comparisons
The equality primary = (or ==) for string comparisons.
[maxwell@MaxwellDBA test]$ ls -ltr strvsnum.sh
-rw-rw-r-- 1 maxwell maxwell 323 Jul 26 14:46 strvsnum.sh
[maxwell@MaxwellDBA test]$ cat strvsnum.sh
#!/usr/bin/env bash
#cookbook filename: strvsnum
#
# the old string vs. numeric comparison dilemma
#
VAR1=" 05 "
VAR2="5"
printf "%s" "do they -eq as equal? "
if [ "$VAR1" -eq "$VAR2" ]
then
echo YES
else
echo NO
fi
printf "%s" "do they = as equal? "
if [ "$VAR1" = "$VAR2" ]
then
echo YES
else
echo NO
fi
[maxwell@MaxwellDBA test]$ bash strvsnum.sh
do they -eq as equal? YES
do they = as equal? NO
[maxwell@MaxwellDBA test]$
1.7 Testing with Pattern Matches
Use the double-bracket compound statement in an if statement to enable shell-style pattern matches on the righthand side of the equals operator:
if [[ "${MYFILENAME}" == *.jpg ]]
1.8 Testing with Regular Expressions
Use the regular expression matching of the =~ operator.Once it has matched the string. the various parts of the pattern are available in the shell variable $BASH_REMATCH.
[maxwell@MaxwellDBA test]$ cat trackmatch.sh
#!/usr/bin/env bash
# cookbook filename: trackmatch.sh
#
for CDTRACK in *
do
if [[ "$CDTRACK" =~ "([[:alpha:][:blank:]]*)-([[:digit:]]*) - (.*)$" ]]
then
echo Track ${BASH_REMATCH[2]} is ${BASH_REMATCH[3]}
mv "$CDTRACK" "Track${BASH_REMATCH[2]}"
fi
done
[maxwell@MaxwellDBA test]$
1.9 Changing Behavior with Redirections
Use the test -t option in an if statement to branch between the two desired behaviors.
1.10 Looping for a While
Use the while looping construct for arithmetic conditions:
# for arithmetic conditions:
while (( COUNT < MAX ))
do
some stuff
let COUNT++
done
# for filesystem-related conditions:
while [ -z "$LOCKFILE" ]
do
some things
done
# for reading input
while read lineoftext
do
process $lineoftext
done
1.11 Looping with a read
To clean up this directory it would be nice to get rid of all the scratch files.
# Try
svn status mysrc | grep '^?' | cut -c8- | \
while read FN; do echo "$FN"; rm -rf "$FN"; done
# Or:
svn status mysrc | \
while read TAG FN
do
if [[ $TAG == \? ]]
then
echo $FN
rm -rf "$FN"
fi
done
1.12 Loop with a Count
Use a special case of the for syntax,one that looks a lot like C language, but with double parentheses:
[maxwell@MaxwellDBA test]$ for (( i=0 ; i<10 ; i++ )); do echo $i ; done
0
1
2
3
4
5
6
7
8
9
[maxwell@MaxwellDBA test]$ for i in 1 2 3 4 5 6 7 8 9 10
> do
> echo $i
> done
1
2
3
4
5
6
7
8
9
10
[maxwell@MaxwellDBA test]$
The special case of the for loop with C-like syntax is a relatively recent addition to bash. It more general from can be described as :
for (( expr1 ; expr2 ; expr3 )) ; do list ; done
[maxwell@MaxwellDBA test]$ for (( i=0,j=0; i+j < 10 ; i++, j++ ))
> do
> echo $((i*j))
> done
0
1
4
9
16
[maxwell@MaxwellDBA test]$
1.13 Looping with Floating-Point Values
The for loop with arithmetic expressions only does integer arithmetic. Use the seq command to generate your floating-point values,
[maxwell@MaxwellDBA test]$ for fp in $(seq 1.0 .01 1.1)
> do
> echo $fp; other stuff too
> done
1.00
-bash: other: command not found
1.01
-bash: other: command not found
1.02
-bash: other: command not found
1.03
-bash: other: command not found
1.04
-bash: other: command not found
1.05
-bash: other: command not found
1.06
-bash: other: command not found
1.07
-bash: other: command not found
1.08
-bash: other: command not found
1.09
-bash: other: command not found
1.10
-bash: other: command not found
[maxwell@MaxwellDBA test]$ seq 1.0 .01 1.1 | \
> while read fp
> do
> echo $fp; other stuff too
> done
1.00
-bash: other: command not found
1.01
-bash: other: command not found
1.02
-bash: other: command not found
1.03
-bash: other: command not found
1.04
-bash: other: command not found
1.05
-bash: other: command not found
1.06
-bash: other: command not found
1.07
-bash: other: command not found
1.08
-bash: other: command not found
1.09
-bash: other: command not found
1.10
-bash: other: command not found
[maxwell@MaxwellDBA test]$
1.14 Branching Many Ways
Use the case statement for a multiway branch:
case $FN in
*.gif) gif2png $FN
;;
*.png) pngOK $FN
;;
*.jpg) jpg2gif $FN
;;
*.tif | *.TIFF) tif2jpg $FN
;;
*) printf "File not supported: %s" $FN
;;
esac
# This equivalent to this using if/then/else statements is
if [[ $FN == *.gif ]]
then
gif2png $FN
elif [[$FN == *.png ]]
then
pngOK $FN
elif [[ $FN == *.jpg ]]
then
jpg2gif $FN
elif [[ $FN == *.tif || $FN == *.TIFF ]]
then
tif2jpg $FN
else
printf "File not supported: %s" $FN
fi
1.15 Parsing Command-Line Arguments
[maxwell@MaxwellDBA test]$ cat dashes
#!/usr/bin/env bash
#cookbook filename:dashes
#
#dashes - print a line of dashes
#
#options: # How many (default 72)
# -c X use char X instead of dashes
#
LEN=72
CHAR='-'
while (( $# > 0 ))
do
case $1 in
[0-9]*) LEN=$1
;;
-c) shift;
CHAR=${1:--}
;;
*) printf 'usage: %s [ -c X] [#]\n' $(basename $0) >&2
exit 2
;;
esac
shift
done
[maxwell@MaxwellDBA test]$ sh dashes -g
usage: dashes [ -c X] [#]
[maxwell@MaxwellDBA test]$ sh /home/maxwell/shelllearning/test/dashes -g
usage: dashes [ -c X] [#]
[maxwell@MaxwellDBA test]$
1.16 Creating Simple Menus
Use the select statement to create simple character-based screen menus.
[maxwell@MaxwellDBA test]$ cat dbinit.sh
#!/usr/bin/env bash
#cookbook filename: dbinit.sh
#
DBLIST=$(sh ./listdb | tail + 2)
select DB in $DBLIST
do
echo Initializing database: $DB
mysql -u user -p $DB <myinit.sql
done
[maxwell@MaxwellDBA test]$
1.17 Changing the Prompt on Simple Menus
The bash enviroment variable $PS3 is the prompt used by select.
#!/usr/bin/env bash
# cookbook filename: dbinit.2
#
DBLIST=$(sh ./listdb | tail +2)
PS3="0 inits >"
select DB in $DBLIST
do
if [ $DB ]
then
echo Initializing database: $DB
PS3="$((i++)) inits >"
mysql -u user -p $DB <myinit.sql
fi
done
$
1.18 Create a Simple RPN Calculator
Create a calculator using shell arithmetic and RPN notation:
#!/usr/bin/env bash
# cookbook filename: rpncalc
#
# simple RPN command line (integer) calculator
#
# takes the arguments and computes with them
# of the form a b op
# allow the use of x instead of *
#
# error check our argument counts:
if [ \( $# -lt 3 \) -o \( $(($# % 2)) -eq 0 \) ]
then
echo "usage: calc number number op [ number op ] ..."
echo "use x or '*' for multiplication"
exit 1
fi
ANS=$(($1 ${3//x/*} $2))
shift 3
while [ $# -gt 0 ]
do
ANS=$((ANS ${2//x/*} $1))
shift 2
done
echo $ANS
1.19 Creating a Command-Line Calculator
[maxwell@MaxwellDBA test]$ cat fun_calc.sh
#!/usr/bin/env bash
#
# cookbook filename: func_calc
# Trivial command line calculator
function calc
{
awk "BEGIN {print \"The answer is: \" $* }";
}
[maxwell@MaxwellDBA test]$ sh fun_calc.sh 2 + 3 + 4
[maxwell@MaxwellDBA test]$ sh -x fun_calc.sh 2 + 3 + 4
[maxwell@MaxwellDBA test]$ calc 2 + 3 + 4
-bash: calc: command not found
[maxwell@MaxwellDBA test]$ # cookbook filename: func_calc
[maxwell@MaxwellDBA test]$ # Trivial command line calculator
[maxwell@MaxwellDBA test]$ function calc
> {
> awk "BEGIN {print \"The answer is: \" $* }";
> }
[maxwell@MaxwellDBA test]$ calc 2 + 3 + 4
The answer is: 9
[maxwell@MaxwellDBA test]$ calc 2 + 3 + 4.5
The answer is: 9.5
[maxwell@MaxwellDBA test]$ calc '(2+2-3)*4'
The answer is: 4
[maxwell@MaxwellDBA test]$ calc \(2+2-3\)\*4
The answer is: 4
[maxwell@MaxwellDBA test]$ calc '(2+2-3)*4.5'
The answer is: 4.5
[maxwell@MaxwellDBA test]$