Distributed Systems : Workshop 6

Literature: Advanced Bash Scripting Guide
http://www.tldp.org/LDP/abs/html/index.html

Note for posteriority: For expr, use + as an example rather than *. Just don't make an exercise with * after demonstrating + is harmless and requires no attention.

Update 11.10.12: Added $(()), fixed syntax errors and explicitly brought up $() in context of functions.

This workshop's theme is still shell scripting.

1. Mathematics in shell scripts.

You might have noticed that

echo "3 * 3"

prints out exactly "3 * 3".

In order to do mathematics in shell scripts, we must use 'expr' or bash's $(()). Expr is more general, but $(()) is more elegant.

# Because * is a magic shell character, it has to be protected with quotes or written as \*.
# Note that since expr needs 3 separate parameters (that's why the spaces are required),
# we cannot make the entire "3 * 3" be in quotes here.
echo "$(expr 3 "*" 3)" 

prints out 9. Similarly,

x=3
y="$(expr "$x" "*" 3)"
echo "$y"

prints out 9 and stores the result in variable y.

Note that adding whitespace around "=" does not work for shell scripts,
while for expr, the parameters *must* be separated by whitespace (i.e. 3+3
does not work).

The same using $(())  would go like this:

x=3
y=$((x * 3))  # Note: x, not $x, no need to protect *, and spaces are now _optional_.
echo "$y"

$(()) has the added benefit that it does not start a new shell for executing the expression, so it works
notably faster.

Produce a shell script that sums up variables x and y and prints out the
result. To make the variables visible to the script, use 'export', which makes
the given current shell process's variable visible to all child processes it
starts:

# export x=4
# export y=5
# ./mysumscript.sh

2. Reading files into a script.

If you have a syntactically correct segment of a shell script in a file, one
that for example contains variable settings or command aliases, you can read it
into a shell or another shell script with ".", e.g. ". aliases" to input a file
in the working directory named 'aliases'. That file could for example contain:

alias ls='ls --color=auto'
HOSTNAME1=ukko057

Make a script that first reads the value of a variable (e.g. z) from a file,
then adds 1 to it, prints out and saves the result, overwriting the same file so
that it can be read in again at the next run. Run this script three times to add
3 to the value.
 
3. If, while, when...

Study 'man bash' to study how the syntax of different loops and branching works
for bash shell scripts. Make a script that prints out a success message if two
variables both store an even number, and otherwise notes which variable was odd,
in any meaning of the word (or if both were).

4. Functions in shell scripts.

You can set up functions in shell scripts, which saves you the trouble of
creating a new script to call for repetitive tasks.  Functions take their
parameters similarly to how an independent shell script would: $1 is the first
parameter, $2 the second, etc, and $@ all of them.

# Usage: send Hei ukko067 4567

send()
{
  # Rename the input parameters for extra readability.
  MESSAGE="$1"
  TARGETHOST="$2"
  PORT="$3"

  # Send from multiple hosts causes a race condition (while the software is
  # busy doing something with the message, it does not listen) that makes
  # the send fail every now and then, so retry once a second. To break out
  # of this loop if the send permanently fails, use Ctrl-C.
  while ! echo "$MESSAGE" | nc "$TARGETHOST" "$PORT"
  do
    echo -n . 1>&2   # Write a "." to STDERR (screen) whenever a send fails.
    sleep 1          # Wait one second before trying again.
  done
}

# Usage: receive 4567

receive()
{
  PORT="$1"
  nc -l "$PORT"
}

Add these functions to a shell script and use them to make two nodes
communicate. Feel free to extend the functions where appropriate. (They might
not do what you want them to do exactly, or not even work. ;))
 

Update: You do not necessarily need functions for the exercises we are working on, they're
mostly included for general usefulness.

A way to call external programs and store their output (STDOUT) to a variable is to use the $() we used
before:

HOSTNAME=$(hostname)   # Stores the output of the 'hostname' command to variable $HOSTNAME.
MESSAGE=$(nc -l 1234)  # Stores the text received with netcat through port 1234 to variable $MESSAGE.

$() expresses the same thing as two "backquotes", e.g. `hostname`. $() works at least in bash, but may
not work in all shell variants.