Bash Scriptings

Bash Scriptings

Based on previous materials by Dr. Robert Kline

1. Bash Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

- In your CloudLab experiment, run the following:

~~~bash
sudo su - seed
wget https://cs.wcupa.edu/lngo/assets/src/bash_basics.zip
sudo apt-get update
sudo apt-get install -y unzip
unzip bash_basics.zip
cd bash_basics
ls
~~~

- These scripts will be used to illustrate concepts in the remainder of 
this slide deck.
- There is far too much content in the Bash language to be covered in 
any single document like this one, a tutorial, or even an introductory 
textbook. Inevitably, if you need to write programs in Bash, you will 
have to consult the online manual: [https://linux.die.net/man/1/bash](https://linux.die.net/man/1/bash)

1
bash SOME-SCRIPT.sh
1
echo "hello world"
1
bash hello.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

-	The file itself must be executable by you. 
-	If you are the owner of the script you can add that 
permission with statements like:

~~~bash
chmod +x SOME-SCRIPT.sh          
~~~

or 

~~~bash
chmod 700 SOME-SCRIPT.sh       
~~~

-	The file must either be locatable by its path prefix or have its containing 
directory in the PATH variable. A full path to the script might be: `/usr/local/bin/SOME-SCRIPT.sh` 
-	If the script is in the shell's current directory, this is also a full path: `./SOME-SCRIPT.sh`
-	The file must identify itself as self-executing. 
  - If the first two characters are `#!`, this indicates that the file is a text-based 
  script file, and that the remaining portion of the first line provides the program to 
  run the script. Thus, a Bash script begins with this first line: `#!/bin/bash`

-	Edit and add `#!/bin/bash` to the first line of `hello.sh`

~~~bash
chmod +x hello.sh
./hello.sh
~~~

2. The Bash Language

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

-	When a shell is run interactively the lines of a bash program a
re created one-by-one. 
-	Shell code usually is considers the script to be interactive if the 
prompt variable, PS1 is defined, since all statements receive this prompt 
before entry. 
  - In interactive execution, Bash will source each statement, which is a 
  form of execution in which all variable settings are retained. 
- Interactive execution also permits many user-friendly control features not 
necessary in script execution such as:
  -	line repeat control with up and down arrows
  -	line editing and extension features
  -	tab-based command and filename completion

3. Variables and Values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

-	The most basic operation on strings is concatenation, which, in Bash, is simply juxtaposition. 
In general, whitespace sequences are collapsed into a single blank; whitespace sequences at the 
ends of strings are truncated (i.e., trimmed).
-	Variables are defined using the assign operator `=` in a very strict sort of way. 
  - Once a variable, `v`, is defined, its value is automatically used with the expression `$v`. 
  - A double-quoted variable's value, like `"$y"`, can behave differently from `$y` when the 
  value has internal whitespace. If there is any doubt, it is recommended to always use **double quotes**.
-	A newline is interpreted as a statement terminator. A semicolon (`;`) can also be used as 
a statement terminator if you want two or more statements on the same line.
-	View, then execute scalars.h
-	Observe the corresponding outcomes versus the codes

~~~bash
more scalars.sh
./scalars.sh
~~~

-	Type something and hit Enter to exit this script.

1
2
printf "num=%05d\n" 27
echo AFTER
1
2
printf -v num "%05d" 27
echo $num
1
2
echo   $'\t'foo
printf "\tfoo\n"
1
echo -e "\033[01;31m HELLO \033[0m THERE"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 4. More about Bash


- Bash, just as other languages, does support additional structured data types in the 
form of lists and maps (associative lists). 
- It also provides a way of assigning a type to a variable through a the declare 
statement. View and execute the following script for observation

~~~bash
more scalar-declares.sh
./scalar-declares.sh
~~~

1
2
3
4
$ more args.sh
$ ./args.sh 
$ ./args.sh  a     b    c
$ ./args.sh "a     b"   c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 5. Bash condition


- The bash if-else syntax is unusual compared to other languages. 
The format looks like this:

~~~bash
if ...
then
  some statements
elif ...
  some statements
else
  some statements
fi
~~~

The "..." sections represent boolean "tests". The chained `elif` and the `else` 
parts are optional. The "then" syntax is often written on the same line as the if 
portion like this: `if ...; then`

1
2
3
4
more pingtest.sh
./pingtest.sh 
./pingtest.sh 8.8.8.8
./pingtest.sh 2.2.2.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

- What is considered as boolean expression in an if test uses this syntax:

~~~bash
if [ BOOLEAN-EXPRESSION ]; then
  statements ...
fi
~~~

- The only value regarded as false is the empty string. Bash does not recognize 
any numerical types per se, only strings used in a numerical context. An undefined 
value is, in every way, equivalent to the empty string in Bash. 
- You have to be careful about using an undefined variable in a script since it may 
be an exported variable and, thereby, implicitly defined. You can always explicitly 
undefined a variable `x` by `unset x`. 
- You can verify the values of false by viewing and running this sample script: `falsetest.sh`


~~~bash
more falsetest.sh
./falsetest.sh
~~~

- An example usage is this line in `pingtest.sh`:

~~~bash
[ "$host" ] || { echo usage: $(basename $0) "<host or ip>"; exit 1; }
~~~

- In this example host is the first parameter; if undefined, give a "usage" message.

1
2
3
if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

- The `if` operator (and other tests) can be used with boolean expressions 
using appropriate syntax. 
- The test expressions are normally within single brackets [ .. ]. 
  - There is a single space after `[` and before `]`. 
- Within these we have these operator usages:
  - `=`, `!=`: lexicographic comparison
  - `-eq`, `-ne`, `-lt`, `-le`, `-gt`, `-ge`: numerical comparison
- However both double brackets `[[ .. ]]` and double parentheses `(( .. ))` 
can serve as delimiters. 
- The operators `<` and `>` normally represent *file redirection*, 
but can be used for lexicographic comparison, within `[[ .. ]]` and numerical comparison within `(( .. ))`. 
- You can view and observe some examples from: `test-values.sh`

~~~bash
more test-values.sh
./test-values.sh
~~~

1
2
more errors.sh
./errors.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

-	Bash uses primitive globbing patterns for various matching operations. 
- The most common is the usage of `*` which matches any sequence of characters. 
- Less common is `?` which matches any single character and even less common are 
character sets, such as `[A-Z]` and `[^0-9]`.
-	These type of expressions stand in contrast to more powerful regular expression 
pattern generators which, in Bash, are only available through auxiliary commands. 
- Glob patterns are simple, familiar patterns such as those used commonly in file listing:
  -	`ls *.html`      # all HTML files (not starting with ".")
  -	`ls .??*`        # all dot files except "." and ".."
  -	`ls test[0-3]`   # "test0", "test1", "test2", "test3"
- The Bash `case` statement distinguishes itself from an `if/else` 
constructions primarily by its ability to test its cases by matching 
the argument against glob patterns. The syntax is like this:

~~~bash
case "$file" in
  *.txt)  # treat "$file" like a text file
          ;;
  *.gif)  # treat it like a GIF file
          ;;
  *) # catch-all
     ;;
