CT320

CT320: Network and System Administration

Fall 2018

Bash Scripts

See this page as a slide show

CT320: Bash Scripts

Bash Scripts

It’s a program

A shell script is a program. Therefore, it deserves all of the care that any other program should get, including, as appropriate:

Morons

Only an idiot would justify sloppy work with, “It’s only a shell script, so I didn’t bother doing …”.

Sure, if it’s a short-lived (you hope) program (whether script or not), then it may not require the full treatment. However, that decision is not determined by whether or not the program is a shell script, a Perl script, a Python script, or a C++ program.

Shell Scripting

Shell scripts are programs that:

Focus

There are many shells, including:

From now on, we will focus on bash.

Input and Output

Variables and Quoting

$ name=Bozo
$ let amount=2+7
$ echo "name amount"
name amount
$ echo "$name $amount"
Bozo 9
$ echo $UID
1038

Arguments

Script Syntax

Arguments example

$ echo -e '#! /bin/bash\nlet sum=$1+$2\necho "Sum is $sum"' >s
$ cat s
#! /bin/bash
let sum=$1+$2
echo "Sum is $sum"
$ ./s 34 5 12
.script: line 12: ./s: Permission denied
$ ls -l s
-rw------- 1 ct320 class 46 Jan 24 04:15 s
$ chmod +x s
$ ls -l s
-rwx------ 1 ct320 class 46 Jan 24 04:15 s
$ ./s 34 5 12
Sum is 39

if/then/else (numeric)

(( expression )) allows a C-like arithmetic expression as a condition:

cd /etc
let amount=$(ls | wc -l)
if (( amount == 0 ))
then
    echo "$PWD is empty."
elif ((amount==1))
then
    echo "$PWD contains one file."
else
    echo "$PWD contains $amount files."
fi
/etc contains 139 files.

case

Of course, as in most programming languages, there’s also a case construct:

cd /etc
let amount=$(ls | wc -l)
case $amount in
0) echo "$PWD is empty.";;
1) echo "$PWD contains one file.";;
*) echo "$PWD contains $amount files.";;
esac
/etc contains 139 files.

if/then/else (strings)

To compare strings, use [[]]. Unlike (()), spaces are required.

if [[ $HOSTNAME = boston || $HOSTNAME = denver ]]
then
    echo "You are in the CSB 120 lab."
elif [[ $HOSTNAME = *[aeiouy]* ]]
then
    echo "Well, at least $HOSTNAME contains a vowel."
else
    echo "$HOSTNAME confuses me. ☹"
fi
Well, at least parsons contains a vowel.

String comparison

if/then/else (files)

Filename expression example

Note that the operator is =. This does not mean to test for string equality, but, instead, to match the left side against the filename expression that is the right side.

if [[ $OSTYPE = linux* ]]
then
    echo "Great, we’re on something Linuxy: $OSTYPE"
else
    echo "Sorry, but the OS $OSTYPE is not supported."
fi
Great, we’re on something Linuxy: linux-gnu

Regular expression example

The =~ operator uses a regular expression, like the grep command. This is not a filename expression.

when=$(date)
if [[ $when =~ (Tue|Thu).*Aug ]]
then
    echo "Good."
else
    echo "Sorry, this only works on August Tuesdays/Thursdays."
    echo "Current date: $when"
fi
Sorry, this only works on August Tuesdays/Thursdays.
Current date: Wed Jan 24 04:15:54 MST 2018

$?

$ date
Wed Jan 24 04:15:54 MST 2018
$ echo $?
0
$ cp /etc/group xyz
$ echo $?
0
$ cp /etc/superman xyz
cp: cannot stat '/etc/superman': No such file or directory
$ echo $?
1
$ grep 'beet' /etc/hosts
10.1.44.127	beethoven.cs.colostate.edu beethoven
10.1.44.176	beet.cs.colostate.edu beet
$ echo $?
0
$ grep "cowabunga" /etc/hosts
$ echo $?
1
$ echo $?
0

if/then/else (program)

if grep -q "colostate.edu" /etc/resolv.conf
then
    echo "operating at CSU"
else
    echo "must be at home or out & about"
fi
operating at CSU

if/then/else (program)

Here’s the same code, written by someone who doesn’t understand how if/then/else works:

grep -q "colostate.edu" /etc/resolv.conf
if (($? == 0))  # or, worse: [[ $? -eq 0 ]]
then
    echo "operating at CSU"
else
    echo "must be at home or out & about"
fi
operating at CSU

if/then/else (general)

You can combine all three syntaxes using && and ||:

    if ((a>4)) && [[ -r infile ]] && cmp -s infile outfile.$a
    then
        echo "Holy Toledo, that was a miracle!"
    fi

Loops use the same syntax

while loops use the same argument as if. That is, you can have:

while loop example

let n=1
while ((n > 0))
do
    echo "n is $n"
    let n=n*32
done
n is 1
n is 32
n is 1024
n is 32768
n is 1048576
n is 33554432
n is 1073741824
n is 34359738368
n is 1099511627776
n is 35184372088832
n is 1125899906842624
n is 36028797018963968
n is 1152921504606846976

for loops (string-based)

for beatle in John Paul George Ringo
do
    echo "$beatle was one of the Beatles."
done
John was one of the Beatles.
Paul was one of the Beatles.
George was one of the Beatles.
Ringo was one of the Beatles.

