Shell Scripting
WeeSan Lee <weesan@cs.ucr.edu>
http://www.cs.ucr.edu/~weesan/cs183/
Roadmap
Introduction
Prompt & Alias
How to Create a Shell Script?
Sharp-Bang & Comments
Always Use Absolute Pathname!
Command Separator , ||, &&
Backquote/Backtick
Variables
Positional Parameters
Misc.
References
Introduction
Shell
For example
sh, bash, csh, tcsh, kosh, zsh, …
Shell Script
A command interpreter
Whole bunch of Unix commands saved into a text file with
flow control
Your shell scripting skills depends on
# Unix commands you know
how well you put them together to do the right thing
Prompt & Alias
PS1="\u@\h:\w \$ “
\u
\h
\w
\$
the username of the current user
the hostname
the current working directory
if the effective UID is 0, a #, otherwise a $
alias v='ls -l --color=auto‘
~/.bashrc
After editing
$ source ~/.bashrc
How to Create a Shell Script?
Edit hello.sh
Make hello.sh an executable
$ chmod +x hello.sh
Run hello.sh
#!/bin/sh
echo "Hello World!"
$ ./hello.sh
$ sh hello.sh
Debug hello.sh
$ sh -x hello.sh
Sharp-Bang (#!) & Comments
Which shell to run the script?
#!
Sharp-Bang, a two-byte magic number
$ cat hello.sh
#!/bin/sh
# My first shell script
echo "Hello World!”
Always Use Absolute Pathname!
$ cat cal.sh
$ cat cal
#!/bin/sh
cal 2008
#!/bin/sh
/usr/bin/bash
$ cat cal2.sh
#!/bin/sh
CAL=/usr/bin/cal
$CAL 2008
Command Separator, ||, &&
$ cd /var/log ; tail message
$ alias todo='echo ; cat -n ~/TODO ; echo‘
$ cd /var/log || {
echo "Cannot change to /var/log"
exit 1
}
$ cat or.sh
#!/bin/sh
[ -e $1 ] || exit 1
/usr/bin/less $1
$ lpr file.tmp && rm file.tmp
Backquote/Backtick
$ echo "The date is `date`"
$ wget `cat z.txt`
$ for i in `cat kilo.txt`; do
echo “$i has `ssh $i who | wc -l` users”
done
Exit Status
Each Unix command returns a exit status ($?)
Unlike C, 0 means true, false otherwise
$ /bin/true
$ echo $?
0
$ cat abc
$ echo $?
1
$ touch abc
$ cat abc
$ echo $?
0
Exit Status
Return a exit status using “exit” built-in command
$ cat exit2.sh
#!/bin/sh
exit 2
$ exit2.sh
$ echo $?
2
$ exit-1.sh
#!/bin/sh
exit -1
$ echo $?
255
Exit Status
The script returns the status of the last
executable
$ cat exit3.sh
#!/bin/sh
/usr/bin/who | /bin/false
$ exit3.sh
$ echo $?
1
Variables
$ a=3
Double quoting a variable preserves
whitespaces
$ echo '$a'
Single quoting a variable disables var.
referencing
Can assign anything to a variable
$ echo $a
$ echo "$a"
Set a to a null value
$ unset a
$ a=`date`
$ echo $a
$ a=`ls -l`
# run 3 with empty environment a
$ a="a b c"
$ echo $a
$ echo "$a"
run a with =3 as parameter
$ a= 3
$a is a reference to its value
$ a=
$ a =3
Define a variable a
$ echo $a
$ echo ${a}
$ echo "a = $a"
Double quotes preserves formatting
$ echo "'a' is $a"
$ echo "\"a\" is $a"
$ echo "\$a is $a"
Variables
$ cat user.sh
#/bin/sh
NUM="`who | cut -d' ' -f1 | sort | uniq | wc -l`"
echo "`hostname` has $NUM users."
Positional Parameters
$ cat param.sh
$ param.sh `seq 1 10`
#!/bin/sh
echo "Program = $0"
echo "# of parameters = $#"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$10 = ${10}"
echo "\$@ = $@"
echo "\$* = $*"
$
Program = ./param.sh
# of parameters = 10
$1 = 1
$2 = 2
$3 = 3
$10 = 10
$@ = 1 2 3 4 5 6 7 8 9 10
$* = 1 2 3 4 5 6 7 8 9 10
echo $$
Current process ID
Positional Parameters & Soft-link Trick
$ ln -s param.sh newcmd.sh
$ newcmd.sh `seq 1 10`
Program = ./newcmd.sh
# of parameters = 10
$1 = 1
$2 = 2
$3 = 3
$10 = 10
$@ = 1 2 3 4 5 6 7 8 9 10
$* = 1 2 3 4 5 6 7 8 9 10
Branching
Syntax
if <condition>; then
<stmts>
elif <condition>; then
<stmts>
else
<stmts>
fi
How to check if a given path is a file or a
directory?
$ test.sh /etc/foo
$ test.sh /etc/passwd
/etc/foo does not exist.
/etc/passwd is a file.
$ test.sh /etc
/etc is a directory.
How to check if a given path is a file or a
directory?
If [ $# -ne 1 ]; then
echo "Usage: $0 file"
#!/bin/sh
exit 1
if test -e $1; then
fi
if [ -f $1 ]; then
[ $# -eq 1 ] || {
echo "$1 is a file."
echo "Usage: $0 file"
exit 1
elif [ -d $1 ]; then
}
echo "$1 is a directory."
else
echo "$1 has an unknown type."
fi
else
echo "$1 does not exist."
fi
File Test Operators
-e
-f
-s
-d
-b
-c
-L
-r
-w
-x
file exists
regular file
file is not zero size
directory
block device
char. device
symbolic link
file has read permission
write
execute
Comparison Operators
#!/bin/sh
a=1
b=2
if [ "$a" -ne "$b" ]; then
# Numerical comparision: 1 != 2
echo "$a and $b are not equal"
else
echo "$a and $b are equal"
fi
if [ "$a" != "$b" ]; then
# String comparision: "1" != "2”
echo "$a and $b are not equal"
else
echo "$a and $b are equal"
fi
Comparison Operators
Integer Comparison Operators
-eq
-ne
-gt
-ge
-lt
-le
is equal to
is not equal to
is greater than
is greater than or equal to
is less than
is less than or equal to
String Comparison Operators
=
==
!=
-n
-z
is equal to
same as =
is not equal to
string is not null
string is null
-n example
#!/bin/sh
# Wrong, should do [ $1 ]
if [ -n $1 ]; then
echo "\$1 is non-null"
else
echo "\$1 is null"
fi
# Correct
if [ -n "$1" ]; then
echo "\$1 is non-null"
else
echo "\$1 is null"
fi
Case Statement
Syntax
case "$var" in
value1)
<stmts>
;;
value2)
<stmts>
;;
*)
<stmts>
;;
esac
Case Statement - an example
#!/bin/sh
echo -n "Hit a key, then hit Enter: "
read key
case "$key" in
a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)
echo "$key is a lower case."
;;
A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z)
echo "$key is an upper case."
;;
0|1|2|3|4|5|6|7|8|9)
echo "$key is a number."
;;
*)
echo "$key is a punctuation."
;;
esac
Case Statement - a better example
#!/bin/sh
read -n1 -p "Hit a key: " key
echo
case "$key" in
[a-z])
echo "$key is a lower case."
;;
[A-Z])
echo "$key is an upper case."
;;
[0-9])
echo "$key is a number."
;;
*)
echo "$key is a punctuation."
;;
esac
for loop
Syntax
for <var> in <list>; do
<stmts>
done
For example
for i in 1 2 3 4 5 6 7 8 9 10; do
echo $i
done
for i in `seq 1 10`; do
for loop
/usr/*/man gets expanded first!
$ for i in /usr/*/man; do echo $i; done
/usr/bin/man
/usr/csshare/man
/usr/kerberos/man
/usr/local/man
/usr/share/man
while & until loop
while loop syntax
while <condition>; do
<stmts>
done
until loop syntax
until <condition>; do
<stmts>
done
How to keep track if a user has been
Null command always returns true
logged out?
$:
$ echo $?
#!/bin/sh
0
user=manager
Create a zero-sized file
while : ; do
$ : > out.txt
who | grep $user &> /dev/null
$ cat /dev/null > out.txt
if [ $? -ne 0 ]; then
$ touch out.txt
break
else
sleep 30
continue
fi
Do we care about the output?
done
echo "$user has logged out at `date`"
How to keep track if a user has been
logged out?
#!/bin/sh
user=manager
while who | grep $user &> /dev/null; do
sleep 30
done
echo "$user has logged out at `date`"
How to keep track if a user has been
logged in?
#!/bin/sh
user=weesan
until who | grep $user &> /dev/null; do
sleep 30
done
echo "$user has logged in at `date`"
read
#!/bin/sh
while read line; do
echo "$line"
done < /etc/passwd
#!/bin/sh
OIFS=$IFS; IFS=:
while read name passwd uid gid fullname ignore; do
echo "$name ($fullname)"
done < /etc/passwd
IFS=$OIFS
How to find the UID of “weesan”?
Parse /etc/passwd line-by-line
Or, use grep & cut
while IFS=: read name passwd uid gid fullname ignore; do
if [ "$name == "weesan" ]; then
echo $uid
fi
done < /etc/passwd
$ grep weesan /etc/passwd | cut -d: -f3
Or, simply
$ id -u weesan
How to remove “weesan” from
/etc/passwd?
Parse /etc/passwd line-by-line
while IFS=: read name ignore; do
if [ "$name" != "weesan" ]; then
echo "$uid:$ignore”
fi
done < /etc/passwd
Or use grep
$ grep -v weesan /etc/passwd
How to find lines starting or ending with
'1'?
$ cat data.txt
$ grep 1 data.txt
1a
411
$ grep ^1 data.txt
1a
2*
3
411
1a
$ grep '1$' data.txt
411
Command Line Options
$ argv.sh -a -b -c -d 1 -e -f foo.txt
Option a
Option b
Option c
Option d with 1
Option e
Unknown option: -f
The rest is: foo.txt
Command Line Options
$ cat argv.sh
#!/bin/sh
for arg; do
case "$1" in
-a) echo "Option a";;
-b) echo "Option b";;
-c) echo "Option c";;
-d) shift
echo "Option d with $1"
;;
-e) echo "Option e";;
-*) echo "Unknown option: $1";;
*) FILE="$FILE $1";;
esac
shift
done
echo "The rest is: $FILE"
$ cat getopts.sh
#!/bin/sh
while getopts "abcd:e" arg; do
case "$arg" in
a) echo "Option a";;
b) echo "Option b";;
c) echo "Option c";;
d) echo "Option d with $OPTARG";;
e) echo "Option e";;
esac
done
shift $((OPTIND - 1))
echo "The rest is: $1"
Command Line Options
$ getopts.sh -abc -d1 -ef foo.txt
Option a
Option b
Option c
Option d with 1
Option e
getopts.sh: illegal option -- f
The rest is: foo.txt
Here Document
#!/bin/sh
cat <<EOF
This is a here document.
Usually it has multiple lines
and long.
EOF
Basename, dirname
$ dirname /boot/System.map
$ basename /boot/System.map
System.map
$ basename /boot/System.map .map
/boot
System
Good for renaming
$ a="a.txt"
$ tmp="`basename $a .txt`.tmp”
$ grep -v weesan $a > $tmp ; mv $tmp $a
eval
#!/bin/sh
cmd='/usr/bin/grep $string $file'
read -p "File: " file
read -p "String: " string
eval $cmd
Parameter Substitution
#!/bin/sh
echo "\$user is not set"
echo ${user-`whoami`}
$user is not set
weesan
echo "\$user is set"
user="foo"
echo ${user-`whoami`}
$user is set
foo
echo "\$user is set and non-empty"
user="foo"
echo ${user-`whoami`}
echo ${user:-`whoami`}
$user is set and non-empty
foo
foo
echo "\$user is set but empty"
user=
echo ${user-`whoami`}
echo ${user:-`whoami`}
$user is set but empty
weesan
Function Call
#!/bin/sh
start() {
echo "Start"
}
stop() {
echo "Stop"
}
restart() {
echo "Restart"
}
case "$1" in
[Ss]tart) start;;
[Ss]top) stop;;
[Rr]estart) restart;;
*) echo "Usage $0 start|stop|restart";;
esac
Function Call - parameters
#!/bin/sh
foo() {
echo "# of para = $#"
echo "\$0 = $0"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
}
foo 1 2 3
# of para = 3
$0 = ./func2.sh
$1 = 1
$2 = 2
$3 = 3
find
$ find -nouser -xdev
$ find /home -print0 | xargs -0 ls -l
$ find /bin /usr/bin /sbin -perm -4000 -or perm -2000
$ find /bin /usr/bin /sbin -perm -4000 -or perm -2000 | xargs ls -l
$ find /bin /usr/bin /sbin -perm -4000 -or perm -2000 -exec ls -l '{}' \;
References
Advance Bash-Script Guide
http://www.tldp.org/LDP/abs/abs-guide.pdf
Unix Power Tools, 3rd Edition
Shelley Powers, Jerry Peek, Tim O'Reilly, Mike
Loukides