Computer Organization I: CS VT
Computer Organization I: CS VT
Programming languages are generally a lot more powerful and a lot faster than scripting
languages.
Programming languages generally start from source code and are compiled into an
executable. This executable is not easily ported into different operating systems.
A scripting language also starts from source code, but is not compiled into an executable.
Rather, an interpreter reads the instructions in the source file and executes each
instruction.
Interpreted programs are generally slower than compiled programs.
The main advantage is that you can easily port the source file to any operating system.
bash is a scripting language. Some other examples of scripting languages are Perl, Lisp,
and Tcl.
bash scripts are just text files (with a special header line) that contain commands.
myinfo.sh
To execute the script you must first set execute permissions (see below).
Then, just invoke the script as a command, by name:
#! /bin/bash
USER is a global variable maintained by the bash shell; it stores the user name of
whoever's running the shell.
You may create variables local to your shell by simply using them:
VARNAME="value"
#! /bin/bash
message="Hello, world!"
echo $message
Variable names are case-sensitive, alphanumeric, and may not begin with a digit.
bash reserves a number of global variable names for its own use, including:
#! /bin/bash
one=1
two=2
three=$((one + two)) # syntax forces arith. expansion
echo $one
echo $two
echo $three
There are some special variables that can be referenced but not assigned to.
#! /bin/bash
The ability to catch the exit code from a command is useful in detecting errors:
#! /bin/bash
ls –e *
exitcode="$?"
Exit status:
0 if OK,
1 if minor problems (e.g., cannot access subdirectory),
2 if serious trouble (e.g., cannot access command-line argument).
The backslash character (outside of quotes) preserves the literal value of the next
character that follows it:
BTW, note that this also shows we can apply variables from the command prompt.
Single quotes preserve the literal value of every character within them:
Double quotes preserve the literal value of every character within them except the dollar
sign $, backticks ``, and the backslash \:
preamble{comma-separated-list}postfix
`command` or $(command)
Arithmetic computations can be carried out directly, using the syntax for arithmetic
expansion:
$((expression))
Arithmetic computations can be carried out directly, using the syntax for arithmetic
expansion.
The usual C-like precedence rules apply, but when in doubt, parenthesize.
#! /bin/bash add.sh
The example lacks a conditional check for the number of parameters; we will fix that a
bit later...
CS@VT Computer Organization I ©2005-2016 McQuain
Control Structures: if/then Scripting
bash supports several different mechanisms for selection; the most basic is:
. . .
if [[ condition ]]; then
NB: there is an older notation using single square brackets; for a discussion see:
http://mywiki.wooledge.org/BashFAQ/031
CS@VT Computer Organization I ©2005-2016 McQuain
Example Scripting
We can fix one problem with the adder script we saw earlier by adding a check on the
number of command-line parameters:
#! /bin/bash add2.sh
sum=$((left + right))
. . .
if [[ condition ]]; then
. . .
if [[ condition1 ]]; then
commands // condition1
elif [[ condition2 ]]; then
commands // !condition1 && condition2
. . .
else
commands // !condition1 && !condition2 &&...
fi
. . .
#! /bin/bash add3.sh
if [[ $# -lt 2 || $# -gt 4 ]]; then
echo "Invocation: ./add.sh integer integer [integer [integer]] "
exit 1
fi
exit 0
There are a number of expressions that can be used within the braces for the conditional,
for testing files, including:
-e FILE true iff FILE exists
-d FILE true iff FILE exists and is a directory
-r FILE true iff FILE exists and is readable
-w FILE true iff FILE exists and is writeable
-x FILE true iff FILE exists and is executable
-s FILE true iff FILE exists and has size > 0
There are a number of expressions that can be used within the braces for the conditional,
for testing strings, including:
-z STRING true iff STRING has length zero
-n STRING true iff STRING has length greater than zero
There are a number of expressions that can be used within the braces for the conditional,
for testing integers, including:
# Change the values of the variables to make the script work for you:
TARFILE=/var/tmp/mybackup.tar # tar file created during backup
SERVER=ap1.cs.vt.edu # server to copy backup to
REMOTEID=wmcquain # your ID on that server
REMOTEDIR=/home/staff/wmcquain # dir to hold backup on server
LOGFILE=~/logs/backup.log # local log file recording backups
. . .
. . .
while [[ condition ]]; do
# Report GCD:
echo "GCD($1, $2) = $x"
exit 0
. . .
for VALUE in LIST; do
for x in $list; do
str+=" $x"
echo "$str"
done for1.sh
for x; do
echo " $x"
sum=$(($sum + $x));
done bash > ./add4.sh 17 13 5 8 10 73
echo "sum: $sum" 17
13
exit 0 5
8
10
73
sum: 126
A function simply groups a collection of instructions and gives the collection a name.
Parameters may be passed, but in the manner they're passed to a script by the command
shell – the syntax is not what you are used to.
The implementation of a function must occur before any calls to the function.
Variables defined within a function are (by default) accessible outside (after) the function
definition – that’s not what you are used to.
Two syntaxes:
function funcname { funcname() {
commands commands
} }
In the backup script, we have the following block of code to create the archive file:
. . .
# Move into the directory to be backed up
cd $BACKUPDIR
We can wrap this into a function interface, and take the name of the directory to be backed
up and the name to give the tar file parameters to the function…
We can wrap this into a function interface, and take the name of the directory to be backed
up and the name to give the tar file parameters to the function…
. . .
#!/bin/bash
# This script makes a backup of a directory to another server.
# Invocation: ./backup3.sh DIRNAME
#################################################### fn definitions
show_usage() {
echo "Invocation: ./backup2.sh DIRNAME"
}
set_variables() {
# Change the values of the variables to make the script work for you:
TARFILE=/var/tmp/$DIRNAME.tar # tar file created during backup
SERVER=ap1.cs.vt.edu # server to copy backup to
REMOTEID=wmcquain # your ID on that server
REMOTEDIR=/home/staff/wmcquain # dir to hold backup on server
LOGFILE=~/logs/backup.log # local log file recording backups
}
. . .
. . .
create_archive() { # param1: fully-qualified name of dir to
backup
# param2: name for tar file
# Move into the directory to be backed up
cd $1
. . .
copy_to_server() { # param1: fully-qualified name of tar file
# param2: user name on server
# param3: network name of server
# param4: destination dir on server
# Copy the file to another host.
echo "Copying $1 to $3:$4"
scp $1 $2@$3:$4
if [[ $? -ne 0 ]]; then
echo "Error: scp returned error code $?"
exit 4 # terminates script
fi
}
. . .
. . .
rm_archive() { # param1: full-qualified name of tar file
log_backup() {
echo "$1: `date`" >> $2
}
. . .
. . .
#################################################### body of script
. . .
# create the archive file
create_archive $BACKUPDIR $TARFILE
IMO, a good script provides the user with feedback about progress and success or
failure.
In the backup script we need to strip any path information from the front of the fully-
qualified name for the directory to be backed up.
/home/wdm/2505 2505
Since the path prefix must end with a forward slash, this gives us exactly what we want.
There are many characters that have special meaning to the bash shell, including:
These special characters may also occur in contexts, like input strings, in which we need
them to retain their normal meanings...
Enclosing an expression in double quotes causes most, but not all, special characters to be
treated literally:
bash > echo #702
It's usually good practice to enclose a variable evaluation in double quotes, since the
variable may be a string that may contain special characters that are not supposed to be
interpreted by the shell.
${VAR:OFFSET:LENGTH}
Take LENGTH characters from $VAR, starting at OFFSET.
${VAR#WORD}
${VAR##WORD}
If WORD matches a prefix of $VAR, remove the shortest (longest) matching part of
$VAR and return what's left. '%' specifies a match at the tail of $VAR.
bash > echo ${str#mairzy}
doatsanddozydoats
bash > echo ${str%doats}
mairzydoatsanddozy
bash > echo ${var%%/*} %%/* matched everything from the end
NB: sometimes you get a path string from the command-line, and the user may or may
not have put a '/' on the end...
${VAR/TOREPLACE/REPLACEMENT}
${VAR//TOREPLACE/REPLACEMENT}
Replace the first (all) occurrence(s) of TOREPLACE in $VAR with REPLACEMENT.
One problem I needed to solve was that I had a directory of tar files submitted by
students, where each tar file contained the implementation of a program, perhaps
consisting of many files:
bash > ls
aakallam.C3.11.tar dnguy06.C3.6.tar laura10.C3.1.tar samm.C3.5.tar
adahan.C3.5.tar domnap.C3.5.tar lucase93.C3.12 sammugg.C3.4.tar
aemoore.C3.5.tar dustinst.C3.7.tar magiks.C3.8.tar samp93.C3.13.tar
afritsch.C3.11.tar elena.C3.5.tar marcato.C3.5.tar sarahn93.C3.1.tar
What I needed was to extract the contents of each student's submission to a separate
directory, named using the PID field from the name of the student's submission.
#! /bin/bash
#
# Invocation: unpacktars.sh tarFileDir extractionRoot
#
# tarFileDir must name a directory containing tar files
# tar file names are in the form fname.*.tar
# extractionRoot is where the subdirs will go
#
# For each file in the specified tar file directory:
# If the file is a tar file
# - a directory named dirname/fname is created
# - the contents of the tar file are extracted into dirname/fname
#
. . .
##################################### fn to extract PID from file name
# param1: (possibly fully-qualified) name of file
getPID() {
fname=$1
. . .
##################################### fn to extract tar file to subdir
# param1: root dir for subdirs
# param2: full name of file
processTar() {
. . .
############################################################## body
if [[ $# -ne 2 ]]; then
echo "Usage: unpacktars.sh tarFileDir extractRoot"
exit 1
fi
. . .
# get 2nd parameter; trim trailing '/'
trgdir=$2 Target directory may or may not
trgdir=${trgdir%/} already exist...
if [[ ! -e "$trgdir" ]]; then
echo "Creating $trgdir" If it does not, create it.
mkdir "$trgdir" This also detects a regular file
with the specified name.
elif [[ ! -d "$trgdir" ]]; then
echo "Error: $trgdir exists but is not a directory"
exit 2
If a regular file exists with that
fi name, we can't (safely) create a
. . . the directory.
. . .
############################################# begin processing
echo "Processing files in $srcdir to $trgdir"
else
# notify user of stray file
echo " Found non-tar file $tfile"
fi
fi
done
exit 0
[1] A Practical Guide to Linux Commands, Editors, and Shell Programming, 2 nd Ed,
Mark G. Sobell, Pearson, 2010