Shell Programming
Init
Interpreter by default starts with #!
#!/bin/bash
Execution of shell script
# method1: use absolute or relavant path (needs +x authority)
./script.sh
# method2: use /bin/sh or /bin/bash
# (no need for +x authority, because bash creates new shell to execute script)
bash ./script.sh
# method3: source or .
# this method will create sub-shell, env variant inherits from upper-shell
source script.sh
. script.sh
Variant
Predefined Variants of System
$HOME, $PWD, $SHELL, $USER, # etc
print all system env varients
> env
> echo $env-var
> printenv
self-define variants
variant=value # note: there is no blankspace besides '='
variant='string exmple'
variant="string exmple"
variant=1+2 # echo variant -> 1+2
variant=$((1+2)) # echo variant -> 3
variant=$[1+2] # echo variant -> 3
readonly variant=value # set variant readonly, which can't be modified or be unset
special variants
$n # stands for n-th args parameter
echo $0 $1 $2
./script.sh 1stparameter 2ndparameter
# script-name 1stparameter 2ndparameter
# special usage
$# # stands for count of parameters
$* # stands for all parameters as one obj
$@ # stands for all parameters as each obj
$? # stands for return value of last execution
here is an example to show the difference between $*
and $@
# shell code
echo "---- \$* ----"
for n in "$*" # note that must use "" to see the difference between $* and $@
do
echo $n
done
echo "---- \$@ ----"
for n in "$@" # note that not use "" there is no difference
do
echo $n
done
# bash command
./script.sh a b c d e
---- $* ----
a b c d e
---- $@ ----
a
b
c
d
e
Operator
arithmetic operators
expr 1 + 2
expr 1 - 2
expr 1 \* 2
expr 1 / 2
popular usage
$((expression)) or $[expression]
expr assignment
variant=$(expr 1 + 2)
variant=`expr 1 \* 2`
variant=$[1 * 2] # no needs add escape character before * in []
new support expr
let n += 1
let n++
# use let before expr can apply normal program language syntax
Test Condition
# normal usage test
var="stringcmp"
test $var = "stringcmp"
echo $?
# easy usage test
var="stringcmp"
[ $var = "stringcmp" ] # note that there are 2 blankspace in [ ]
Besides, there is no <
or >
test condition in shell
TEST CONDITIONS using in shell
-eq equal
-ne not equal
-gt greater than
-lt less than
-le less equal
-ge greater equal
[ $v1 -lt $v2 ]
[ $v1 -gt $v2 ]
[ $v1 -le $v2 ]
[ $v1 -ge $v2 ]
check authority of shell file
[ -r script.sh ]
[ -w script.sh ]
[ -x script.sh ]
check class of files
[ -e file ] # obj exists or not
[ -f file ] # normal file exists or not
[ -d directory ] # is a directory or not
multiple conditions
&&
stands for and
||
stands for or
[ condition1 ] && [ condition2 ] || [ condition3 ]
-a
stands for and
-o
stands for or
[ condition1 -a condition2 -o condition3 ]
Process Control
if
statement
# single branch
if [ condition ];then # ';' spilits the execution of commands
# code here
fi
# or
if [ condition ]
then
# code here
fi
# multiple branch
if [ condition1 ]
then
# code here
elif [ condition2 ]
then
# code here
elif [ condition3 ]
then
# code here
...
else # note that there is no 'then' after else
# code here
fi
case
statement
case $variant in
v1) # case v1
# code here
;; # double ';' to end one branch
v2) # case v2
# code here
;;
v3) # case v3
# code here
;;
...
*) # stands for default
# code here
;;
esac
for
statement
for ((initial;loop-condition;variant-mutation))
do
# code here
done
# example code
sum=0
for (( i=0; i<10; i++ )) # note that in (()) can use < > <= >=
do
sum=$[$sum+$i] # note that $[] to calculate
# if not use $[] will be string concat
done
echo sum=$sum
# other usage
for n in 1 2 3 4 5
do
echo $n
done
sum=0
for n in {1..100} # note that there are only 2 '.'
do
sum=$[$sum+$n]
done
echo $sum
while
statement
while [ condition ]
do
# code here
done
# or
while ((condition))
do
# code here
done
Input
read
statement
-p: sets the prompt for reading the value
-t: Indicates the amount of time (in seconds) to wait for the value to be read. The default is always
read -t 10 -p "input your string (in 10 seconds)" string
Functions
System function call
# example: basename
basename [string / pathname] [suffix]
# basename will remove all prefix-str including '/' in string or path name
# if suffix is indicated then suffix-str in string or pathname will be removed
self-defined function
[function] funcname[()] # parameters are $1, $2, ..., $n
{
# code here
[return int;]
}
We can think of functions as scripts. Some tips here:
- Before call function in shell script, we have to define it first
- Return value of function can only be referenced as
$?
; if addreturn intval
statement in the last line of function, the return value of function isintval
whose range is between 0 to 255.
example of self-define function
function sum_of_nums()
{
sum=0
for n in $@
do
sum=$[$sum+$n]
done
echo $sum
return 0;
}
if [ $# -lt 2 ]
then
sum_of_nums 1 2 3 4 5 # echo 15
else
sum_of_nums $1 $2 # echo $[$1 + $2]
fi
res=$(sum_of_nums $1 $2) # return value to main
Practice
Descriptions: Set a dirname
(there is no '/' in suffix), tar all files in the dirname
everyday, and attach the date after .tar
filename then save to /root/archive
tar
-c: stands for archive
-z: stands for compress, the compressed file suffix is .tar.gz
shell script implementation
# tar synthesis example
if [ $# -ne 1]; then
echo "Usage: $0 <dir>"
exit 1
fi
# get the path of the dir from the first argument
if [ -d $1 ]; then
dir=$(cd $1; pwd)
else
echo "$1 is not a dir"
exit 1
fi
DIR_NAME=$(basename $dir)
DIR_PATH=$dir
# get the current time
DATE=$(date +%Y%m%d)
# set the name of the tar file
FILE=archive_$DIR_NAME_$DATE.tar.gz
# DEST=/root/archive/$FILE # we test in the /tmp dir
DEST=/tmp/archive/$FILE
# create the dir of the tar file if not exists
if [ ! -d $(dirname $DEST) ]; then
mkdir -p $(dirname $DEST)
echo "create dir $(dirname $DEST) successfully"
fi
# start to tar
echo "start to tar $DIR_PATH to $DEST"
tar -zcvf $DEST $DIR_PATH
if [ $? -eq 0 ]; then
echo "tar success"
else
echo "tar failed"
exit 1
fi
exit
# set daily backup
# crontab -e
# 0 0 * * * /root/script.sh /root/dir
# the first 0 is minute, the second 0 is hour,
# the third * is day, the fourth * is month, the fifth * is week
RegEx
normal matching
grep dest-string
special characters
^ : the start of matching
$ : the end of matching
. : any single character
* : combines with pre-one char to math any character any times
[n1-n2,c1-c2] : match range n1 to n2 or c1 to c2
\ : escape character, used like \$, to match $
note that: ^$
will match null. There is no blank space in [n1-n2,c1-c2]
grep -n ^$
will match all null lines
grep ^start.*end$
will match those string that starts with start
and ends with end
grep -E ^1[34578][0-9]{9}$
to match normal phone number, -E
stands for supports extensive usage, grep doesn't support {}
usage by default
String Process
cut
usage
cut [parameters] filename
# separator is tab by default
# example: extracts 2,3 cols from source.txt and the separator for columns is " "
cut -d " " -f 2,3 source.txt
# example: extracts 1,2,3 cols from /etc/passwd for those lines end with bash, and the separator is ":"
cat /etc/passwd | grep bash$ | cut -d ":" -f 1,2,3
# example: extracts 6 col and all cols after it from /etc/passwd for those lines end with bash, and the separator is ":"
cat /etc/passwd | grep bash$ | cut -d ":" -f 6-
# example: cut the IP address
ifconfig ens33 | grep netmask | cut -d " " -f 10
-f : extracts column
-d : indicates separator
-c : splits as char, use -c n to fetch n-th column
awk
usage
awk [parameters] '/pattern1/{action1} /pattern2/{action2}...' filename
# pattern stands for matching pattern
# action stands for execution commands during matching
# first explain the data structure of /etc/passwd
# user-name:password(encrypted):user-id:group-id:comment:user-homedir:shell-interpreter
# match lines in /etc/passwd for those starts with 'root' then output the 7th column of the lines
cat /etc/passwd | awk -F ":" '/^root/{print $7}'
# comparing to grep usage
cat /etc/passwd | grep ^root | cut -d ":" -f 7
# print 1,7 cols
cat /etc/passwd | awk -F ":" '/^root/{print $1", "$7}'
# use variants
awk -v i=2 -F ":" '{print $3+i}'
# inner variants
# FILENAME, NR, NF
awk -F ":" '{print "filename: "FILENAME " row number: "NR " col number: " NF}' /etc/passwd
# print row number of all null lines
ifconfig | awk -F " " '/^$/{print NR}'
-F : indicates separator of input filename
-v : assignment for a self-defined variant
Summary
So this is, the blog is about basic shell programming, including
- Variants
- Expr
- Conditions
- Process Control
- RegEx
- String Process