for loops (string-based)

for datafile in /bin/*a*e*i*o*
do
    echo Processing $datafile
done
Processing /bin/build-jar-repository
Processing /bin/gtk-update-icon-cache
Processing /bin/makeinfo
Processing /bin/ocamlbyteinfo
Processing /bin/pam-panel-icon
Processing /bin/pkla-check-authorization
Processing /bin/rebuild-jar-repository
Processing /bin/start-pulseaudio-x11
Processing /bin/system-config-authentication
Processing /bin/update-gtk-immodules

for loops (integer based)

A C-like arithmetic loop exists:

let max=5
for ((i=max; i>=0; i--))
do
    echo $i
done
echo "Blast off!"
5
4
3
2
1
0
Blast off!

Why not $max?

Arithmetic Loops

Or:

for i in {5..1}
do
    echo $i
done
echo "Blast off!"
5
4
3
2
1
Blast off!

Arithmetic evaluation

Primary attempt

Let’s try some straightforward code:

$ alpha=2+5
$ beta=alpha*3
$ echo beta
beta

Secondary attempt

Of course, echo beta simply yields “beta”. Perhaps a $ will help to extract the value of the variable:

$ alpha=2+5
$ beta=alpha*3
$ echo $beta
alpha*3

Tertiary attempt

We got the string “alpha” instead of its value. Let’s try $alpha:

$ alpha=2+5
$ beta=$alpha*3
$ echo $beta
2+5*3

A sure sign of guesswork: “let’s try”. Don’t guess. Know.

Quaternary attempt

Use let for arithmetic assignment:

$ alpha=2+5
$ let beta=$alpha*3
$ echo $beta
17

Quinary attempt

The problem was that $alpha literally returned the string 2+5. What is 2+5*3? Let’s avoid $:

$ alpha=2+5
$ let beta=alpha*3
$ echo $beta
21

Senary attempt

To be consistent, and to force arithmetic evaluation every step of the way, use let in the first assignment:

$ let alpha=2+5
$ let beta=alpha*3
$ echo $beta
21

Rules

Let’s boil this down into rules:

Comedy of errors

$ alpha=1
$ beta=2             # no spaces around = 
$ gamma=alpha+beta
$ echo gamma
gamma
$ echo $gamma
alpha+beta
$ delta=$alpha+$beta
$ echo $delta
1+2
$ let epsilon=alpha+beta
$ echo $epsilon
3

Arrays

Somewhat limited, and certainly inelegant, capabilities compared to C:

$ array=(one two three)
$ echo ${array[1]}
two
$ echo ${array[*]}
one two three
$ echo ${#array[*]}
3

Filename Expressions

Filename Expressions Examples

$ cd ~/bin
$ ls
checkin		      grade		  lsf	peek	 tools	   wikigrep
checkin-file-checker  grade-busy	  moss	playpen  u	   wikiupdate
checkin_prog	      grade-file-checker  new	ruler	 unold	   wikiwhence
cls		      grades		  note	run	 wikicat
demo-script	      l			  old	runner	 wikidiff
e		      ll		  p	save	 wikiedit
$ ls ad
/bin/ls: cannot access ad: No such file or directory
$ ls *ad*
grade  grade-busy  grade-file-checker  grades
$ ls [check]in*
/bin/ls: cannot access [check]in*: No such file or directory
$ ls checkin*
checkin  checkin-file-checker  checkin_prog

Regular Expressions

Levels of Regular Expressions

Beware of “levels” of regular expressions.

Obsolete stuff

If you’re writing a shell script that has to work on 20th century antiques, then you might have to use stone knives & bearskins. Otherwise, don’t.

    if [ $PWD = / ]           # ☠☠☠ don’t use single square brackets
    if [[ $PWD = / ]]         # good

    if [[ $n -eq 100 ]]       # ☠☠☠ don’t use [[ … ]] for numbers
    if (($n==100))            # ok
    if ((n==100))             # good

    now=`date`                # ☠☠☠ `backquotes` look too much like 'quotes'
    now=$(date)               # good

    sum=`expr $sum + $zulu`   # ☠☠☠ invoking an external program‽
    sum=$(expr $sum + $zulu)  # ☠☠☠ not quite as bad
    let sum=sum+zulu          # ok
    let sum+=zulu             # better

Script Example

#! /bin/bash
#
# Go into a temporary “playpen” directory; clean it up when done.

# If we can’t execute a file in TMPDIR, then change it to somewhere executable.
script=$(mktemp -t playpen-script-XXXXXX)
chmod u=rx,go= "$script"
"$script" 2>&- || TMPDIR=~/tmp
rm -f "$script"

cd "$(mktemp -d -t playpen-XXXXXX)"	# Create temporary dir.
cp -r ~/.playpen/* . 2>&-		# Files to play with
chmod -R u+rw .				# Works even if no files got copied.
ls -lhog | grep -v '^total '		# Show what’s here.
$SHELL
chmod -R u=rwx .			# Make everything removable.
cd /tmp					# Get away from temporary directory.
rm -rf $OLDPWD				# Remove previous, temporary, directory.

Modified: 2017-12-14T21:31

User: Guest

Check: HTML CSS
Edit History Source
Apply to CSU | Contact CSU | Disclaimer | Equal Opportunity
Colorado State University, Fort Collins, CO 80523 USA
© 2015 Colorado State University
CS Building