Shell Scripting
A shell script is a file containing a series of commands. The shell reads the file and carries out the commands as if they have been entered directly on the command line.
How to Write a Shell Script
- Write a script. Shell scripts are ordinary files, so they are made with a text editor. Preferably with syntax highlightin.
- Make the script executable.
- Put the script somewhere the shell can find i
Script file format
#!/bin/bash
# This is out first script
echo 'Hello World!'
Executable Permission
chmod 755 hello_world
Script File Location
With the permissions set, we can now execute our script. For the script to run, we must precede the script name with an explicit path.
./hello_world
export PATH=~/bin:"$PATH"
Good Locations for Scripts
- personal use: ~/bin
- everyone: ~/usr/local/bin
- sys admin: ~/usr/local/sbin
More Formatting Tricks
- If a command has both long and short option, use the short option
- If command is long, readability can be enhanced by spreading it over several lines and indenting
- Using line continuation backslash \
variables and constants
variables are mutable, constants are immutable
variable=value
: no spaces in-between name of variable and its value
CONSTANT=VALUE
: CONSTANTS in al caps, if you want to enforce immutability, must make it read only. declare -r CONSTANT=VALUE
parameter expansion
- Use
parameter expansion
if a variable is going be used more than once in a filename="yemane" echo="This is ${name} using path expansion twice with variable ${name}"
Here Document
In the format of ...
command << _EOF_
text
_EOF_
Shell Functions
Shell functions are "mini-scripts" that are located inside other scripts and can act as autonomous programs.
# formal form
function name {
commands
return
}
# simpler and preferred form
name () {
commands
return
}
#!bin/bash
function step2 {
echo "Step 2"
return 0 # shell functions can return an exit status by including an integer argument to the return command
}
# Main program starts here
echo "Step 1"
step2
echo "Step 2"
Local Variables
- Global variables are available throughout the the programm
- Local variables are accessible only within the shell function they are defined in
#!/bin/bash
foo=0 # global variable foo
func () {
local foo # variable foo local to func
foo=1
echo "funct_1: foo = $foo"
}
echo "global: foo = $foo"
func
If Statement
#!/bin/bash
x=5
if ["$x" -eq 5]; then # quotes ensure it is always a string, avoiding errors
echo "x equals 5"
else
echo "x does not equal 5"
fi
test
The test command performs a variety of checks and comparisons. It has two equivalent expression. It returns an exit status of 0 if true, and 1 if false. - test expression - [expression]
Compound command - [[expression]] - string1 =~ regex ; adds regex support
(( ))—Designed for Integers
(( )) is used to perform arithmetic truth tests.
Combining Expressions
Operation | test | [[ ]] and (( )) |
---|---|---|
AND | -a | && |
OR | -o | || |
NOT | ! | ! |
### Control Operators: Another Way to Branch | ||
bash provides two control operators that can perform branching. The && | ||
(AND) and | (OR) operators work like the logical operators in the [[ ]] compound command. |
- With the && operator, command1 is executed, and command2 is executed if, and only if, command1 is successful.
- With the || operator, command1 is executed and command2 is executed if, and only if, command1 is unsuccessful
Reading Keyboard Input
- read assigns fields from standard input to the specified variables.
read [-options] [variable...]
#!/bin/bash
echo -n "Enter one or more values > "
read var1 var2
echo "var1 = '$var1'"
echo "var2 = '$var2'"
''
- an excessive amount of input results in the final variable containing all of the extra input
- - If no variable name is supplied, the shell variable REPLY contains the line of data.
Option | Description |
---|---|
-a | array Assign the input to array, starting with index zero. |
-d | delimiter The first character in the string delimiter is used to indicate the end of input, rather than a newline character. |
-e | Use Readline to handle input. This permits input editing in the same manner as the command line. |
-i | string Use string as a default reply if the user simply presses enter. Requires the -e option. |
-n | num Read num characters of input, rather than an entire line. |
-p | prompt Display a prompt for input using the string prompt. |
-r | Raw mode. Do not interpret backslash characters as escapes. |
-s | Silent mode. Do not echo characters to the display as they are typed. This is useful when inputting passwords and other confidential information. |
-t | seconds Timeout. Terminate input after seconds. read returns a non-zero exit status if an input times out. |
-u | fd Use input from file descriptor fd, rather than standard input. |
IFS
- shell normally performs word splitting using white spaces
- behavior is configured by shell variable IFS
Menus
#!/bin/bash
# read-menu: a menu driven system information program
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "
echo "You have chosen $REPLY"
while loop
while commands; do commands; done
#!/bin/bash
# while-count: display a series of numbers
count=1
while [[ "$count" -le 5 ]]; do
echo "$count"
count=$((count + 1))
done
echo "Finished."
until loop
#!/bin/bash
# until-count: display a series of numbers
count=1
until [[ "$count" -gt 5 ]]; do
echo "$count"
count=$((count + 1))
done
echo "Finished."
Case
case word in
[pattern [| pattern]...) commands ;;]...
esac
Pattern | Description |
---|---|
a) | Matches if word equals a. |
[[:alpha:]]) | Matches if word is a single alphabetic character. |
???) | Matches if word is exactly three characters long. |
*.txt) | Matches if word ends with the characters .txt. |
*) | Matches any value of word. |
- can also use | to combine patterns as an
or
conditional - add the
;;&
notation to terminate each action to enable performing multiple actions
#!/bin/bash
# case4-2: test a character
read -n 1 -p "Type a character > "
echo
case "$REPLY" in
[[:upper:]]) echo "'$REPLY' is upper case." ;;&
[[:lower:]]) echo "'$REPLY' is lower case." ;;&
[[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
[[:digit:]]) echo "'$REPLY' is a digit." ;;&
[[:graph:]]) echo "'$REPLY' is a visible character." ;;&
[[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
[[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
[[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
esac
for loops
-
traditional shell form
for variable [in words]; do commands done
#!/bin/bash for i in 0 1 2 3 4; do echo "$i" done
-
C Language form
for (( expression1; expression2; expression3 )); do commands done
#!/bin/bash
# simple_counter: demo of C style for command
for (( i=0; i<5; i=i+1 )); do
echo $i
done
- expression1 initializes the variable i
- expression2 sets the condition for loop to run until
- expression3 increments the value of i by 1 each time the loop repeats
Positional Parameters
- The shell provides a set of variables called positional parameters that contain the individual words on the command line. The variables are named 0 through 9.
- 0 is script name
- can be accessed like $0 $1 $2 etc.
- determine number of arguments:
$#
- to access many arguments use
shift
- cause each parameter to "move down one" each time it is executed
#!/bin/bash
# posit-param2: script to display all arguments
count=1
while [[ $# -gt 0 ]]; do
echo "Argument $count = $1"
count=$((count + 1))
shift
done
"$@"
Parameter Expansion
- Expansions to Manage Empty Variables
- expansion with defaults to manage empty variables
${parameter:-default}
- expansion with defaults to manage empty variables and assign default value to parameter if used
${parameter:=default}
- exit with an error if parameter is unset
${parameter:?default}
- expand to default if parameter exists, else return nothing
${parameter:+default}
Expansions That Return Variable Names
- to return the names of variables
{!prefix*}
String Operations
- expand into length of string contained in parameter
${#parameter}
- getting a slice of a string
${parameter:offset:length}
- remove portion of string matching a pattern
${parameter#pattern}
or${parameter##pattern}
- a search-and-replace operation
${parameter/pattern/string}
Case Conversion
- using
declare
force a variable to always contain the desired format declare -u upper
declare -l lower
Arithmetic Evaluation and Expansion
$((expression))
Operator | Description |
---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Integer division |
** | Exponentiation |
% | Modulo (remainder) |
Assignment
Notation | Description |
---|---|
parameter = value | Simple assignment. |
parameter += value | parameter = parameter + value. |
parameter++ | parameter = parameter + 1 |
Logic
Operator | Description |
---|---|
<= | Less than or equal to. |
>= | Greater than or equal to. |
< | Less than. |
> | Greater than. |
== | Equal to. |
!= | Not equal to. |
&& | Logical AND. |
|| |
Logical OR. |
expr1?expr2:expr3 | If expr1 then expr2; else expr3 |
Arrays
Limited to one dimension
- single value: name[subscript]=value
- multiple values: name=(value1 value2 ...)
- subscript @
can be used to access every element in an array
- number of array elements: o ${#a[@]}
- find subscript used in array: ${!array[@]}
- appending to an array: array+=value
- delete an array: unset array
- sort an array : ($(for i in "${a[@]}"; do echo $i; done | sort))
- Associative arrays (string indexes): array["string"]=value
Troubleshooting
- Syntactic Errors
- Missing Quotes
- Missing or Unexpected Tokens
- Unanticipated Expansions
- Logical Errors
- Incorrect conditional expressions
- “Off by one” errors
- Unanticipated situations.
Defensive Programming
- Important to verify assumptions when programming, like whether a directory exists before using
cd
Verifying Input
- A general rule of good programming is that if a program accepts input, it must be able to deal with anything it receives.
Testing
- To perform useful testing, it’s important to develop and apply good test cases.
- This is done by carefully choosing input data or operating conditions that reflect edge and corner cases.
Debugging
- find problem area by commenting out sections
- use informative echo messages to trace the flow of program
- examine values during execution using echo