CT320

CT320: Network and System Administration

Fall 2017

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
/tmp/pmwiki-bash-scriptk9CKN3: line 14: ./s: Permission denied
$ ls -l s
-rw------- 1 ct320 class 46 Nov 22 02:48 s
$ chmod +x s
$ ls -l s
-rwx------ 1 ct320 class 46 Nov 22 02:48 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: Fri Nov 22 02:48:59 MST 2024

$?

$ date
Fri Nov 22 02:48:59 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 ((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/abrt-action-install-debuginfo
Processing /bin/build-jar-repository
Processing /bin/cinnamon-desktop-editor
Processing /bin/cinnamon-file-dialog
Processing /bin/cinnamon-menu-editor
Processing /bin/cinnamon-screensaver-lock-dialog
Processing /bin/cinnamon-session
Processing /bin/cinnamon-session-cinnamon
Processing /bin/cinnamon-session-cinnamon2d
Processing /bin/cinnamon-session-quit
Processing /bin/dbus-update-activation-environment
Processing /bin/gtk-update-icon-cache
Processing /bin/makeinfo
Processing /bin/mariadb-service-convert
Processing /bin/ocamlbyteinfo
Processing /bin/opencv_interactive-calibration
Processing /bin/pam-panel-icon
Processing /bin/papi_mem_info
Processing /bin/papi_version
Processing /bin/papi_xml_event_info
Processing /bin/pipewire-media-session
Processing /bin/pkla-check-authorization
Processing /bin/plasma-browser-integration-host
Processing /bin/plasma-interactiveconsole
Processing /bin/plasma_session
Processing /bin/rebuild-jar-repository
Processing /bin/start-pulseaudio-x11
Processing /bin/tpm2_startauthsession
Processing /bin/UnicodeNameMappingGenerator
Processing /bin/update-gtk-immodules
Processing /bin/upload-system-info
Processing /bin/vala-gen-introspect
Processing /bin/vala-gen-introspect-0.40
Processing /bin/vala-gen-introspect-0.40-x86_64

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

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-09-21T12:39

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