CT320: Network and System Administration

Fall 2019

Bash Scripts

Show Lecture.BashScripts 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
/tmp/pmwiki-bash-script7D1O7A: line 14: ./s: Permission denied
$ ls -l s
-rw------- 1 ct320 class 46 Nov 21 09:35 s
$ chmod +x s
$ ls -l s
-rwx------ 1 ct320 class 46 Nov 21 09:35 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 148 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 148 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 beethoven 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: Thu Nov 21 09:35:01 MST 2024

$?

$ date
Thu Nov 21 09:35:01 MST 2024
$ 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
129.82.44.240	dung-beetle.cs.colostate.edu dung-beetle
129.82.45.48	beethoven.cs.colostate.edu beethoven
$ 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 ((UID>0)) && [[ -r /etc/group ]] && ! cmp -s /bin/date /bin/sh
then
    echo "Holy Toledo, that was a miracle!"
fi
Holy Toledo, that was a miracle!

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/c*s*u*
do
    echo Processing $datafile
done
Processing /bin/cimsub
Processing /bin/cinnamon-session-quit
Processing /bin/cinnamon-settings-users
Processing /bin/cinnamon-subprocess-wrapper
Processing /bin/cksum
Processing /bin/clang-pseudo
Processing /bin/clevis-decrypt-null
Processing /bin/clevis-encrypt-null
Processing /bin/clevis-luks-bind
Processing /bin/clevis-luks-common-functions
Processing /bin/clevis-luks-edit
Processing /bin/clevis-luks-list
Processing /bin/clevis-luks-pass
Processing /bin/clevis-luks-regen
Processing /bin/clevis-luks-report
Processing /bin/clevis-luks-unbind
Processing /bin/clevis-luks-unlock
Processing /bin/cmsutil
Processing /bin/cvsbug
Processing /bin/cvtsudoers

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		      curve		  imv	p	 scores   wikidiff
checkin-checker       demo-script	  l	peek	 stats	  wikiedit
checkin-file-checker  domoss		  ll	playpen  tools	  wikigrep
checkin_prog	      e			  lsf	pwget	 u	  wikiupdate
chit		      grade		  moss	ruler	 unold	  wikiwhence
cls		      grade-busy	  new	run	 untar
code		      grade-file-checker  note	runner	 vman
cronedit	      grades		  old	save	 wikicat
$ 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-checker  checkin-file-checker	checkin_prog

Regular Expressions

Levels of Regular Expressions

Beware of “levels” of regular expressions.

Obsolete stuff

made at imgflip.com

Use obsolete features only when dealing with obsolete computers.

if [ $PWD = / ]       # ☠ avoid single [ ]
if [[ $PWD = / ]]     # good

if [[ $n -eq 100 ]]   # ☠ avoid [[ … ]] for numbers
if (($n==100))        # better, but still poor
if ((n==100))         # good

now=`date`            # ☠ `backquotes` resemble 'quotes'
now=$(date)           # good

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

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.