Linux运维学习笔记之二十七:Shell基础

第三十九章 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

说明:shbash的软链接

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: [email protected]

#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 是否可以被正常赋值。
若此替换出现在Shell脚本中,那么脚本将停止运行。

${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" [email protected]

             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 <

    **************************************

    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" [email protected] < $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)" [email protected]

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)模拟用户的方式

   $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 &


你可能感兴趣的:(Linux)