Shell Scripting
Script arguments
$0
The shell script name.
$1 $2 ...
The positional arguments passed into the shell script.
$*
Prints all arguments passed into the script (except
$0), space separated.To change the separator (here: comma), define the
IFS(Internal Field Separator):IFS=, echo "$*"
$#
The amount of arguments passed into a shell script (without
$0).
Variables
local
Define a variable as local. This will overwrite any equally named global variable.
var1="hello" function hi() { local var1 var1="hi" echo "$var1" } hi()will print
hi, nothello.
String Operators
${varname:-word}
If
${varname}exists and isn’tnullit returns its value, otherwise returnsword.
${varname:=word}
If
${varname}exists and isn’tnullit returns its value, otherwise it is set towordand then returned.
${varname:?message}
If
${varname}exists and isn’tnullit returns its value, otherwise printvarnamefollowed bymessageand abort the current command or script (non-interactive shells only).Used to catch errors from undefined variables.
${varname:+word}
If
${varname}exists and isn’tnullit returns itsword, otherwise return null.
${varname:offset: length}
It returns the substring of $varname starting at offset and up to length characters. First character is at position 0. If length is omitted, string is returned until the end. If offset is less than 0, offset is taken from end of varname.
Patterns
Example: some_path=/home/cam/book/long.file.name
Pattern matching operator:
#matches the front (“number signs precede numbers”)%matches the rear (“percent signs follow numbers”)
${variable#pattern}
If the
patternmatches the beginning of the variable’s value, delete the shortest path that matches and return the rest.$ echo ${some_path#/*/} # cam/book/long.file.name
/*/matches everything between two slashes. The shortest pattern match on the front is/home/, which is deleted.
${variable##pattern}
If the
patternmatches the beginning of the variable’s value, delete the longest part that matches and return the rest.$ echo ${some_path##/*/} # long.file.name
/*/matches everything between two slashes. The longest pattern match on the front is/home/cam/book, which is deleted.
${variable%pattern}
If the
patternmatches the end of the variable’s value, delete the shortest part that matches and return the rest.$ echo ${some_path%.*} # /home/cam/book/long.file
.*matches a dot and everything that follows it. The shortest pattern match on the rear is.name, which is deleted.
${variable%%pattern}
If the
patternmatches the end of the variable’s value, delete the longest part that matches and return the rest.$ echo ${some_path%%.*} # /home/cam/book/long
.*matches a dot and everything that follows it. The longest pattern match on the rear islong.file.name, which is deleted.Hint
If exact matches are found, only the first match is deleted:
$ export name=alicece $ echo ${name%%ce} aliceBut if using the wildcard
*, then the longest occurrence will contain everything that follows it:$ export name=alicece $ echo ${name%%ce*} ali
${variable/pattern/string}
The longest match to
patternin variable is replaced bystring. Only the first match is replaced.Ifpatternstarts with a#it must match with the start of the variable.Ifpatternstarts with a%it must match with the end of the variable.Ifstringis null, the matches are deleted.
${variable//pattern/string}
The longest match to
patternin variable is replaced bystring. All matches are replaced.Example: Replace all colons with LINEFEED in PATH variable (print each entry in new line)
$ echo -e ${PATH//:/'\n'}
${#varname}
Returns the length of the value of the variable as a character string
$ export SOME_VAR=123456789 $ echo ${#SOME_VAR} 9
Command substitution
command_A $(command_B) means, to use the return value of a command_B
as a variable for command_A. For example:
# open all txt files in the current directory which contain 'hello'
$ vi $(grep -l 'hello' *.txt)
Directory stack
The directory stack is known as a “last-in, first-out” or LIFO structure. Putting something on the stack is referred to as pushing, taking something off the stack is called popping. The current working directory is always on top of the directory stack.
Pushing and popping allows to move back to previous directories along the directory stack.
pushd <some/path>
Puts the current directory onto the directory stack and
cdinto the directory. The first time,pushdis called in a script or shell session, the current directory is pushed onto the directory stack first.Example:
$ pushd /some/path # cd into /some/path# Directory stack /some/path # top of the stack $(pwd) # directory where pushd was initially called from
popd
Remove the directory at the top of the directory stack, which then reveals a new top, then
cdinto that.Example:
# cd into $(pwd) $ popd# Directory stack $(pwd) # directory where pushd was initially called from
dirs
Prints the current directory stack.
$ dirsIt supports the following options:
Option
Description
-c
Clears the directory stack
-l
Print full path
-p
One directory per line
-v
Prints the directory stack with line numbers
+N
Displays the Nth directory (counting from the left)of the directory stack, starting with zero.-N
Displays the Nth directory (counting from the right)of the directory stack, starting with zero.Important
+Nand-Nare not supported in Zsh.
Flow controls
if/else
if condition
then
statements
[elif condition
then statements...]
[else
statements]
fi
Condition Tests
Square brackets ([]) surround expressions that include various types of operators:
if [ condition_a ]; then
...
elif [ condition_b ]; then
...
else
...
fi
Multiple conditions
# all conditions must be True (AND)
if [ condition ] && [ condition ] ...; then
...
# using the -a operator (AND)
if [ condition_a -a condition_b ... ]; then
...
# at least one codition must be True (OR)
if [ condition ] || [condition ] ...; then
...
# using the -o operator (OR)
if [ condition_a -o condition_b ...]; then
...
Combine condition and command
# Combine command and condition (command must exit with 0)
if command && [ condition ]; then
Negate condition
# condition is not True
if [ ! condition ]; then
...
All conditions must be matches
String Comparisons
Operator |
True if … |
|---|---|
str1 = str2 |
str1 matches str2 |
str1 != str2 |
str1 does not match str2 |
str1 < str2 |
str1 is less than str2 |
str1 > str2 |
str1 is greater than str2 |
-n str1 |
str1 is not null (has length greater than 0) |
-z str1 |
str1 is not null (has length 0) |
File attribute checking
Operator |
True if … |
|---|---|
-a file |
file exists |
-d file |
file exists and is a directory |
-e file |
file exists; same as -a |
-f file |
file exists and is a regular file * |
-r file |
you have read permission on file |
-s file |
file exists and is not empty |
-w file |
you have write permissions on file |
-x file |
you have execution permission on file |
-N file |
file was modified since it was last read |
-O file |
you own file |
-G file |
file’s group IP matches yours ** |
file1 -nt file2 |
file1 is newer than file2 |
file1 -ot file2 |
file1 is older than file2 |
Integer Conditionals
Comparison of two integer values
Test |
Comparison |
|---|---|
-lt |
Less than |
-le |
Less than or equal |
-eq |
Equal |
-ge |
Greater than or equal |
-gt |
Greater than |
-ne |
Not equal |
Usage:
# greater_10.sh
if [ $1 -gt 10 ]; then
echo "Your input is greater than 10"
else
echo "Your input is smaller than or equals 10"
fi
# Running...
./greater_10.sh 20
# Your input is greater than 10
./greater_10.sh 2
# Your input is smaller than or equals 10
for
The for loop is not suited to execute a command for a fixed amount of times, but rather execute over a list a arguments (like a set of files):
# execute for each member of list -> $name
for name [in list]
do
statements tha can use $name
done
If [in list] is omitted, the list defaults to $@ (all command line arguments),
but for readability, it should always be provided.
Example: Print info of all directories in the PATH variable
# Set the IFS (Internal Field Separator) to colon (as used by PATH)
IFS=:
for dir in $PATH
do
ls -ld $dir
done
Example: Print all positional arguments
for filename in "$@"; do
echo "$filename"
done
case
Used to test a variable’s value and execute specific statements depending on the value.
case expression in
pattern1 )
statements ;;
pattern2 )
statements ;;
...
esac
The esac statement end a case block (it actually case backwards).
Example: Case differentiates file suffix
for filename in "$@"; do
pnmfile=${filename%.*}.ppm
case $filename in
*.jpg ) exit 0 ;;
*.tga ) tgatoppm $filename > $pnmfile ;;
*.gif ) giftopnm $filename > $pnmfile ;;
* ) echo "procfile: $filename is an unknown graphic file."
exit 1 ;;
esac
outfile=${pnmfile%.ppm}.new.jpg
pnmtojpeg $pnmfile > $outfile
rm $pnmfile
done
The * ) case matches everything, but is only executed, if no other case
matched before.
Example: Checking the amount of arguments
case "$#" in
0 | 1) echo "Zero or one argument ;;
2 ) echo "Two arguments" ;;
* ) echo "More than two arguments" ;;
esac
Multiple values can be combined via |.
While & until
Allow code to run repetitively until a condition becomes True.
The while syntax:
while [ condition ]; do
statements
done
The condition is actually a list of statements of which the last statement’s
return value is used as the value of the condition.
These are the same:
# loop while condition is True
while [ condition ]; do
# loop until condition becomes False, behavior as with upper while example
until [ ! condition ]; do
So until is not used a lot. A use case for until can be to execute a
failing command until it succeeds:
until commmand; do
statements
done
Example: Loop through all PATH variable entries
path=$PATH # make copy of PATH and append colon at the end
# all entries are separated by colon
while [ $path ]; do # loop until path is empty ("")
ls -ld ${path%%:*} # remove colon at the end of $path
path=${path#*:} # remove current $path from path (colon-separated)
done
Example: Copy file until succeeds
until cp $1 $2; do
echo 'Attempt to copy failed. waiting...'
sleep 5
done
Command Line Options & Typed Variables
Command Line Options
shift
The shift command shifts the argument index by as many times as defined.
For example, shift 3 leads to 1=$4, 2=$5, ..., meaning $1 references the
fourth positional argument, $2 the fifth and so on. The original first three
arguments are shifted out of scope.
This allows for putting command line options before the positional arguments. It only allows for separate, uniform arguments (e.g. single dash).
Example: Single option (-o) script
# my_script.sh
if [ $1 = -o ]; then
shift
echo "We have an option: $1"
# process the -o option
fi
echo "First positional argument: $2"
# normal processing of arguments
# ./my_script.sh -o "foo" "bar"
# We have an option: foo
# First positional argument: bar
shift without any integer number is a shift of 1 index position.
Example: Multiple options
while [ -n "$(echo $1 | grep '-')" ]; do
case $1 in
-a) process option -a ;;
-b) process option -b ;;
-c) process option -c ;;
* ) echo 'Usage: alice [-a] [-b] [-c] args...'
exit 1
esac
shift
done
# normal processing of arguments
Example: Multiple options + options with arguments
while [ -n "$(echo $1 | grep '-')" ]; do
case $1 in
-a) process option -a ;;
-b) process option -b ;;
# $2 is the option's argument
shift ;;
-c) process option -c ;;
* ) echo 'Usage: alice [-a] [-b] [-c] args...'
exit 1
esac
shift
done
# normal processing of arguments
As the -b option has a argument, like -b debug, before proceeding to the
next option (-c), another shift must happen.
getopts
The getopts command allows for
combined options (e.g.
-abc, instead of-a -b -c)option without space in between its argument (e.g.
-barginstead of-b arg)
getopts takes two arguments:
the first is a string which can contain letters and colon. A letter is a valid option, a colon means, the preceding option requires an argument.
the second is a variable name to which each option letter is assigned to (without the dash)
1 # getopts_simple.sh
2 while getopts ":ab:c" opt; do
3 case $opt in
4 a ) echo "processing -a" ;;
5 b ) echo "processing -b"
6 echo "$OPTIND is the option index"
7 echo "$opt is the option"
8 echo "$OPTARG is the option's argument" ;;
9 c ) echo "processing -c" ;;
10 \? ) echo "usage: getopts_simple.sh [-a] [-b barg] [-c] args..."
11 exit 1
12 esac
13 done
14 shift $(($OPTIND - 1))
15 # normal processing of arguments...
16
17 # ./getopts_simple.sh -a -b "foo" -c
18 # 2: processing -a
19 # 4: processing -b
20 # 4 is the option index
21 # b is the option
22 # foo is the option's argument
23 # 5: processing -c
- Line 2:
The first colon (
:) in":ab:c"prevents getopts to print errors in case an illegal option is provided. The-a,-band-coptions are accepted, while-btakes an argument (b:)- Line 8:
$OPTARG… The argument value of the currently selected option- Line 14:
$OPTIND… The index of the next command-line argument to be processed. Aftergetoptsis done, it equals the number of the first “real” argument. It is initialized with 1 and getopts increments it by 1 at the start of processing the next option.- Line 15:
The expression
shift $(($OPTIND - 1))shifts the index to the regular arguments (like$1or$2) -> arithmetic operations
getoptsdoes not rely onshiftstatements to know where it is
Typed Variables
The built-in declare command defines variables for this session or script.
It features the following options:
Option |
Meaning |
|---|---|
-a |
The variables are treated as arrays |
-f |
Use function names only |
-F |
Display function names without definitions |
-i |
The variables are treated as integers |
-f |
Make the variables as read-only |
-x |
Mark the variables for export via the environment |
Running declare without any options prints all variables within this environment.
The command syntax:
declare <option> <variableA=valueA> <variableB=valueB> ...
Integer Variables and Arithmetic
Arithmetic operations are enclosed in
$((...))Variables within arithmetic operations don’t require but can have a preceding dollar sign
Inside double-quotes arithmetic operations are evaluated
Arithmetic operators:
Operator |
Meaning |
|---|---|
++ |
Increment by one (prefix and postfix) |
– |
Decrement by one (prefix and postfix) |
+ |
Plus |
- |
Minus |
* |
Multiplication |
/ |
Division (with truncation) |
% |
Remainder |
** |
Exponentiation |
<< |
Bit-shift left |
>> |
Bit-shift right |
& |
Bitwise and |
| |
Bitwise or |
~ |
Bitwise not |
! |
Logical not |
^ |
Bitwise exclusive or |
, |
Sequential evaluation |
Relational operators:
Operator |
Meaning |
|---|---|
< |
Less than |
> |
Greater than |
<= |
Less than or equal to |
>= |
Greater than or equal to |
== |
Equal to |
!= |
Not equal to |
&& |
Logical and |
|| |
Logical or |
Arithmetic Conditional
Testing of numerical values via operators (similar to string comparisons):
Operator |
Meaning |
|---|---|
-lt |
Less than |
-gt |
Greater than |
-le |
Less than or equal to |
-ge |
Greater than or equal to |
-eq |
Equal to |
-ne |
Not equal to |
Example:
if [ 3 -gt 2 ]; then ...
[ \( 3 -gt 2 \) || \( 4 -le 1 \) ]
Arithmetic Variables and Assignment
The let command allows for assigning the return value of a arithmetic
expression to a variable:
let intvar=expression
The expression does not have to be written within a $((...)) statement.
# Examples
let x=2+5*10
echo $x
# output: 52
Arithmetic for loops
The arithmetic for loop is used to execute a set of statement based on …
for (( initialisation ; ending condition ; update )) do
statements...
done
The initialisation expressions must equal to true for the loop to be started (otherwise it is skipped). If it is entered, the ending condition is evaluated. If it is also true, the loop executes the statements and evaluates the update and repeats the cycle again. The cycle repeats up until the ending condition is false or the loop is exited via the statements.
Example:
for (( i=1; i <= 12 ; i++ )) do for (( j=1 ; j <= 12 ; j++ )) do echo –ne "$(( j * i ))\t" done echo done
Array
An array is a series of numerical indexed elements starting with the index 0.
Important
In zsh shell, the index starts with 1, not 0.
It is initialized as follows:
names=(alice hatter duchess)
The index for each value can be assigned during initialization and may gaps
names_random_indices=([134]=alice [23]=hatter [1]=duchess)
To create an empty array names use
declare -a names
Hint
Make the array read-only by also providing the -r option.
To reference a single element within an array use
echo $names
To reference a value from within an array use the indices (the variables may be
enclosed in {} brackets):
echo $names[0]
# alice
echo $names[100]
# <empty> (as index 100 does not have any value)
To reference all values from within an array use the @ or * special indices
echo $names[@]
# alice hatter duchess
echo ${names[*]}
# alice hatter duchess
Hint
To find out the length of a array value, use the # operator:
echo ${#names[2]}
# 7 (for 'duchess')
To find out the amount of items in an array run:
echo ${#names[@]}
To assign a value to an index of an array run
names[<index>]=<value>
If the index is already occupied, the value is overwritten.
To append a value to the next free index use the += assignment operator
# append single element
names+=rabbit
echo $names
alice hatter duchess rabbit
# append multiple elements
names+=(tweedledee tweedledum)
I/O Redirectors
A comprehensive list of redirectors (many are used by system programmers only):
Redirector |
Function |
|---|---|
cmd1 | cmd2 |
Pipe; take standard output of cmd1
as standard input to cmd2
|
> file |
Direct standard output to file |
< file |
Take standard input from file |
>> file |
Direct standard output to file;
append to file if it already exists
|
>|file |
Force standard output to file even if noclobber is set |
n>|file |
Force output to file from file descriptor n even if noclobber is set |
<>file |
Use file as both standard input and standard output |
n<>file |
Use file as both input and output for file descriptor n |
<<label |
Here-document; see text |
n>file |
Direct file descriptor n to file |
n<file |
Take file descriptor n from file |
n>>file |
Direct file descriptor n to file; append to file if it already exists |
n>& |
Duplicate standard output to file descriptor n |
n<& |
Duplicate standard input from file descriptor n |
n>&m |
File descriptor n is made to be a copy of the output file descriptor |
n<&m |
File descriptor n is made to be a copy of the input file descriptor |
&>file |
Directs standard output and standard error to file |
<&- |
Close the standard input |
>&- |
Close the standard output |
n>&- |
Close the output from file descriptor n |
n<&- |
Close the input from file descriptor n |
n>&word |
If n is not specified, the standard output
(file descriptor 1) is used. If the digits in
word do not specify a file descriptor open for
output, a redirection error occurs. As a special
case, if n is omitted, and word does not expand
to one or more digits, the standard output and
standard error are redirected as described previously.
|
n<&word |
If word expands to one or more digits, the file
descriptor denoted by n is made to be a copy of
that file descriptor. If the digits in word do
not specify a file descriptor open for input,
a redirection error occurs. If word evaluates
to -, file descriptor n is closed. If n is not
specified, the standard input (file descriptor 0)
is used.
|
n>&digit- |
Moves the file descriptor digit to file descriptor
n, or the standard output (file descriptor 1)
if n is not specified
|
n<&digit |
Moves the file descriptor digit to file descriptor
n, or the standard input (file descriptor 0)
if n is not specified. digit is closed after
being duplicated to n.
|
continue at page 163 of learning bash shell