第三十九章 Shell基础
一、学好Shell编程的基础必备(练、想、再练、再想)
1、vi/vim编辑器的熟练使用,SSH终端及".vimrc"的设置等需要熟练
2、命令基础:Linux的150个常用命令的熟练使用
3、常见Linux网络服务部署及排错。如crond、nfs、rsync、inotify、lanmp、sersync、ssh等
4、基本语法,敲n+1遍。为什么不是n遍那,因为n遍是你刚开始为了编程而努力的几天,也就是说要每天都要写写想想,至少是要看看。
5、各种基本语法,if多种判断都要会,这样做不是为了什么都要学而是为了看懂别人的代码。 这个要写一段时间,各种都用。
6、解决上边说的问题,各种语法都要学的问题,现在是不要做各种语法的程序,与上边相反,形成自己风格,if用一种。
7、从简单做起,简单判断,简单循环.
8、多找几个例子分析一下,不要光看,会了。当你闭上眼睛时候,你还能写出来吗?
9、对于问题分析形成编程思维,就是如果要用到编程的问题,脚本的问题,能不能脑子里首先把大问题分解。
例如关闭不需要服务的脚本,对于这句话的理解分析:
关闭服务首先命令:chkconfig 服务名 --level 345 off
然后服务时多个:多个要用多条,但是分析以上命令出来服务名不同其他都一样,那就会想到循环。
你自己看到这句话能想到这些吗,当你想到了,你的思维就形成了初级的编程思维。
当你看到很大一个问题,然后能分析到一个一个单元,但到大的方面,函数,然后是判断,
循环,然后是命令组合.
你就会了编程,一般的问题,只要让你在机器上调试,就能写出来。
10、编程变量名字规范,驼峰表示,iptTmpAsdfDd
11、初期时候,不要去看大的脚本,要从小问题,从小方面,当你觉得小的方面就是判断,循环等在你脑子里瞬间就能出来时候,在开始大方面。初期最好的学习方法就是多敲和分解问题练习。
12、最高的编程自我感觉是:
问题分析分解快速完整。
完整性:就是判断出各种可能性。
高效率,高性能,1+2+3...+100 =(1+100)*(100/2)/2
二、Shell概述
1、查看Linux系统支持的Shell
cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh
/bin/zsh
/bin/ksh
/bin/mksh
2、查看Linux系统默认Shell
(1)方法一
echo $SHELL
/bin/bash
(2)方法二
grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
3、查看shell版本
bash --version
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
4、sh和bash的区别
ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Nov 4 11:57 /bin/sh -> bash
说明:sh是bash的软链接
5、Shell脚本格式
(1)脚本开头(第一行)
规范的shell脚本的第一行会指出由哪个程序(解释器)来执行脚本中的内容,在Linux bash编程中一般为:
#!/bin/bash或#!/bin/sh
其中开头的"#!"称为幻数,在执行bash脚本的时候,内核会根据"#!"后的解释器来确定由哪个程序来解释脚本中的内容。
(2)格式
一般来说Linux 的系统脚本都是以#!/bin/bash开头,而其它软件的脚本开头就是要看开发者的习惯了,有的以#!/bin/bash开头,有的以#!/bin/sh开头
(3)注意事项
这一行必须在每个脚本的顶端第一行,要在255个字符以内。写在其它行了就是注释了。
CentOS和RedHat默认的shell均是bash,因此,在写shell脚本的时候,在脚本的第一行也可以不加"#!/bin/bash",但如果当前系统默认的shell不是bash时,那么就必须要写#!了。否则脚本的执行结果可能就不是想要的。所以最好的编程习惯,就是不管什么脚本都加上开头语言标识“#!/bin/bash”.
(4)脚本注释
在shell脚本中,跟在"#"后面的内容表示注释。可以单独自成一行,也可跟在命令后,与命令在同一行。一定要有良好的写注释的习惯,因为注释不仅方便他人,也方便自己,防止时间久了,忘记代码意思。
(5)示例
head -1 /etc/rc.d/rc.sysinit
#!/bin/bash
head -1 /etc/init.d/nfs
#!/bin/sh
建议用标准写法:#!/bin/bash
6、Shell脚本的执行
当shell脚本以非交互的方式运行时,它会先查找环境变量ENV,该变量指定了一个环境文件(通常是.bashrca、.bash_profile、/etc/bashrc、/etc/profile等文件),然后从该环境变量文件开始执行,当读取了ENV文件后,shell程序才开始执行shell脚本中的内容。
例外是:crond任务中,需把用到的环境变量要在脚本中重新定义,因为crond中可以识别的环境变量很少。
(1)执行方法
a、方法一:指定bash解释器执行(推荐方法)
bash script-name或shscript-name
b、方法二:全路径或./当前路径下执行
/path/script-name或./script-name
c、方法三:source命令或". "执行(注意.后面有空格)
source script-name或.script-name
(2)三种方法的区别
a、方法一指定bash解释器执行时,不需要脚本文件有+x的可执行权限。通过bash解释器,可直接执行。
b、方法二全路径或当前路径执行时,脚本文件必须要有+x的可执行权限。
c、方法三source命令或". "执行时,不需要脚本文件有+x的可执行权限。
d、第三种方法与前2种方法的主要区别
通过source命令或". "执行时,可以把该脚本中的变量或函数带到当前shell中,也就是就是会将结果加入当前环境变量中,让当前shell可以正常引用。(可以这样理解:正常的shell脚本执行相当于函数的内部局部变量,函数执行完后,局部变量的作用空间就结束了,而source或". "执行时相当于将局部变量变为了全局变量)
所以系统的脚本中全部是用的source命令或". "来执行的
cat /etc/init.d/nfs
#!/bin/sh
。。。
# Source function library.
./etc/rc.d/init.d/functions
(3)示例1
cat test.sh
user=`whoami`
cat zhixi.sh
sh test.sh
echo $user
sh zhixi.sh的执行结果是什么?
解答:返回结果为空
(4)示例2
[ryan@test ~]$ cat test.sh
user=`whoami`
[ryan@test ~]$ sh test.sh && echo $user的执行结果是什么?
解答:返回结果为空
因为sh test.sh只是执行了这个文件,不会将user引入当前环境变量,只有用点"."或source命令执行过的脚本,在脚本结束后脚本中的变量(包括函数)值在当前shell中依然存在,也就是会将结果加入当前环境变量中,而sh和bash则不行,脚本执行完后变量就丢弃了。本题中sh test.sh执行完后只是把当前用户ryan赋给了user,并没有把user=ryan加入到当前环境变量中,所以echo $user的值为空。
如果用. test.sh && echo $user或source test.sh && echo $user来执行,则结果为ryan。
(5)示例3
vi 3.sh
#!/bin/sh
source /etc/init.d/functions
action "This is my Linux Study" /bin/true
sh 3.sh
This is my Linux Study [ OK ]
(6)示例4
vi /etc/init.d/functions
#以结尾加入自定义函数mytestFun()
mytestFun(){
echo " This is myLinux Study! bye! "
}
echo mytestFun >> 3.sh
sh 3.sh
This is my Linux Study [ OK ]
This is my Linux Study! bye!
7、Shell脚本的基本开发规范及习惯
(1)开头指定脚本解释器
#!/bin/bash或#!/bin/sh
(2)开头加版本版权等信息
#Date:21:32 2017-05-03
#Author: Created by myName
#Mail: 12345678@aaa.com
#Function: This scripts function is ....
#Version: 1.1
提示:也可以在配置vim编辑文件时自动加上以上信息,方法是修改~/.vimrc配置文件
(3)脚本中不要用中文注释
(4)脚本要以.sh为扩展名
(5)规范代码书写
a、成对的内容要一次写出来,防止遗漏。
如{}、[]、''、""等。
b、中括号"[]"内两端要有空格。
在书写中括号"[]"时,可先留出空格[ ],然后在退格到中括号内书写内容。
c、流程控制语句要按格式一次书写完,再添加内容。
(i)if语句格式
if 条件内容
then
内容
fi
(i)for语句格式
for
do
内容
done
(iii)while、until、case等语句也是一样
(6)通过缩进让代码易读
三、Shell变量
1、变量分类
变量可分为2类:环境变量(全局变量)和局部变量(本地变量)。
环境变量可以在创建他们的shell及其派生出来的任意子进程shell中使用。局部变量只能在创建他们的shell函数或脚本中使用。
命名规范:
一般是字母、数字、下划线组成,必须以字母开头。
语义要清晰,能够正确表达变量内容的含义,过长的英文单词可采用前几个字符代替。多个单词用"_"连接。
避免无含义的字符或数字。
2、环境变量
(1)概念
环境变量用于定义Shell的运行环境,保证Shell命令的正确执行。所有环境变量都是系统全局变量,可用于所有子进程中。
环境变量可以在命令行中设置,但用户退出时这些变量值也会丢失,因此,最好在用户Home目录下的.bash_profile或全局配置文件/etc/bashrc、/ect/profile,还可以将定义文件放在/etc/profile.d/目录下定义,在每次用户登录时将其初始化。
根据规范,所有环境变量应均为大写。在用于用户进程前,必须用export命令抛出。
使用习惯:一般数字不加引号,其它默认加双引号。
(2)查看系统所有环境变量
a、env命令查看
[root@lamp scripts]# env
HOSTNAME=lamp
TERM=linux
SHELL=/bin/bash
HISTSIZE=1000
。。。
b、set命令查看
[root@lamp scripts]# set
BASH=/bin/bash
...
HISTCONTROL=ignoredups
HISTFILE=/root/.bash_history
HISTFILESIZE=1000
...
(3)临时生效自定义环境变量
a、export命令设置
变量名=value; export 变量名
或
export 变量名=value
b、declare -x命令设置
declare -x变量名=value
c、示例
abc=20
export abc
export abd=30
declare -x abe=40
(4)永久生效自定义环境变量(别名alias也是一样)
a、全局生效(所有用户)
vi /etc/profile 或 vi /etc/bashrc
export abc = 20
b、当前用户生效
vi ~/.bashrc 或 vi ~/.bash_profile
export abc = 20
(5)/etc/profile.d/方式定义环境变量
vi /etc/profile.d/test.sh
echo "Thisis /etc/profile.d/test.sh...."
exportTEST_ABC=30
chmod +x /etc/profile.d/test.sh
logout
回车登入
Last login: SatMay 6 11:53:28 2017 from 192.168.1.11
This is/etc/profile.d/test.sh....
[root@lamp ~]#echo $TEST_ABC
30
(6)显示和取消环境变量
a、通过echo命令打印环境变量
echo $HOME
/root
echo $UID
0
echo $PWD
/root
echo $SHELL
/bin/bash
echo $USER
root
b、通过printf命令打印环境变量(需在结尾加\n,显示的格式比echo丰富)
printf "$HOME\n"
/root
c、通过unset命令取消环境变量(此时不要带$)
echo $TEST_ABC
30
unset TEST_ABC #不能加$
echo $TEST_ABC #值为空
3、局部变量
(1)概念
局部变量又称本地变量,只在用户当前的Shell生存期的脚本中使用。如果在Shell中启动另一个进程或退出,则本地变量的值将无效。
(2)定义局部变量
a、普通字符串变量定义
变量名=value
变量名='value'
变量名="value"
b、命令变量定义
变量名=``
变量名=$()
c、函数中变量定义
local 变量名
local 变量名=value
一定要用local方式进行声明,使之只在本函数作用域内有效,防止变量在函数中命名与变量外部程序中变量重名,造成程序异常。
(3)示例1:命令行输入下列命令返加什么结果
题干:
a=192.168.1.2
b='192.168.1.2'
c="192.168.1.2"
echo "a=$a"
echo "b=$b"
echo "c=${c}"
提示:
$c与${c}在这里是等同的
解答:
a=192.168.1.2
b=192.168.1.2
c=192.168.1.2
小结:
将连接普通字符串的内容赋值给变量,打印变量时,是原样输出。
(4)示例2:命令行输入下列命令返加什么结果
题干:
a=192.168.1.2-$a
b='192.168.1.2-$a'
c="192.168.1.2-$a"
echo "a=$a"
echo "b=$b"
echo "c=${c}"
解答:
a=192.168.1.2-192.168.1.2
b=192.168.1.2-$a
c=192.168.1.2-192.168.1.2-192.168.1.2
小结:
单引号“'”是原样输出,不论引号内有什么,即使引号有变量,也会把变量名原样输出。适用于定义纯字符串。
双引号““”中的内容是会被解析的,将引号中的变量解析成该变量的内容结果输出。适用于字符串中附带有变量的内容的定义。
4、单引号、双引号和无引号的区别
(1)单引号
所见即所得,将单引号内的所有内容都不解析,原样输出。
(2)双引号
输出比引号中的所有内容。如果引号中有命令(反引号中)、变量、特殊转义符等,就会先解析变量,将解析结果输出到最终内容中。
(3)无引号
类似于双引号,把解析结果输出到最终内容中,但如果字符串中带有空格等特殊字符,则不能完整的输出,需要加上双引号。最好用双引号代替无引号。一般脚本中单纯的数字可以不加引号,普通字符串尽量用双引号。
(4)注意事项
对某些语言不适合,如awk内部就有特殊规定(单、双引号正好与shell中相反)。
(5)示例1:awk调用数据型shell变量
[root@mysqldb scripts]# ETT=123
[root@mysqldb scripts]# echo $ETT
123
[root@mysqldb scripts]# awk 'BEGIN {print "$ETT"}'
$ETT <--#没有调用shell变量
[root@mysqldb scripts]# awk 'BEGIN {print '$ETT'}'
123 <--#正确调用shell变量
[root@mysqldb scripts]# awk 'BEGIN {print $ETT}'
<--#结果为空
[root@mysqldb scripts]#
(6)示例2:awk调用字符型shell变量
[root@mysqldb scripts]# ETT='abc'
[root@mysqldb scripts]# echo $ETT
abc
[root@mysqldb scripts]# awk 'BEGIN {print "$ETT"}'
$ETT <--#没有调用shell变量
[root@mysqldb scripts]# awk 'BEGIN {print '$ETT'}'
<--#结果为空
[root@mysqldb scripts]# awk 'BEGIN {print $ETT}'
<--#结果为空
[root@mysqldb scripts]# awk 'BEGIN {print "'$ETT'"}'
abc <--#正确调用shell变量
[root@mysqldb scripts]#
5、Shell特殊变量
(1)位置变量
a、$0:获取当前执行的shell脚本的文件名,如果执行脚本带路径,则包含脚本路径。
(i)示例1
cat 1.sh
echo $0
sh 1.sh
1.sh
sh $(pwd)/1.sh
/wddg/scripts/1.sh
(ii)示例2
cat 2.sh
echo '$0 = '$0
echo 'dirname ='$(dirname $0)
echo 'basename ='$(basename $0)
sh $(pwd)/2.sh
$0 = /wddg/scripts/2.sh
dirname =/wddg/scripts
basename = 2.sh
b、$n:获取当前执行的shell脚本的第n个参数值,n>1,如果n>9时,则需用大括号括起来,如${10}。
(i)示例1
cat 3.sh
echo $1
sh 3.sh
<--#没有参数,结果为空
sh 3.sh aaa
aaa <--#正确返回第一个参数
sh 3.sh aaa bbb
aaa <--#正确返回第一个参数,第二个参数没有接收
sh 3.sh "aaa bbb"
aaa bbb <--#双引号内为一个参数
(ii)示例2
echo $(echo -n 'echo $1' && echo ' $'{2..15}) > 4.sh
cat 4.sh
echo $1 $2 $3 $4$5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15
sh 4.sh {a..z}
a b c d e f g hi a0 a1 a2 a3 a4 a5 <--#$10开始不正确,显示为($1)0了。
echo $(echo -n 'echo $1' && echo -n ' $'{2..9} &&echo ' ${'{10..15}'}') >4.sh
cat 4.sh
echo $1 $2 $3 $4$5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15}
sh 4.sh {a..z}
a b c d e f g hi j k l m n o <--#正确返回
c、$#:获取当前执行的shell命令行中参数的总个数。
(i)示例1
echo 'echo $#' > 5.sh
cat 5.sh
echo $#
sh 5.sh {a..z}
26
(ii)示例2
cat > 6.sh
[ $# -ne 2 ]&& {
echo "musetwo"
exit 1
}
echo 'OK'
^C <--#Ctrl+C
[root@mysqldb scripts]# cat 6.sh
[ $# -ne 2 ]&& {
echo "musetwo"
exit 1
}
echo 'OK'
sh 6.sh
muse two
sh 6.sh aaa bbb
OK
sh 6.sh aaa bbb ccc
muse two
d、$*:获取当前执行的shell的所有参数,但将命令行的所有参数视为一个字符串。相当于"$1$2$3.."。
e、$@:获取当前执行的shell的所有参数,是将命令行的所有参数视为一个个的单个个体,以"$1""$2" "$3" "$4"...形式获取,这是将参数传递给其它程序的最佳方式。
f、$*与$@的区别
(i)示例1:有双引号
set -- "I am" handsome test <--#以set方式来模拟传入3个参数
echo $#
3 <--#当前共传入3个参数
for i in "$*";do echo $i;done
I am handsometest <--#$*将3个参数视为1个参数
for i in "$@";do echo $i;done
I am <--#$*3个参数还是认为是3个参数
handsome
test
(ii)示例2:无引号($*与$@效果一样)
for i in $@;do echo $i;done
I
am <--#将第1个参数“I am”也折分了
handsome
test
for i in $*;do echo $i;done
I
am
handsome
test
g、课外作业-看懂参考博27:linux下set和eval的使用小案例精彩解答
网址:http://oldboy.blog.51cto.com/2561410/1175971
(2)进程状态变量
a、$$:获取当前Shell的进程号(PID)
(i)作用
获取当前Shell的进程号,在企业应用中场景是如果某个脚本只能运行一个进程时,在启动时,需自动kill以前运行的进程。
(ii)示例1
sh 6.sh aaa bbb ccc
muse two
echo $$
2066
(iii)示例2
vi pid.sh
#!/bin/sh
pidpath=/tmp/a.pid
if [ -f"$pidpath" ]
then
kill -USR2 `cat $pidpath`
rm -f $pidpath
fi
echo $$ >$pidpath
sleep 300
b、$!:上一个指令的PID
c、$?:获取上一个指令执行后的返回值(0表示成功,非0表示失败)
(i)示例1
sh 6.sh aaa bbb
OK
echo $?
0
(ii)示例2
su - mysql
ls /root
ls: cannot opendirectory /root: Permission denied
echo $?
2
(iii)返回值参考
返回值 | 表达意义 |
0 | 运行成功 |
1-125 | 运行失败。原因多种多样,如命令错误或参数传递错误、权限拒绝Permission denied等 |
126 | 找到命令,但无法执行 |
127 | 没有找到命令 |
>128 | 命令被系统强制结束。如命令在执行过程中被Ctrl+C中止 |
d、$_:在此之前扫行的命令或脚本的最后一个参数
sh 6.sh aaa bbb ccc
muse two
echo $_
ccc
(3)移动位置变量的命令shift
a、说明
将后面的变量位置依次往前移动。不指定位移量的默认情况下每次前移1个位置。
每执行一次shift命令,都会使所有位置的参数依次向左移动1个位置(默认),并使位置参数$#减1,直至0为止。
作用:就是方便。
b、查看帮助
help shift
shift: shift [n]
Shift positionalparameters.
Rename the positionalparameters $N+1,$N+2 ... to $1,$2 ... IfN is
not given, it is assumedto be 1.
Exit Status:
Returns success unless Nis negative or greater than $#.
c、示例1
for i in "$@";do echo $i;done
I am
handsome
test
shift
for i in "$@";do echo $i;done
handsome
test
shift
for i in "$@";do echo $i;done
test
d、示例2:查看ssh-copy-id命令内容,学习shift命令使用
cat $(which ssh-copy-id)
。。。
if [ "-i" = "$1" ]; then
shift
# check if we have 2parameters left, if so the first is the new ID file
if [ -n "$2" ];then
if expr "$1" :".*\.pub" > /dev/null ; then
ID_FILE="$1"
else
ID_FILE="$1.pub"
fi
shift # and this should leave $1 as the targetname
fi
else
if [ x$SSH_AUTH_SOCK != x ]; then
GET_ID="$GET_IDssh-add -L"
fi
fi
6、Shell内置命令
alias, bg, bind, break,builtin, caller, cd, command, compgen, complete, compopt,
continue, declare, dirs, disown,echo, enable, eval, exec, exit,export, false, fc, fg,
getopts, hash, help, history, jobs, kill, let, local, logout, mapfile, popd,printf, pushd, pwd,read, readonly, return, set, shift, shopt, source, suspend, test, times, trap,true, type, typeset, ulimit, umask,unalias, unset, wait
四、字符串操作
1、字符串操作列表(长度,读取,替换,#是从开头开始,%是从结尾开始匹配)
表达式 | 含义 |
${#string} | $string的长度 |
${string:position} | 在$string中, 从位置$position开始提取子串(从0开始,类似cut -c) |
${string:position:length} | 在$string中, 从位置$position开始提取长度为$length的子串 |
${string#substring} | 从变量$string的开头, 删除最短匹配$substring的子串 |
${string##substring} | 从变量$string的开头, 删除最长匹配$substring的子串 |
${string%substring} | 从变量$string的结尾, 删除最短匹配$substring的子串 |
${string%%substring} | 从变量$string的结尾, 删除最长匹配$substring的子串 |
${string/substring/replacement} | 使用$replacement, 来代替第一个匹配的$substring |
${string//substring/replacement} | 使用$replacement, 代替所有匹配的$substring |
${string/#substring/replacement} | 如果$string的前缀匹配$substring, 就用$replacement来代替匹配到的$substring |
${string/%substring/replacement} | 如果$string的后缀匹配$substring, 就用$replacement来代替匹配到的$substring |
2、演示示例
TEST="This is a cup"
echo ${TEST}
This is a cup
3、示例1:获取字符串长度
(1)方法一:${#string}
echo ${#TEST}
13
(2)方法二:wc -L
echo $TEST | wc -L
13 <--#最准确
echo $TEST | wc -c
14 <--#按字节,多1位
echo $TEST | wc -m
14 <--#按字符,多1位
4、示例2:截取子串
(1)方法一:${string:position},起始位置为0
a、从第2个位置开始截取到结尾
echo ${TEST:2}
is is a cup
b、从第2个位置开始截取2个字符
echo ${TEST:2:2}
is
c、从第2个位置开始截取4个字符
echo ${TEST:2:4}
is i
(2)方法二:cut -c,起始位置为1
a、从第2个位置开始截取到结尾
echo $TEST | cut -c 2-
his is a cup
b、从第2个位置开始截取2个字符
echo $TEST | cut -c 2-4
his
c、从第2个位置开始截取4个字符
echo $TEST | cut -c 2-6
his i
5、示例3:删除子串
(1)示例
echo $DEL
abcABC123ABCabc
(2)方式一:${string#substring},从开头,删除最短匹配的子串
#删除开头最短匹配a*C子串
echo ${DEL#a*C}
123ABCabc
(3)方式二:${string##substring},从开头,删除最长匹配的子串
#删除开头最长匹配a*C子串
echo ${DEL##a*C}
abc
(4)方式三:${string%substring},从结尾 删除最短匹配的子串
#删除开头最短匹配a*c子串
echo ${DEL%a*c}
abcABC123ABC
(5)方式四:${string%%substring},从结尾删除最长匹配的子串
#删除开头最短匹配a*c子串
echo ${DEL%%a*c}
<--#结果为空,全部匹配删除
6、示例4:替换子串
(1)方法一:${string/substring/replacement},从开头替换第一个匹配子串
echo ${DEL/abc/mmm}
mmmABC123ABCabc
(2)方法二:${string/%substring/replacement},从结尾替换第一个匹配子串
echo ${DEL/%abc/mmm}
abcABC123ABCmmm
五、变量操作
1、变量替换表
形式 | 说明 |
${var} | 变量本来的值 |
${var:-word} | 如果变量 var 为空或已被删除(unset),那么返回word,但不改变 var 的值。 |
${var:=word} | 如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。 |
${var:?message} | 如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。 |
${var:+word} | 如果变量 var 被定义,那么返回 word,但不改变 var 的值。用于测试变量var是否存在 |
2、示例1:${var:-word}
echo $result
<--#空,无值
echo $test
<--#空,无值
result=${test:-"aaaa"}
echo $result
aaaa
test='kkkk'
result=${test:-"aaaa"}
echo $result
kkkk
[root@mysqldb ~]#
3、示例2:${var:?message}
echo ${value:?"this var is not defined"}
-bash: value:this var is not defined
value=1
echo ${value:?"this var is not defined"}
1
4、示例3:${var:+word}
r=${value:+1}
echo $r
1
unset value
r=${value:+1}
echo $r
<--#空,无值
5、示例4:${var-word}等同于${var:-word}
http=${HTTPD-/usr/sbin/httpd}
echo $http
/usr/sbin/httpd
HTTPD="/var/https"
http=${HTTPD-/usr/sbin/httpd}
echo $http
/var/https
6、示例5:${var:=word}
echo $HTTPD
<--#空,无值
http=${HTTPD:=/usr/sbin/httpd}
echo $http
/usr/sbin/httpd
echo $HTTPD
/usr/sbin/httpd
7、示例6:查看系统脚本
cat /etc/init.d/httpd
httpd=${HTTPD-/usr/sbin/httpd}
pidfile=${PIDFILE-/var/run/httpd/httpd.pid}
lockfile=${LOCKFILE-/var/lock/subsys/httpd}
8、示例7:防止脚本误删除案例
(1)现象
有很多脚本会调用环境变量,通过环境变量代表的路径来删除文件,但有时环境变量会变其它人删除或不小必替代,导致直接删除根目录或其它目录(为空时,大多数是home目录)下的文件,造成误删除事故。如命令rm -fr $logs/*本是删除log目录下所有文件及文件夹,但当$log为空时,则命令变为rm -fr /*,删除根目录下的所有文件和文件夹了。
(2)原则
有变量路径的操作,必须事先判断路径是否为空,特别是删除操作,高风险。
(3)示例
(i)不严谨方法
path=/tmp
find $path -type f -name "*.log -mtime +7 | xargs rm -f"
(ii)严谨方法
find ${path-/tmp} -type f -name "*.log -mtime +7 | xargs rm-f"
9、示例8:通过取字符串长度测试命令执行时间
(1)取字符串长度测试
chars=`seq -s" " 10`
echo $chars
1 2 3 4 5 6 7 89 10
echo ${#chars}
20
echo ${chars} | wc -L
20
echo ${chars} | wc -m
21
echo $(expr length "$chars")
20
(2)不同方法取字符串长度耗时对比
chars=`seq -s" " 100`
time for i in $(seq 1111);do count=${#chars};done; #最快
real 0m0.204s
user 0m0.179s
sys 0m0.023s
time for i in $(seq 1111);do count=`echo ${chars} | wc -L`;done;
real 0m17.784s
user 0m0.182s
sys 0m16.217s
time for i in $(seq 1111);do count=`echo ${chars} | wc -m`;done; #最慢,因为多个字符
real 0m18.268s
user 0m0.205s
sys 0m16.603s
time for i in $(seq 1111);do count=`echo $(expr length"$chars")`;done;
real 0m17.899s
user 0m0.148s
sys 0m15.849s
(3)结论
一般情况下调用外部命令,与内置功能操作性能相差较大(相差几十到上百倍),所以在shell编程时,应尽量用内置操作或函数来完成。
六、变量的数值计算
1、常用变量的数据计算命令
(1)命令
(())、let、expr、bc、$[]
(2)说明
bc:可能计算浮点数(小数),其它只能计算整数。最常用的是(()),效率也最高。
2、双括号(())命令
(1)示例1:普通计算
((a=1+2**3-4%3)) # **表示幂运算
echo $a
8
b=$((1+2**3-4%3))
echo $b
8
echo $((1+2**3-4%3))
8
(2)示例2:自加计算(变量在前,先输出,后运算;变量在后,先运算,后输出)
echo $((a+=1))
9
echo $a
9
echo $((a++))
9
echo $a
10
echo $((++a))
11
echo $a
11
echo $((a--))
11
echo $a
10
echo $((--a))
9
echo $a
9
(3)示例3:定义变量进行计算
myvar=99
echo $(($myvar + 1))
100
echo $(( $myvar + 1 ))
100
myvar=$(( $myvar + 1 ))
echo $myvar
100
echo $((myvar+1)) # (())中的变量也可以去掉$符号
101
(4)示例4:各种计算
echo $(( 100 + 5)) # 加
105
echo $(( 100 - 5)) # 减
95
echo $(( 100 * 5)) # 乘
500
echo $(( 100 / 5)) # 除
20
echo $(( 100 ** 2)) # 幂
10000
echo $(( 100 % 3)) # 取模,求余
1
(5)示例5:shell脚本
vi test.sh
#!/bin/bash
a=6
b=2
echo "a+b=$(($a + $b))"
echo "a-b=$(($a - $b))"
echo "a*b=$(($a * $b))"
echo "a/b=$(($a / $b))"
echo "a**b=$(($a ** $b))"
echo "a%b=$(($a % $b))"
sh test.sh
a+b =8
a-b =4
a*b =12
a/b =3
a**b =36
a%b =0
(6)示例6:将上述shell脚本改为由命令行获取参数值进行计算
vi test1.sh
#!/bin/bash
a=$1
b=$2
echo "a+b=$(($a + $b))"
echo "a-b=$(($a - $b))"
echo "a*b=$(($a * $b))"
echo "a/b=$(($a / $b))"
echo "a**b=$(($a ** $b))"
echo "a%b=$(($a % $b))"
sh test1.sh 4 3
a+b =7
a-b =1
a*b =12
a/b =1
a**b =64
a%b =1
(7)示例7:实现一个加减乘除的计算器,命令行传参
vi test2.sh
#!/bin/bash
echo $(($1$2$3))
sh test2.sh 3*5 # 等同于$(($1)):参数中间没有空格分隔,相当于$1=3*5 $2和$3为空。
15
sh test2.sh 5/3
1
sh test2.sh 5 + 3 # $1=5 $2="+" $3=3
8
3、let命令
(1)格式
let 赋值表达式 #等同于((赋值表达式))
(2)示例1:let基本用法
i=2
i=i+8
echo $i
i+8
i=2
let i=i+8
echo $i
10
i=2
echo $((i=i+8))
10
(3)示例2:利用let计数监控web服务状态(守护进程)
#监控服务状态
ServerMonitor () {
#服务状态监控
timeout=10
fails=0
success=0
while true
do
/usr/bin/wget --timeout=$timeout--tries=1 http://192.168.20.84/ -q -O /dev/null
if [ $? -ne 0 ]
then
let fails=fails+1
success=0
else
fails=0
let success=1
fi
if [ $success -ge 1 ]
then
exit 0
fi
if [ $fails -ge 2 ]
then
Critical="TMS应用服务出现故障,请紧急处理!"
echo $Critical | mutt -s "服务down" oldboy@etiantian.org
exit
fi
done
}
3、expr命令
(1)说明
expr命令一般用于整数值,但也可用于字符串,用来求表达式变量的值。同时,expr是一个手工命令行计算器。expr命令格式严格,表达式的运算符及计算的数字等各参数前后必须要有空格(多空格也行),且乘号"*"需要"\"转义。
(2)格式
expr Expression
(3)示例1:手工命令行计算器
expr 2 + 2
4
expr 2 + 1
3
expr 2-1 # <--没有空格,当成字符串了
2-1
expr 2 * 3 # <--需要转义
expr: syntaxerror
expr 2 \* 3 # <--需要转义
6
(4)示例2:增量计数
i=0
i=`expr $i + 1`
echo $i
1
(5)示例3:与[]配合。实际是$[]的功能,用echo也一样
expr $[2*3]
6
expr $[2**3]
8
expr $[2+3]
5
expr $[ 2 + 3 ] # <-- expr
5
echo $[ 2 + 3 ] # <-- echo
5
(6)示例4:查看系统脚本中的expr用法
cat `which ssh-copy-id`
ID_FILE="${HOME}/.ssh/id_rsa.pub"
if ["-i" = "$1" ]; then
shift
# check if we have 2 parameters left, if sothe first is the new ID file
if [ -n "$2" ]; then
if expr "$1" : ".*\.pub" >/dev/null ; then
ID_FILE="$1"
else
ID_FILE="$1.pub"
fi
shift # and this should leave $1 as thetarget name
fi
else
if [ x$SSH_AUTH_SOCK != x ] ; then
GET_ID="$GET_ID ssh-add -L"
fi
fi
(7)示例5:判断文件扩展名
expr "test.pub" : ".*\.pub" > /dev/null&& echo 1 || echo 0
1
expr "test.txt" : ".*\.pub" > /dev/null&& echo 1 || echo 0
0
(8)示例6:判断变量是否为整数(技巧)
a、命令行测试
expr 1 + 2
3 # <-- 正常
echo $?
0 # <-- 正常返回值为0
expr 1 + a
expr:non-numeric argument # <-- 报错
echo $?
2 # <-- 错误返回值不为0
b、shell脚本测试
vi expr.sh
expr 1 + $1&>/dev/null
if [ $? -eq 0 ]
then
echo "This is a zhengshu"
else
echo "This isn't a zhengshu"
fi
sh expr.sh 1
This is azhengshu
sh expr.sh a
This isn't azhengshu
sh expr.sh 1.2
This isn't azhengshu
sh expr.sh 100
This is azhengshu
(9)示例7:计算字符串长度
chars=`seq -s " " 100`
echo ${#chars}
291
echo $(expr length "$chars")
291
4、bc命令
(1)说明
bc是UNIX下的计算器,支持小数计算,也可以在命令行下执行。同时,bc支持科学计算。
(2)示例1:命令行计算功能(类似python)
bc
bc 1.06.95
Copyright1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is freesoftware with ABSOLUTELY NO WARRANTY.
For details type`warranty'.
9 - 8
1
5/2
2
5%2
1
(3)示例2:通过管道命令"|"交由bc进行计算
echo 1 + 1 | bc
2
echo 1 + 1 + a | bc
2
echo 1+2 | bc
3
echo 1.1+2 | bc # <-- 支持小数,正常计算
3.1
expr 1.1 + 2 # <-- 不支持小数,报错
expr:non-numeric argument
echo $((1.1+2)) # <--不支持小数,报错
-bash: 1.1+2:syntax error: invalid arithmetic operator (error token is ".1+2")
(4)示例3:通过scale参数设置结果中小数点后的位数(scale只对除法、取余、乘幂有效,对乘法就有问题)
echo "scale=0;5.23*3.13"|bc
16.36 # <-- 乘法,保留0位小数,无效
echo "scale=1;5.23*3.13"|bc
16.36 # <-- 乘法,保留1位小数,无效
echo "scale=2;5.23*3.13"|bc
16.36 # <-- 乘法,保留2位小数,不知是否有效,默认为保留2位
echo "scale=3;5.23*3.13"|bc
16.369 # <-- 乘法,保留3位小数,有效
echo "scale=4;5.23*3.13"|bc
16.3699 # <-- 乘法,保留4位小数,有效
echo "scale=0;5.23/3.13"|bc
1 # <-- 除法,保留0位小数,有效
echo "scale=1;5.23/3.13"|bc
1.6 # <-- 除法,保留1位小数,有效
echo "scale=2;5.23/3.13"|bc
1.67 # <-- 除法,保留2位小数,有效
echo "scale=3;5.23/3.13"|bc
1.670 # <-- 除法,保留3位小数,有效
(5)示例4:通过obase参数进行进制转换
a、十进制8转为二进制
echo "obase=2;8"|bc
1000
b、十进制20转为十六进制
echo "obase=16;20"|bc
14
(6)示例5:通过命令生成表达式,并计算出结果(结果格式:1+2+3+4+5+6+7+8+9+10=55)
a、方法一
echo `seq -s "+" 10` = `seq -s "+" 10 |bc`
1+2+3+4+5+6+7+8+9+10= 55
b、方法二
echo `seq -s "+" 10` = $((`seq -s "+" 10`))
1+2+3+4+5+6+7+8+9+10= 55
c、方法三
echo `seq -s "+" 10` = `seq -s " + " 10 | xargs expr` #第二个" +"加号前后一定要有空格
1+2+3+4+5+6+7+8+9+10= 55
5、typeset命令
typeset -i A=1 B=3
A=A+B # <-- 效率高
echo $A
4
6、$[]命令
echo $[2+3]
5
echo $[ 2 + 3 ]
5
echo $[ 2 + 3 ]
5
echo $[2*3]
6
七、Shell变量的输入(read命令)
Shell变量除了可以直接赋值或脚本传参外,还可以使用read命令从标准输入获得。read是Shell的内置命令,可以通过help read查看帮助。
read从键盘读取变量的值,通常用在shell脚本中与用户进行交互的场合。该命令可以一次读取多个变量的值,变量和输入的值都需要使用空格隔开。在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量REPLY。
(1)格式
read [参数] [变量名]
(2)参数
-p prompt:指定读取值时的提示信息;
-t timeout:指定读取值时等待的时间(秒)。
(3)示例1:-t参数
read -t 5 -p "please input: " a
please input: # <-- 5秒没有任何输入操作,自动退出。这就是-t的作用
[root@my ~]#
(4)示例2:赋值
read -t 5 -p "please input: " a #<--变量a前面一定要有空格
please input: 1
echo $a
1
(5)示例3:批量赋值
read -p "please input two number: " a1 a2
please input two number: 12 13
echo $a1
12
echo $a2
13
echo $a1 $a2
12 13
(5)示例4:批量赋值的echo + read实现
vi echotoread.sh
echo -n"please input two number: "
read a1 a2
echo $a1 $a2
(6)示例5: read方式读入实现 加减乘除读计算的脚本
vim read.sh
#!/bin/bash
read -t 10 -p"pls input": a b
echo "$a-$b=$(( $a - $b ))"
echo "$a+$b=$(( $a + $b ))"
echo "$a*$b=$(( $a * $b ))"
echo "$a/$b=$(( $a / $b ))"
echo "$a**$b=$(( $a ** $b ))"
echo "$a%$b=$(( $a % $b ))"
(7)示例6:综合示例,分别以定义变量、脚本传参、read读入方式比较2个整数大小
a、要求
用条件表达式进行判断,并以屏幕输出方式提醒用户比较结果。当用脚本传参和read读入的方式时,需要对充数量是否为数字做判断。
b、方式一:定义变量
vi var7601.sh
#!/bin/bash
a=1
b=2
[ -z"$a" ] || [ -z "$b" ] &&{
echo "please input two numagain."
exit 1
}
expr $a + 0&>/dev/null
RETVAL1=$?
expr $b + 0&>/dev/null
RETVAL2=$?
test $RETVAL1-eq 0 -a $RETVAL2 -eq 0 || {
echo 'please input two "num"again.'
exit 2
}
[ $a -lt $b ]&& {
echo "$a < $b"
exit 0
}
[ $a -eq $b ]&& {
echo "$a = $b"
exit 0
}
[ $a -gt $b ]&& {
echo "$a > $b"
exit 0
}
c、方式二:脚本传参
vi args7602.sh
#!/bin/bash
a=$1
b=$2
[ $# -ne 2 ] &&{
echo "please input two numagain."
exit 1
}
expr $a + 0&>/dev/null
RETVAL1=$?
expr $b + 0&>/dev/null
RETVAL2=$?
test $RETVAL1-eq 0 -a $RETVAL2 -eq 0 || {
echo 'please input two "num"again.'
exit 2
}
[ $a -lt $b ]&& {
echo "$a < $b"
exit 0
}
[ $a -eq $b ]&& {
echo "$a = $b"
exit 0
}
[ $a -gt $b ]&& {
echo "$a > $b"
exit 0
}
d、方式三:read读入
vi read7603.sh
#!/bin/bash
read -p"please input two number: " a b
[ -z"$a" ] || [ -z "$b" ] &&{
echo "please input two numagain."
exit 1
}
expr $a + 0&>/dev/null
RETVAL1=$?
expr $b + 0&>/dev/null
RETVAL2=$?
test $RETVAL1-eq 0 -a $RETVAL2 -eq 0 || {
echo 'please input two "num"again.'
exit 2
}
[ $a -lt $b ]&& {
echo "$a < $b"
exit 0
}
[ $a -eq $b ]&& {
echo "$a = $b"
exit 0
}
[ $a -gt $b ]&& {
echo "$a > $b"
exit 0
}
sh read7603.sh
please input two number: 1 2
1 < 2
sh read7603.sh
please input two number: 2 2
2 = 2
sh read7603.sh
please input two number: 2 1
2 > 1
sh read7603.sh
please input two number: a
please input twonum again.
sh read7603.sh
please input two number: 1 a
please input two"num" again.
sh read7603.sh
please input two number: a b c
please input two"num" again.
(8)示例7:综合示例,开发shell菜单
a、效果
sh menu.sh
1. [installlamp]
2. [installlnmp]
3. [exit]
please input thenum you want:
当输入1时:
installing.... lamp
当输入2时:
installing.... lnmp
当输入3时:退出脚本
b、解答
menu(){
cat <<END
**************************************
1. [install lamp]
2. [install lnmp]
3. [exit]
please input the num you want:
**************************************
END
read -t 15 a
}
menu
[ $a -eq 1 ]&& {
echo "installing lamp...."
sleep 3
echo "lamp is installed."
menu
}
[ $a -eq 2 ]&& {
echo "installing lnmp...."
sleep 3
echo "lnmp is installed."
menu
}
[ $a -eq 3 ]&& {
exit
}
[ $a -ne 1 -o $a-ne 2 -o $a -ne 3 ] && {
read -p " please input the num (1 2 3)you want: " -t 15 a
}
八、判断字符串是否为数字的多种方法
1、方法一:sed加正则表达式(思路:过滤数字后为空,则都是数字,否则有数字以外的字符)
[ -n "`echo $num|sed 's/[0-9]//g'`" -a -n "`echo$2|sed 's/[0-9]//g'`"] && \
echo "两个参数必须为数字" && exit 1
2、方法二:变量的子串替换加正则表达式(思路:过滤数字后为0,则都是数字,否则有数字以外的字符)
[ -z "`echo '${num//[0-9]}'`" ] && echo 1 || echo0
3、方法三:变量的子串替换加正则表达式(思路:不为空时,过滤非数字部分,如果结果等本身,则都是数字)
[ -n "$num" -a"$num"="${num//[^0-9]/}" ] && echo "it isnum"
-n "$num":表示num不为空
"$num"="${num//[^0-9]/}":去掉num中的非数字部分,判断是否相等,等则为数字,不等则含有其它字符
4、方法四:expr计算判断(思路:把变量和整数相加,看是否成功执行)
expr $1 + 0 >/dev/null 2>&1
[ $? -eq 0 ] && echo int
5、方法五:利符号“=~”来判断
[[ ! $a =~ [0-9] ]] || [[ ! $b =~ [0-9] ]] && {
echo "please input two numbers like : num1 num2"
exit 2
}
6、方法六:bc判断
echo 12|bc
12
echo aa|bc
0
echo aa12|bc
0
echo 2d|bc
(standard_in) 1:syntax error
九、if条件句
1、语法格式
(1)单分支结构
a、格式一
if [条件]
then
命令
fi
b、格式二
if [条件];then
命令
fi
(2)双分支结构
a、格式一
if [条件]
then
命令
else
命令
fi
b、格式二
if [条件];then 命令;else命令;fi
(3)多分支结构
if [条件]
then
命令
elif [条件]\
then
命令
else
命令
fi
2、示例1:单分支比较2个整数大小
(1)方法一:脚本传参
vi if1.sh
#!/bin/bash
if [ $1 -lt $2 ]
then
echo "Yes, $1 is less than $2"
exit
fi
if [ $1 -eq $2 ]
then
echo "Yes, $1 equal $2"
exit
fi
if [ $1 -gt $2 ]
then
echo "Yes, $1 is greater than$2"
exit
fi
sh if1.sh 10 12
Yes, 10 is lessthan 12
(2)方法二:read读入
vi if2.sh
#!/bin/bash
read -p"please input two num: " -t a b
if [ $a -lt $b ]
then
echo "Yes, $a is less than $b"
exit
fi
if [ $a -eq $b ]
then
echo "Yes, $a equal $b"
exit
fi
if [ $a -gt $b ]
then
echo "Yes, $a is greater than $b"
exit
fi
3、示例2:开发shell脚本,如果/server/scripts下有if3.sh,则输出提示,不存在,则创建
vi /service/scripts/findif3.sh
#!/bin/bash
path=/server/scripts
file=if3.sh
if [ ! -d $path]
then
mkdir -p $path
echo "$path dir is not exitst,already created it."
fi
if [ ! -f$path/$file ]
then
touch $path/$file
echo "$path/$file is not exitst,already created it."
fi
echo "ls -l$path/$file"
ls -l$path/$file
4、示例3:开发shell脚本,判断系统剩余内存大小,如低于100M,则发邮件提示,3分钟检查一次
(1)思路
a、查看系统内存的命令
free -m
b、命令结果
total used free shared buffers cached
Mem: 2022 1244 777 0 286 690
-/+ buffers/cache: 267 1755
Swap: 4063 0 4063
c、查看剩余内存
Linux系统剩余内存要看-/+buffers/cache行的free列,因为在Linux系统中如果内存没有使用,则做为缓存使用。所台buffers/cache行的free列才是系统真正剩余内存。
(2)解答步骤
a、第一步:获取剩余内存
(i)方法一
free -m | grep buffers/cache | awk '{print $NF}' # $NF:awk中表示最后一列,还可以$(NF-1)
1755
(ii)方法一
free -m | awk 'NR==3 {print $NF}'
1755
b、第二步:发邮件
(i)sendmail服务要启动
/etc/init.d/sendmail start
(ii)发邮件
mail -s "title" abcd@qq.com < $char
c、第三步:编写脚本
vi /service/scripts/getfree.sh
#!/bin/sh
free_mem=` free-m | awk 'NR==3 {print $NF}'`
if [ $free_mem-lt 100 ]
then
echo "mem is not enough,$free_mem."
echo "mem is not enough,$free_mem." | mail -s "mem waring $(date +%F)" aaa@a.com
fi
d、第四步:编写定进任务
crontab -e
#### This is afree mem ####
*/3 * * * */bin/sh /service/scripts/getfree.sh &>/dev/null
5、示例4:多分支比较2个整数大小
vi if4.sh
#!/bin/bash
if [ $1 -lt $2 ]
then
echo "Yes, $1 is less than $2"
elif [ $1 -eq $2]
then
echo "Yes, $1 equal $2"
else
echo "Yes, $1 is greater than$2"
fi
exit 0
6、示例5:read读入,比较2个整数大小(要求判断输入参数个数和是否是整数)
vi read.sh
#!/bin/bash
#提示输入参数
read -p"please input two num: " -t 10 a b
#判断参数个数
if [ $# -ne 2 ]
then
echo " USAGE:$0 num1 num 2. please input twonumbers."
exit 1
fi
#判断参数是否是整数($a + $b如果不是整数,说明至少有一个不是整数)
expr $a + $b&>/dev/null
if [ $? -ne 0 ]
then
echo "please input two numbers."
exit 2
fi
#进行判断
if [ $a -lt $b ]
then
echo "Yes, $a is less than $b"
exit
fi
if [ $a -eq $b ]
then
echo "Yes, $a equal $b"
exit
fi
if [ $a -gt $b ]
then
echo "Yes, $a is greater than $b"
exit
fi
7、示例6:监控MySQL服务
(1)要求
监控MySQL服务是否正常启动,如果未正常启动,则启动MySQL
(2)演示示例
以多实例MySQL数据库中的3306数据库为例。启动命令/data/3306/mysqlstart
(3)方法一:通过3306端口进行判断
a、初始脚本
vi /server/scripts/judgedb_port.sh
#!/bin/sh
port=`netstat-lntup | grep 3306 | awk -F '[: ]+' '{print $5}'`
if ["$port" != "3306" ];then
/data/3306/mysqlstart
fi
b、存在问题
(i)过滤出 3306端口赋值给port的思路不是最佳的
一但mysql没有启动,port的取值将为空。下面判断时,如果使用整数来判断,则会出现问题。
(ii)进行端口判断时,最好使用字符串进行判断,不要用整数比较,整数比较时,一旦端口不存在则报错
[ "$port" != "3306" ]:字符串判断
[ $port ne 3306 ]:整数比较
(iii)获取端口的过程太复杂,不是最好方法
c、最终脚本(比较好的脚本,思路是将端口号转变为行数)
vi /server/scripts/judgedb_port.sh
#!/bin/sh
port=`netstat-lntup | grep 3306 | wc -l`
if [ $port -ne 1];then
/data/3306/mysqlstart
fi
(4)方法二:通过mysql进程进行判断
a、脚本
vi /server/scripts/judgedb_process.sh
#!/bin/sh
pnum=` ps -ef |grep mysql | grep -v grep | wc -l`
if [ pnum -ne 2];then
/data/3306/mysqlstart
fi
b、存在问题
在使用进程进行判断时,如果脚本中有grep过滤,则一定要保证脚本名称中不能含有grep过滤的内容,否则会导致计数不准确。如
ps -ef | grepmysql | grep -v grep | wc -l 的结果为2
如果脚本名称为judgemysqldb_process.sh,则结果为4
(5)方法三:通过端口和mysql进程进行判断
vi /server/scripts/judgedb_portandprocess.sh
#!/bin/sh
pnum=` ps -ef |grep mysql | grep -v grep | wc -l`
port=`netstat-lntup | grep 3306 | wc -l`
if [ pnum -eq 2] && [ port -eq 1 ]
then
echo "MySQL is running"
else
/data/3306/mysqlstart
fi
(6)方法四:通过-e在命令行执行mysql查询的返回值进行判断
vi /server/scripts/judgedb_cmd.sh
#!/bin/sh
mysql -uroot-p'123456' -e "select version();" &>/dev/null
if [ $? -ne 0];then
/data/3306/mysqlstart
fi
(7)方法五:通过php/java程序url连接进行判断
#php
$link_id=mysql_connect('localhost','root','123456') ormysql_error();
if($link_id){
echo "mysqlsuccessful"
}else{
echo mysql_error();
}
十、条件测试表达式
1、条件测试语法
(1)格式一:test 测试表达式
(2)格式二:[测试表达式]
(3)格式三:[[测试表达式]]
2、语法说明
(1)格式一和格式二是等价的。格式三是扩展的test命令,有网友推荐用格式三,实际上格式无好坏,看个人习惯。
(2)在[[]]中可以使用通配符进行模式匹配。&&、||、>、<等操作符。但不能应用于[]中。
(3)对整数进行关系运算时,也可以使用shell的(())算术运算符。
3、查看帮助
man test
4、test判断示例
(1)示例1:测试文件是否存在
test -f file&& echo 1 || echo 0
0
touch file
test -f file&& echo 1 || echo 0
1
(2)示例2:非“!”的用法
test ! -f file && echo 1 || echo 0
0
(3)示例3:-z 参数0值判断(判断长度为0)
arg=
test -z"$arg" && echo 1 || echo 0
1
arg="aaaaa"
test -z"$arg" && echo 1 || echo 0
0
(4)示例4:-n 参数的非0值判断(判断长度不为0)
arg=
test -n"$arg" && echo 1 || echo 0
0
arg="aaaaa"
test -z"$arg" && echo 1 || echo 0
1
5、中括号“[]”判断示例
(1)示例1:测试文件是否存在
[ -f file ]&& echo 1 || echo 0
0
touch file
[ -f file ]&& echo 1 || echo 0
1
(2)示例2:非“!”的用法
[ ! -f file ] && echo 1 || echo 0
0
6、双中括号“[[]]”判断示例
(1)示例1:测试文件是否存在
[[ -f file ]]&& echo 1 || echo 0
0
touch file
[[ -f file ]]&& echo 1 || echo 0
1
(2)示例2:非“!”的用法
[[ ! -f file ]] && echo 1 || echo 0
0
(3)示例3:[[]]中有&&、||等操作符的用法
[[ ! -f file && -d dir]] &&echo 1 || echo 0
0
touch file
[[ ! -f file && -d dir]] &&echo 1 || echo 0
0
[[ ! -f file || -d dir]] && echo 1|| echo 0
1
mkdir dir
[[ ! -f file && -d dir]] &&echo 1 || echo 0
1
7、常用判断示例:以中括号来演示
(1)模拟环境
pwd
/wddg-data/scripts
mkdir 03
cd 03
touch oldboy
(2)测试-f参数
[ -f oldboy ]&& echo 1 || echo 0
1
mkdir oldgirl
[ -f oldgirl ]&& echo 1 || echo 0
0
(3)测试-e参数
[ -e oldgirl ]&& echo 1 || echo 0
1
(4)测试-d参数
[ -d oldgirl ]&& echo 1 || echo 0
1
[ -d oldboy ]&& echo 1 || echo 0
0
(5)测试-r、-w、-x参数(root用户比较特殊,没有权限,也可以读写)
chmod 000 oldboy
ll oldboy
---------- 1root root 0 May 29 12:56 oldboy
[ -r oldboy ]&& echo 1 || echo 0
1
[ -x oldboy ]&& echo 1 || echo 0
0
[ -w oldboy ]&& echo 1 || echo 0
1
su - oracle
[ -r oldboy ]&& echo 1 || echo 0
0
[wddg@myCentOS03]$ [ -x oldboy ] && echo 1 || echo 0
0
[wddg@myCentOS03]$ [ -w oldboy ] && echo 1 || echo 0
0
十一、字符串测试操作符
1、作用
比较2个字符串是否相同、测试字符串长度是否为零、测试字符串是否为NULL(bash区分零长度字符串和空字符串)等
2、注意事项
(1)在字符串判断中,“=”和“==”是等价的,都是比较两个字符串是否相同,但最好是用“==”,因为在其它地方“=”表示的是赋值。
(2)变量最好是用双引号“”括起来(单引号也行),如“aaa”、“$a”等,因为如果中间有空格、*号等符号时,就可能出错了。最好的方法是["${a}"="${b}"]。
(3)字符串比较,比较特号两端最好有空格。如果没有空格,有时候会导致结果不正确。
(4)多参考系统脚本。
sed -n '30,31p'/etc/init.d/network
# Check thatnetworking is up.
["${NETWORKING}" = "no" ] && exit 6 #等号两边有空格
3、常用字符串操作符
操作符 | 说明 |
-z "字符串" | 如果字符串长度为0,表达式值则为真。-z表示zero |
-n "字符串" | 如果字符串长度不为0,表达式值则为真。-n表示no zero |
"字符串1" = "字符串2" | 如果字符串1等于字符串2,表达式值则为真。最好是用“==”代替“=” |
"字符串1" != "字符串2" | 如果字符串1不等于字符串2,表达式值则为真。最好是用“==”代替“=” |
4、示例
[ -n "abc" ] && echo 1 || echo 0
1
[ -n "" ] && echo 1 || echo 0
0
test="abcd"
[ -n "$test" ] && echo 1 || echo 0
1
[ -n $test ] && echo 1 || echo 0 #没有用双引号“”将变量括起来,有时会不对。
1
test=""
[ -n $test ] && echo 1 || echo 0 #此处,变量为空,但没有没有用双引号“”将变量括起来,结果不对。
1
[ "$test" = "abc" ] && echo 1 || echo 0
0
[ "$test" = "abcd" ] && echo 1 || echo 0
0
[ "abcd" = "abcd" ] && echo 1 || echo0
1
test="abcd"
[ "$test" = "abcd" ] && echo 1 || echo 0
1
十二、整数二元比较操作符
1、常用整数操作符
在[]中使用的比较符 | 在(())和[[]]中使用的比较符 | 说明 |
-eq | == | 等于,equal的缩写 |
-ne | != | 不相等,not equal的缩写 |
-gt | > | 大于,greater than的缩写 |
-ge | >= | 大于等于,greater equal的缩写 |
-lt | < | 小于,less than的缩写 |
-le | <= | 小于等于,less equal的缩写 |
说明:如果[]中想使用在(())和[[]]中使用的比较符,除“=”和“!=”外,其它需要用"\"转义。麻烦,最好不用。 |
2、示例
[ 12 -eq 13 ] && echo 1 || echo 0
0
[ 12 -ne 13 ] && echo 1 || echo 0
1
[ 12 -gt 13 ] && echo1 || echo 0
0
[ 12 -lt 13 ] && echo 1 || echo 0
1
[ 12 < 13 ] &&echo 1 || echo 0 #报错,需转义
-bash: 13: No suchfile or directory
0
[ 12 \< 13 ] && echo 1 || echo 0 #转义,麻烦
1
[[ 12 < 13 ]] && echo 1 || echo 0
1
[ 12 = 13 ] && echo 1 || echo 0 #等号“=”可以不转义,最好不这样用,遵循标准
0
[ 12 \= 13 ] && echo 1 || echo 0
0
[ 12 != 13 ] && echo 1 || echo 0 #不等号“!=”可以不转义,最好不这样用,遵循标准
1
[ 12 \!= 13 ] && echo 1 || echo 0
1
(( 12 > 12 )) && echo 1 || echo 0
0
(( 12 = 12 )) && echo 1 || echo 0 #等于时,最好使用“==”,否则容易发生错误。
-bash: ((: 12 =12 : attempted assignment to non-variable (error token is "= 12 ")
0
(( 12 == 12 )) && echo 1 || echo 0
1
十三、逻辑操作符
1、常用整数操作符
在[]中使用的比较符 | 在(())和[[]]中使用的比较符 | 说明 |
-a | && | 与,and |
-o | || | 或,or |
! | ! | 非,not |
2、示例
f1=/etc/rc.local
f2=/etc/services
[ -f "$f1" -a -f "$f2" ] && echo 1 ||echo 0
1
[ -f "$f1" -a -f "$f2" ] && echo 1 ||echo 0
1
[[ -f "$f1" && -f "$f2" ]] &&echo 1 || echo 0
1
[[ -f "$f1" && -f "$f2" ]] &&echo 1 || echo 01
[ -n "$f1" -a -z "$f2" ] && echo 1 ||echo 0
0
[ -n "$f1" || "$f1" = "$f2" ] && echo 1 || echo0 #报错,[]不能用||
-bash: [:missing `]'
1
[[ -n "$f1" || "$f1" = "$f2" ]] && echo 1 || echo0
1
[ -n "$f1" -o "$f1" = "$f2" ] && echo 1 || echo0 #字符串内容比较
1
echo ${#f1} #求字符串长度
13
echo ${#f2}
13
[ -n "$f1" -a "${#f1}" = "${#f2}" ] && echo 1 ||echo 0 #字符串长度比较
1
十四、学习系统脚本
(1)/etc/init.d/nfs
cat /etc/init.d/nfs
#!/bin/sh
# Source functionlibrary.
./etc/rc.d/init.d/functions
# Sourcenetworking configuration.
[ -f/etc/sysconfig/network ] && ./etc/sysconfig/network
# Check for andsource configuration file otherwise set defaults
[ -f/etc/sysconfig/nfs ] && . /etc/sysconfig/nfs
# Remote quotaserver
[ -z"$RQUOTAD" ] && RQUOTAD=`type -path rpc.rquotad`
RETVAL=0
uid=`id | cut-d\( -f1 | cut -d= -f2`
# See how wewere called.
case"$1" in
start)
# Check that networking is up.
[ "${NETWORKING}" !="yes" ] && exit 6
[ -x /usr/sbin/rpc.nfsd ] || exit 5
[ -x /usr/sbin/rpc.mountd ] || exit 5
[ -x /usr/sbin/exportfs ] || exit 5
# Make sure the rpc.mountd is notalready running.
if status rpc.mountd > /dev/null ;then
exit 0
fi
。。。。。。
(2)/etc/init.d/crond
cat /etc/init.d/crond
#!/bin/sh
[ -f/etc/sysconfig/crond ] || {
[ "$1" = "status" ]&& exit 4 || exit 6
}
......
[ $UID -eq 0 ]&& [ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
start() {
if [ $UID -ne 0 ] ; then
echo "User has insufficientprivilege."
exit 4
fi
[ -x $exec ] || exit 5
[ -f $config ] || exit 6
......
}
(3)/etc/rc.d/rc.sysinit
cat /etc/rc.d/rc.sysinit
#!/bin/bash
# Print a text banner.
echo -en$"\t\tWelcome to "
read -rsystem_release < /etc/system-release
if [["$system_release" == *"Red Hat"* ]]; then
[ "$BOOTUP" = "color" ]&& echo -en "\\033[0;31m"
echo -en "Red Hat"
[ "$BOOTUP" = "color" ]&& echo -en "\\033[0;39m"
PRODUCT=$(sed "s/Red Hat \(.*\)release.*/\1/" /etc/system-release)
echo " $PRODUCT"
elif [["$system_release" == *Fedora* ]]; then
[ "$BOOTUP" = "color" ]&& echo -en "\\033[0;34m"
echo -en "Fedora"
[ "$BOOTUP" = "color" ]&& echo -en "\\033[0;39m"
PRODUCT=$(sed "s/Fedora \(.*\)\?release.*/\1/" /etc/system-release)
echo " $PRODUCT"
elif [["$system_release" =~ "CentOS" ]]; then
[ "$BOOTUP" = "color" ]&& echo -en "\\033[0;36m"
echo -en "CentOS"
[ "$BOOTUP" = "color" ]&& echo -en "\\033[0;39m"
PRODUCT=$(sed "s/CentOS \(.*\)\?release.*/\1/" /etc/system-release)
echo " $PRODUCT"
else
PRODUCT=$(sed "s/ release.*//g"/etc/system-release)
echo "$PRODUCT"
fi
......
十五、综合示例1:Linux Web服务监控
1、思路
(1)监控端口
本地:ss、netstat、lsof
远程:telnet、nmap、nc
注:查看远端的端口是否通畅3个简单实用案例http://oldboy.blog.51cto.com/2561410/942530
(2)查看本地进程数
(3)Http连接查看httpcode
header、curl -l:返回200就OK
注:掌握技术思想比解决问题本身更重要http://oldboy.blog.51cto.com/2561410/1196298
(4)模拟用户的方式
URL(wget、curl)
PHP、Java等应用程序监控
2、单项测试
(1)监控端口
a、本地监控:
lsof -i :80 | wc-l #一般要求大于等于1
b、远程监控:
nmap 192.168.1.5-p 80 | grep open | wc -l #一般要求大于等于1
(2)查看本地进程数
ps -ef | grepapache | wc -l #一般要求大于2或3
(3)Http连接查看httpcode
a、原始内容
curl -I http://192.168.1.5
HTTP/1.1 200 OK
Date: Tue, 30May 2017 02:20:04 GMT
Server: Apache
Last-Modified:Wed, 22 Feb 2017 13:40:55 GMT
ETag:"1bf365-13-5491ea5590f07"
Accept-Ranges:bytes
Content-Length:19
Content-Type:text/html
b、失败的获取httpcode方式(也就是HTTP/1.1 200 OK这行)
curl -I http://192.168.1.5 | head -1
% Total % Received% Xferd Average Speed Time Time Time ...
0 19 0 0 0 0 0 0 --:--:-- --:-- ...
HTTP/1.1 200 OK
curl -I http://192.168.1.5 | grep HTTP/1.1
% Total %Received % Xferd Average Speed Time TimeTime ...
0 19 0 0 0 0 0 0 --:--:-- --:-- ...
HTTP/1.1 200 OK
curl -I http://192.168.1.5 | grep HTTP/1.1 | tail -1
% Total %Received % Xferd Average Speed Time TimeTime ...
0 19 0 0 0 0 0 0 --:--:-- --:-- ...
HTTP/1.1 200 OK
c、获取httpcode方式一:wget
(i)步骤一:获取header
wget --spider--timeout=5 --tries=2 192.168.1.5
Spider modeenabled. Check if remote file exists.
--2017-05-3010:45:00-- http://192.168.1.5/
Connecting to 192.168.1.5:80...connected.
HTTP requestsent, awaiting response... 200 OK
Length: 19[text/html]
Remote fileexists and could contain further links,
but recursion isdisabled -- not retrieving.
(ii)步骤二:通过命令返回值判断wget命令是否正确执行
wget --spider--timeout=5 --tries=2 192.168.1.92 &>/dev/null
echo $?
4 #192.168.1.92不存在,返回值错误
wget --spider--timeout=5 --tries=2 192.168.1.5 &>/dev/null
echo $?
0 #正确
d、获取httpcode方式二:curl
(i)步骤一:获取第一行:HTTP/1.1200 OK
curl -I -shttp://192.168.1.5 | head -1
HTTP/1.1 200 OK
curl -I http://192.168.1.5 2>/dev/null | head -1
HTTP/1.1 200 OK
(ii)步骤二:直接获取httpcode
curl -I -s -w"%{http_code}" -o /dev/null http://192.168.1.5
200
(4)模拟用户的方式
<?php
$link_id=mysql_connect('localhost','root','123456') or mysql_error();
if($link_id){
echo "mysqlsuccessful by aaa";
}else{
echo mysql_error();
}
?>
3、完整脚本(以curl为例)
vi /service/scripts/check_web.sh
#!/bin/sh
http_code=`curl-I -s -w "%{http_code}" -o /dev/null http://192.168.1.5`
if [ $http_code-ne 200 ]
then
echo " Web is error."
else
echo " Web is OK."
fi
sh check_web.sh
Web is OK.
/application/apache/bin/apachectl stop
sh check_web.sh
Web is error.
/application/apache/bin/apachectl start
sh check_web.sh
Web is OK.
十六、综合示例2:利用系统函数模拟实现web服务脚本启动的特殊颜色效果
1、系统脚本效果
2、完整脚本
vi /services/scripts/webctl_apache.sh
#!/bin/sh
./etc/init.d/functions
if [ $# -ne 1 ]
then
echo "USAGE $0{start|stop|restart}"
exit 1
fi
if ["$1" == "start" ]
then
action "Starting webctl_apache: " /bin/true
exit 0
elif ["$1" == "stop" ]
then
action "Stopping webctl_apache: " /bin/true
exit 0
elif ["$1" == "restart" ]
then
action "Stopping webctl_apache: " /bin/true
action "Starting webctl_apache: " /bin/true
exit 0
else
echo "USAGE $0" {start|stop|restart}
exit 1
fi
3、脚本演示
sh webctl_apache.sh
USAGEwebctl_apache.sh {start|stop|restart}
sh webctl_apache.sh start
Startingwebctl_apache: [ OK ]
sh webctl_apache.sh stop
Stoppingwebctl_apache: [ OK ]
sh webctl_apache.sh restart
Stoppingwebctl_apache: [ OK ]
Startingwebctl_apache: [ OK ]
十七、综合示例3:监控web站点目录下所有文件是否被篡改
1、要求
(1)站点目录:/var/html/www
(2)将被篡改的文件的文件名发邮件给管理员
(3)每3分钟执行一次检查
2、思路
(1)什么是恶意篡改:只要未经许可的改动都是篡改
(2)文件被篡改后的特征
a、大小可能会有变化
b、修改时间会变化(通过文件测试符:ot、nt来判断)
c、文件内容会变化(通过md5 sum指纹来判断)
d、增加或删除文件
(3)
3、完整脚本
vi /services/scripts/webSiteCheck.sh
4、脚本演示
十八、后台执行脚本
1、防止脚本执行中断的方法
(1)sh test.sh &
(2)screen命令
(3)nohup test.sh &
2、后台执行脚本的控制
命令 | 功能 |
sh test.sh & | 把脚本test.sh放到后台执行 |
ctrl + c | 停止执行当前脚本或任务 |
ctrl + z | 暂停执行当前脚本或任务 |
bg | 把当前脚本或任务放到后台执行 |
fg | 把当前脚本或任务拿到前台执行,如果有多个任务,可以fg加任务编号调出,如fg 1 |
jobs | 查看执行的脚本或任务 |
3、示例
(1)示例1:bg命令演示
a、场景
已执行sh while01.sh,但忘记加&,让脚本后台执行,发现时,该脚本已执行完一半任务,不想停止脚本,全部重新执行,希望把脚本直接放到后台继续执行。
b、操作步骤
(i)ctrl + Z:先暂停脚本的执行
(ii)bg :将脚本放到后台继续执行
c、脚本演示
cat while01.sh
#!/bin/sh
while true
do
uptime >>/var/log/uptime.log
sleep 2
done
sh while01.sh
^Z # 这是ctrl + z
[1]+ Stopped sh while01.sh
bg
[1]+ sh while01.sh & # 脚本已在后台继续执行
(2)示例2:fg命令演示
a、场景
后台已执行2个while01.sh脚本,希望把第2个脚本停止执行。
b、操作步骤
(i)jobs:查看当前正在执行的脚本或任务
(ii)fg 2 :将第2个脚本放到前台执行
(iii)ctrl + c :停止执行第2个脚本
c、脚本演示
sh while01.sh &
[1] 3980
sh while01.sh &
[2] 3985
jobs
[1]- Running sh while01.sh &
[2]+ Running sh while01.sh &
fg 2
sh while01.sh
^C # 这是ctrl + c
jobs
[1]+ Running sh while01.sh &