esac 
~~~

- Unlike C++ or Java syntax, the break exits an enclosing loop, not exit the particular case.

6. Bash loop

Bash has both for and while loops. However, the type of control for these is typically not numerical. The most common looping structure in Bash is the for/in structure like this: for x in … do statements involving $x done

Loops The “…” is a list of things generated in a number of ways. The x is the loop variable which iterates through each item in the list. For example, try running this program in the current directory: $ more fileinfo.sh $ ./fileinfo.sh In this case the things iterated are the files in the current directory. Loops One can use numerical-like looping with the double-parentheses like those in for numerical comparison: for ((i=1; i<=10; ++i)); do echo $i done

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

- The while loop also has an advantage in its ability to read live input. 
For example, this simple program reads and echos input lines:

~~~bash
while read line; do
  echo "$line"
done
~~~

- In a programmatic setting, it is often useful to process lines generated 
from the output of some command. 
- Say we want to process all words starting with `my` in the system 
dictionary (`/usr/share/dict/words`) by removing - the initial `my` part. 
- The following two scripts represent two possible ways of doing so:

~~~bash
more process-lines-1.sh
more process-lines-2.sh
~~~

- The command `grep ^my /usr/share/dict/words` is used to generate the target information. 
- The two respective approaches to processing this are:
  -	input redirection into the `while ... done` loop using the manufactured "input device" `< (grep ^my /usr/share/dict/words)`
  -	piping (i.e., `|`) the command into the "while ... done" loop.
- It turns out that only the former method works as we want it to. The problem with the 
latter method is that the `count` variable is being manipulated in a subshell created by 
the pipe operation and so its value cannot be used upon exiting the while loop. 
  - In contrast, the former method with the odd syntax "<(..)" turns out to be more useful.

1
unzip -q -o FILE.zip -d /usr/local
1
unzip -qo FILE.zip -d /usr/local
1
2
more getopts-test.sh
./getopts-test.sh
1
./getopts-test.sh -q -o FILE.zip -d /usr/local

yields the output:

1
2
3
4
5
6
q 2
o 3
? 3
FILE.zip
d 3 /usr/local
? 3
1
2
./optflags.sh
./optflags.sh -abc foo -d bar foobar barfoo
1
2
3
4
5
6
7
8
9
10
11
12
## 7. More Bash


- The Bash language itself has very unintuitive string-processing operations. 
Later we'll see how to use UNIX commands to do string processing.

~~~bash
more string-processing.sh
./string-processing.sh
~~~

1
2
more functions.sh
./functions.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

-	The Bash language relies heavily on the UNIX-like environment in which it resides in order to create utility scripts. This environment includes many standard UNIX string processing operations such as these:
  -	`sed`: (stream editor) for regular-expression substitution
  -	`grep`: can be used to perform match testing with -c (count) option; the -e option uses regular expression instead of glob patterns
  -	`awk`: captures the fields of a line (separated by whitespace) and does operations on these fields;
  -	`tr`: translate from one list of characters to another; often used to convert case of a string
- `sed`, `grep`, `awk`, and `tr` are used in Bash via standard I/O. All above operations act on text files when given file name as a parameter, or act from standard input with no arguments. 
- A common bash expression which uses an external OPERATION to compute some internal value 
looks something like this: `result="$(echo "input string" | OPERATION)"`
- The pipe operator "|" is crucial for passing the input string to OPERATION via echo. 
The following program illustrates some of these external operations.

~~~bash
more string-operations.sh
./string-operations.sh
~~~