shell脚本

Posted 一直飞的无脚鸟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shell脚本相关的知识,希望对你有一定的参考价值。

Shell脚本编程

1shell脚本--简介

1.1格式要求:首行shebang(sharp bang)<==>#!机制

? #!/bin/bash(用于shell脚本)

? #!/usr/bin/python(用于python脚本)

? #!/usr/bin/perl(用于perl脚本)

1.2shell脚本的用途:

? 自动化常用命令

? 执行系统管理和故障排除

? 创建简单的应用程序

? 处理文本或文件

1.3运行脚本的两种执行方式

1、用bash命令去执行(但是他是会再开辟一个字shell)

[[email protected] ~]# bash wuyanzu.sh

2、作为可执行文件,给于执行权限,直接当命名运行;

[[email protected] ~]# chmod +x abc.sh   
[[email protected] ~]# ./wuyanzu.sh   #相对路径写法
[[email protected] ~]# /root/wuyanzu.sh   #绝对路径的写法 

对于程序运行的顺序,首先别名,上一次hash到这个指令,内部命令,外部命令根据PATH变量,所以这里要么把变量添加到PATH变量中,要么全路径

1.4小试牛刀--第一个脚本:

要求:显示系统的一些相关信息

#/bin/bash
echo "The hosts is `hostname`"
echo "The kernel version is `uname -r`"
echo "The cpu is `lscpu|grep "Model name"|tr -s " "|cut -d: -f2`"
`head -1 /proc/meminfo`
echo "The ip is `ifconfig eth1|grep "netmask"|tr -s " "|cut -d" " -f3`"
[[email protected] ~]# chmod a+x sysinfo.sh      #给予权限
[[email protected] ~]# ./sysinfo.sh             #利用相对路径执行
The hosts is centos7.wuyanzu.com
The kernel version is 3.10.0-693.el7.x86_64
The cpu is  Intel(R) Core(TM) i5-5287U CPU @ 2.90GHz
./sysinfo.sh: line 5: MemTotal:: command not found
The ip is 192.168.241.178
[[email protected] ~]# ./sysinfo.sh 

1.5脚本调试:

? 检测脚本中的语法错误

? bash -n /path/to/some_script

? 调试执行(在后期脚本执中查看执行过程用来排错,非常重要)

? bash -x /path/to/some_script

2变量:

? 强类型:变量不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换。一般定义变量时必须指定类型、参与运算必须符合类型要求;调用未声明变量会产生错误

? 如 java,c#等

? 弱类型:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量 无须事先定义可直接调用

? 如:bash不支持浮点数,php

? 变量命名法则:

? 1、不能使程序中的保留字:

? 例如if, for

? 2、只能使用数字、字母及下划线,且不能以数字开头

? 3、见名知义

? 4、统一命名规则:驼峰命名法

2.1变量在内存的中的存放

[[email protected] ~]# i=100                 #弱类型语言赋值即定义
[[email protected] ~]# j=i
[[email protected] ~]# echo $j
i
[[email protected] ~]# j=$i
[[email protected] ~]# echo $j
100
[[email protected] ~]# i=200
[[email protected] ~]# echo $j
100
         1. j=i只是把这个字符串赋过去,真正的你要里面的值,要$i赋值给他;        
         2. 当把i的值给为200时,j的值仍然不变,这是因为j=$i是将100这个值给j,2者并没有共同引用一块内存空间,所以修改i的值是不影响j的值;

2.2bash中变量的种类:

2.2.1局部变量:

生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效;

? 变量赋值:

? (1) 可以是直接字符串: name="root"

? (2) 变量引用:name="$USER"

? (3) 命令引用:name=`command `` name=$(COMMAND)

? 变量引用:${name} $name

? "":弱引用,其中的变量引用会被替换为变量值

? ‘‘:强引用,其中的变量引用不会被替换为变量值,而保持原字符串

? 显示已定义的所有变量:set

? 删除变量:unset name

2.2.2shell有效范围的测试

vim p.sh

#!/bin/bash
name=p
echo "p.sh:name=${name}"
./s.sh              #再调用一个子shell

vim s.sh

#!/bin/bash
echo "s.sh:name=${name}"

给予权限并执行

[[email protected] ~]# chmod +x p.sh
[[email protected] ~]# chmod +x s.sh 
[[email protected] ~]# ./p.sh        #使用的是相对路径,也可将目录放到PATH变量中直接调用   
p.sh:name=p
s.sh:name=                  

由此可以证明局部变量仅对当前shell有效;

?

2.2.3全局变量

(环境变量):生效范围为当前shell进程及其子进程指函数本地变量:生效范围为当前shell进程中某代码片断,通常指函数;

? 变量声明、赋值:

? export name=VALUE

? declare -x name=VALUE

? 变量引用:

? $name, ${name}

? 显示所有环境变量:

? env

? printenv

? export

? declare -x

? 删除变量: unset name

2.2.4只读变量:

只能声明,但不能修改和删除(只能随着进程关闭而删除)

? 声明只读变量:

? readonly name

? declare -r name

? 查看系统所有的只读变量:

? readonly –p

? SHLVL可以看到shell的嵌套深度:不过用pstree可以看的更清楚;

?

如果将变量的声明改为全局变量

#!/bin/bash
declare -x name=p
echo "p.sh:name=${name}"
./s.sh

[[email protected] ~]# ./p.sh  
p.sh:name=p
s.sh:name=p

是对当前shell以及子shell有效

可以继承给子shell,子shell改了影响孙子,后面改的,只影响下一代;

2.3获取变量长度

[[email protected] ~]# a=dasdasdad
[[email protected] ~]# echo ${#a}            #在输出变量的值前面多加一个#
9

2.4位置变量:

? $1, $2, ...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数;

? $1, $2, ...:对应第1、第2等参数,shift [n]换位置 (一下子挤掉前n个参数,然后后面的参数前移,如果往前挤都有参数可以挤都是返回正确,如果你是最后没参数可以往前挤了,还有shift的话,返回错误,因此也可以通过这个来判断还有没有参数,有几个参数;)

? 循环控制shift命令

? shift [n]

? 用于将参量列表 list 左移指定次数,缺省为左移一次。

? 参量列表list一旦被移动,最左端的那个参数就从列表中删除。while循环遍历位置参量列表时,常用到shift

? *.sh 后面接参数;

?

? 特殊变量:$?, $0, $*, [email protected], $#,$$;

? $0: 脚本名字

? $*: 传递给脚本的所有参数,全部参数合为一个字符串

? [email protected]: 传递给脚本的所有参数,每个参数为独立字符串

? [email protected] $* 只在被双引号包起来的时候才会有差异

? $#: 传递给脚本的参数的个数

? set -- 清空所有位置变量

2.4.1位置变量实例

位置变量的传递

[[email protected] ~]# vim arg.sh    目标是显示10个位置变量的传递,但是会存在一个问题
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "4st arg is $4"


echo "9st arg is $9"
echo "10st arg is $10"      #这里存在一个问题

问题:

[[email protected] ~]# ./arg.sh  #未传参
1st arg is 
2st arg is 
3st arg is 
4st arg is 
9st arg is 
10st arg is 0           
        
[[email protected] ~]# ./arg.sh a b c d e f g h i j      #传参
1st arg is a
2st arg is b
3st arg is c
4st arg is d
9st arg is i
10st arg is a0              #这个10会看成$1和0,所以要加括号加以区分

#正确的写法
#echo "10st arg is ${10}"
[[email protected] ~]# ./arg.sh a b c d e f g h i j 
1st arg is a
2st arg is b
3st arg is c
4st arg is d
9st arg is i
10st arg is j

额外与位置变量无关,$类的特殊变量补充:

$$:当前shell的pid,除了执行bash命令和shell脚本,$$不会继承父shell的值,其他类型的子shell都继承;

$BASHPID:当前shell的PID,这和“$$”是不同的,因为每个shell的$BASHPID是独立的,而$$有时候会继承父shell的值

$?:上一次代码执行的回传指令,回传0正确,1-255为错误

$!:最近一次执行的后台进程PID

2.4.2实例二
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "4st arg is $4"


echo "9st arg is $9"
echo "10st arg is ${10}"

#*和@都是表示所有参数的
echo "all args are $*"
echo "all args are [email protected]"

#参数个数
echo "the args number is $# "

#脚本名字
echo "the scripts name is $0"

#输出
[[email protected] ~]# ./arg.sh a b c d e f g h i j 
1st arg is a
2st arg is b
3st arg is c
4st arg is d
9st arg is i
10st arg is j
all args are a b c d e f g h i j
all args are a b c d e f g h i j
the args number is 10 
the scripts name is ./arg.sh

#set -- 清空所有位置变量

#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "4st arg is $4"


echo "9st arg is $9"
echo "10st arg is ${10}"

#*和@都是表示所有参数的
echo "all args are $*"
echo "all args are [email protected]"

#参数个数
echo "the args number is $# "

#脚本名字
echo "the scripts name is $0"

set --                  #用来清空位置变量,脚本名字还是存在的
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "4st arg is $4"


echo "9st arg is $9"
echo "10st arg is ${10}"

#*和@都是表示所有参数的
echo "all args are $*"
echo "all args are [email protected]"

#参数个数
echo "the args number is $# "

#脚本名字
echo "the scripts name is $0"

#输出
[[email protected] ~]# ./arg.sh a b c d e f g h i j 
1st arg is a
2st arg is b
3st arg is c
4st arg is d
9st arg is i
10st arg is j
all args are a b c d e f g h i j
all args are a b c d e f g h i j
the args number is 10 
the scripts name is ./arg.sh
1st arg is 
2st arg is 
3st arg is 
4st arg is 
9st arg is 
10st arg is 
all args are 
all args are 
the args number is 0    
the scripts name is ./arg.sh        #唯有脚本名称还是存在的
2.4.3实例三($*与[email protected]的区别(只有在有引号时))
[[email protected] ~]# vim arg.sh 
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"

echo all args is "$*"
echo the number is "$#"

f2.sh "$*"          

[[email protected] ~]# vim f2.sh
#!/bin/bash
echo "1st arg is $1"
  ho "2st arg is $2"
▽cho "3st arg is $3"

echo all args is "$*"
echo the number is "$#"

#输出
[[email protected] ~]# ./arg.sh a b c d e f g h i j 
1st arg is a
2st arg is b
3st arg is c
all args is a b c d e f g h i j
the number is 10
1st arg is a b c d e f g h i j
2st arg is
3st arg is
The number si 1
All number is a b c d e f g h i j       #当成一个参数

#把echo all args is "$*",改为[email protected]
[[email protected] ~]# ./arg.sh a b c d e f g h i j 
1st arg is a
2st arg is b
3st arg is c
all args is a b c d e f g h i j
the number is 10
1st arg is a
2st arg is b
3st arg is c
The number si 10
All number is a b c d e f g h i j

#没有引号的时候,f2.sh $*与[email protected]效果一样;
[[email protected] ~]# ./arg.sh a b c d e f g h i j 
1st arg is a
2st arg is b
3st arg is c
all args is a b c d e f g h i j
the number is 10
1st arg is a
2st arg is b
3st arg is c
The number si 10
All number is a b c d e f g h i j

有引号的前提下:

? $*是会把进来的参数当成一个参数的;

? [email protected]仍然是把他当做各个参数的

2.4.4实例四(shift参数的偏移)
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
shift
echo $?                 #一个shift偏移一个参数变量

echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
shift
echo $?

echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
shift
echo $?

#输出
[[email protected] ~]# ./arg.sh a b 
1st arg is a
2st arg is b
3st arg is 
0
1st arg is b
2st arg is 
3st arg is 
0
1st arg is 
2st arg is 
3st arg is 
1                       #只有2个参数,当无参数可以移动,那么时候返回非0,由此可以来判断,参数个数;

在shell脚本的交互式判断中,那么不知道用户输入的参数个数,就可以用此方法来判断有几个参数;

2.5进程使用退出状态来报告成功或失败:

0 代表成功,1-255代表失败

$? 变量保存最近的命令退出状态(如果在脚本中多次执行命令,值保留最后一次的命令结果;)

退出状态码:

? bash自定义退出状态码

? exit [n]:自定义退出状态码

? 要用(exit 25)来执行(用子shell,不然就退出了),遇到exit会退出;

? ()是在开辟一个子进程的意思;

? 注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字

? 注意:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

2.5.1状态码相关用户创建指令

简单实现

#!/bin/bash
[ $# -ne 1 ]&&echo "please must input one ,Usage:b.sh username" &&exit 20
id ${1} &>/dev/null && echo "The username is exist "&&exit 10
useradd ${1} && echo "${1} is created!"

#输出
[[email protected] ~]# ./b.sh
please must input one ,Usage:b.sh username
[[email protected] ~]# ./b.sh w1
The wang1 is exist 
[[email protected] ~]# ./b.sh w2
w2 is created!

另一种写法

#!/bin/bash
[ -z ${1} ] && echo "please input one username;usage :f2.sh username" && exit 20        #1
id ${1} &>/dev/null && echo "$1 is exist"&& exit 10
useradd ${1}&&echo "${1} is created!"

#输出
[[email protected] ~]# ./b.sh  
please input one username;usage :f2.sh username
[[email protected] ~]# ./b.sh w
w is created!
[[email protected] ~]# ./b.sh w
w is exist

注意1:有些时候的判断需要后面2者甚至更多同时都要执行时,那么需要括起来但是因为()是会在开一个子进程(你在()里执行的exit,对程序本身没有帮助,因为exit的是那个子进程;),有时候会影响结果,所以需要用匿名函数来解决{*****;}

利用read交互式的写法

#!/bin/bash
while true
do
        read -p "please input a username:" username
        [ ${username} = "quit" -o ${username} = "q" ] && exit
        [ -z ${username} ] && echo "you must input a arg"&&continue
        [ -n ${username} ] && id ${username} &>/dev/null && echo "${username} is exist
,please again"&&continue || { useradd ${username} && echo "${username} is created!"&& 
exit 10; }


done

#输出
[[email protected] ~]# ./b.sh  
please input a username:d
d is created!
[[email protected] ~]# ./b.sh  
please input a username:a
a is exist,please again
[[email protected] ~]# ./b.sh   
please input a username:q
[[email protected] ~]#
[[email protected] ~]# ./b.sh 
please input a username:quit
[[email protected] ~]# 

2.6利用()临时执行一些命令(非常实用);

[[email protected] ~]# name=test;(echo $name;name=222;echo $name);echo $name
test
222
test   

()相当是临时进入一个子进程,从这里可以看出是name=222赋值的时候开辟的;

[[email protected] ~]# cd /etc;pwd
/etc
[[email protected] etc]# cd
[[email protected] ~]# (cd /etc;pwd)
/etc
[[email protected] ~]#                   #当前目录不变

第一个pwd在/etc下了,后者是开了一个进程去执行完再回来;所以还在原来的目录下;

2.6.1作用1:当你需要创建一个600的文件 
[[email protected] ~]#  (umask 066;touch /app/f1)
[[email protected] ~]# ll /app/f1
-rw------- 1 root root 0 Aug 13 19:15 /app/f1
2.6.2作用2:利用变量去创建一个文件,且省去取消变量
[[email protected] ~]#  (name=haha;touch /app/$name.log)
#变量会随着当前shell的结束,而取消变量

2.7declare/typeset(可以不考虑使用)

? Shell变量一般是无类型的,但是bash Shell提供了declare和 typeset两个命令用于指定变量的类型,两个命令是等价的

? 定义变量的类型

? -a:将后面接的变量定义成为数组;

? -i:将后面接的变量定义成为整型;

? -x:用法同export,将变量变成环境变量;

? -r:将变量设置成为readonly类型,该变量不能被更改,也不能unset;

? -p:可以单独列出变量的类型;

? -A:将变量定义为关联数组

? -f:显示已定义的所有函数名及其内容

? -F:仅显示已定义的所有函数名

? -l:声明变量为小写字母 declare –l var=UPPER

? -u:声明变量为大写字母 declare –u var=lower

2.7.1declare的使用
#-i定义为整型
[[email protected] ~]# sum=100+20+123
[[email protected] ~]# echo $sum
100+20+123
#方法一
[[email protected] ~]# echo $sum |bc
243
#方法二
#默认解释器都是字符串,所以-i是将变量定义成为整型
[[email protected] ~]# declare -i sum=110+23+2
[[email protected] ~]# echo $sum 
135

#-x定义为环境变量
[[email protected] ~]# declare -x sum
[[email protected] ~]# export |grep sum
declare -ix sum="135"

#-r定位为只读
[[email protected] ~]# declare -r sum
[[email protected] ~]# export|grep sum
declare -irx sum="135"    #有-r说明是只读

#sum取消环境变量为局部变量
[[email protected] ~]# declare +x sum
[[email protected] ~]# export|grep sum       #在环境变量中就找不到了
[[email protected] ~]# declare -p sum        #-p:可以单独列出变量的类型;
declare -ir sum="135"                  

#只读变量不能被更改和unset,只能关闭shell
[[email protected] ~]# unset sum
-bash: unset: sum: cannot unset: readonly variable
2.7.1.1存在问题
[[email protected] ~]# declare -i num=10
[[email protected] ~]# num=abc
[[email protected] ~]# echo $?
0
[[email protected] ~]# echo $num
0

存在这样一个问题,当先定义是数字,然后赋字符串,是赋不进去的,但是返回是成功的;

3算术运算与判断:

? bash中的算术运算:help let

? +, -, *, /, %取模(取余), **(乘方)

? 实现算术运算(普通的赋值,默认是字符串,只有以下才会是数值相加):

? (1) let var=算术表达式

? (2) var=$[算术表达式]

? (3) var=$((算术表达式))

? (4) var=$(expr arg1 arg2 arg3 ...)

? expr 1 * 3(*号需要转义)

? (5) declare –i var = 数值(声明为整型,默认是字符串)

? (6) echo ‘算术表达式’ | bc

? bash有内建的随机数生成器:$RANDOM(0-32767)

#方法一:模拟随机数
[[email protected] ~]# echo $[ $RANDOM%50 ]          #等价于echo $(($RANDOM%50))
12
[[email protected] ~]# echo $[ $RANDOM%50 ]
42
[[email protected] ~]# echo $[ $RANDOM%50 ]
22
[[email protected] ~]# echo $[ $RANDOM%50 ]
4
[[email protected] ~]# echo $[ $RANDOM%50 ]
7                                               #0-49之间随机数 

#方法二:模拟随机数
[[email protected] ~]# seq 1 50 | sort -R |head -1 
34
[[email protected] ~]# seq 1 50 | sort -R |head -1
46
[[email protected] ~]# seq 1 50 | sort -R |head -1
16
[[email protected] ~]# seq 1 50 | sort -R |head -1
32                                              #1-50的随机数
#sort -R是随机排序;所以可以当做随机数;

sort -R是随机排序;所以可以当做随机数;

? 赋值:

? 增强型赋值:

? +=, -=, *=, /=, %=

? 自增,自减:

? let var+=1

? let var++

? let var-=1

? let var--

其他同理

a=b是赋值

a = b 是两者的比较

3.1逻辑判断(用户相关操作)

#删除已存在的用户的
#!/bin/bash
while true
do
        read -p "please input you will del username!:" u
        [ ${u} = "q" -o ${u} = "quit" ] && exit
        [ -z ${u} ]&&echo "please again"&&continue
        [ -n ${u} ]&& id ${u} &>/dev/null && userdel -r ${u}&&echo "del success"&&exit
 10 || echo "${u} is not exist"
done

#输出
please input you will del username!:w4
w4 is not exist
[[email protected] ~]# ./del.sh  
please input you will del username!:w2
del success

#利用grep找到返回0,找不到返回非0的特性来找
[[email protected] ~]# grep wang /etc/passwd;echo $?  
wang:x:1000:1000:wangxiaochun,Opt,110,10086:/home/wang:/bin/bash
wang1:x:1026:1026::/home/wang1:/bin/bash
wang2:x:1028:1030::/home/wang2:/bin/bash
0
[[email protected] ~]# grep wangdd /etc/passwd;echo $?
1
[[email protected] ~]# grep -q wang /etc/passwd && echo "wang is exist"
wang is exist

#用户不存在创建之
[[email protected] ~]# id w22 &>/dev/null &&echo "username exist"||{ useradd w22 && echo "create success" ;}    
create success
[[email protected] ~]# id wang &>/dev/null &&echo "username exist"||{ useradd wang && echo "create success" ;}     
username exist

#判断主机是否可达
[[email protected] ~]# ping 137.23.43.5 -w1 -c1 &>/dev/null&&echo "the host is up" || echo "the host is down"  
the host is down

[[email protected] ~]# ping 127.1 -w1 -c1 &>/dev/null&&echo "the host is up" || echo "the host is down"
the host is up

-c1:只会显示一次,默认是一直;

-w1:默认5秒算是一次往返,改为1秒;

3.2 bash的测试:

? -v VAR

? 变量VAR是否设置(后面引用的时候不需要加$)

[[email protected] ~]# a=;[ -v a ]&&echo "true" ||echo "false"
true
[[email protected] ~]# a="";[ -v a ]&&echo "true" ||echo "false"
true
[[email protected] ~]# a=" ";[ -v a ]&&echo "true" ||echo "false"
true
[[email protected] ~]# unset a;[ -v a ]&&echo "true" ||echo "false"     
false
3.2.1 数值测试:

? -gt 是否大于

? -ge 是否大于等于

? -eq 是否等于

? -ne 是否不等于

? -lt 是否小于

? -le 是否小于等于

3.2.2变量是否为空测试

? -n:变量不为空(not zero)

? -z:变量为空(zreo)

3.2.3字符串测试

? =或==:两边需要空格

? =:两边没有空格为赋值

? !=:不同

? -n:长度是否不为0

? -z:长度是否为0

? <:

? >:

#异或的应用,不需要借助第三方变量
[[email protected] ~]# a=10;b=20;a=$[a^b];b=$[a^b];a=$[a^b];echo $a;echo $b
20
10

注意:在linux的变量中,如果没有赋值成功,或者没有赋值,那么这个变量是什么也没有,不像java有初始值; 

如果一个变量为赋值那么他做运算时被当做0,还有个问题是在用变量删除文件时一定要判断这个变量是否为空;

#expr值与操作符之间都要空格
[[email protected] ~]# expr $dsada+1 
+1
[[email protected] ~]# expr $dsada + 1
1
#所以在脚本使用时,利用变量名删除文件时一定要判断这个变量是否为空,否则有可能会带来灾难性的危害;

[[email protected] ~]# expr $dsada + 0       #dsada是未定义的变量
0
[[email protected] ~]# echo $?
1

问题1:未赋值

问题2:如果运算结果为0,返回1,帮助文档里有提示是这样设置的,并非是bug;

3.2.4bash的条件测试:

? 判断某需求是否满足,需要由测试机制来实现专用的测试表达式需要由测试命令辅助完成测试过程

? 评估布尔声明,以便用在条件性执行中

? 若真,则返回0

? 若假,则返回1

? 测试命令:

? test EXPRESSION

? [EXPRESSION](两边需要空格,不然会报语法错误)

? []内,只有“”和不填为假,其余都为真;

? 其中变量要加隐号,可以避免一些问题;如果a=“ ”那么在[$a]这样不加隐号,就是[ ]这个效果是认为不填,返回错误的;

引号的重要性实例一

[[email protected] ~]# a=
[[email protected] ~]# [ $a ]&&echo true||echo false 
false
[[email protected] ~]# a=1
[[email protected] ~]# [ $a ]&&echo true||echo false 
true
[[email protected] ~]# unset a
[[email protected] ~]# [ $a ]&&echo true||echo false 
false

#但是会存在这样一个问题,当a=" ",他会默认与变量两边的隔空融为一体,才会导致这样
[[email protected] ~]# a=" "
[[email protected] ~]# [ $a ]&&echo true||echo false 
false
[[email protected] ~]# [ ${a} ]&&echo true||echo false 
false
[[email protected] ~]# [ "$a" ]&&echo true||echo false   
true

所以""$a"和${},这两者的使用,是"$a"能更好的规避这样的问题;

引号的重要性实例二

[[email protected] ~]# a=asd
[[email protected] ~]# [ -n ${a} ] && echo true ||echo false
true
[[email protected]centos7 ~]# unset a
[[email protected] ~]# [ -n ${a} ] && echo true ||echo false     #1
true
[[email protected] ~]# [ -n "$a" ] && echo true ||echo false  
false

注意1:没有引号,a为空的时候,解析器会认为是[ -n ]-n会被当做字符串

判空比较好的方法

[[email protected] ~]# a=asd
[[email protected] ~]# [ "$a" = "$ax" ]&&echo true || echo false
false
[[email protected] ~]# a=
[[email protected] ~]# [ "$a" = "$ax" ]&&echo true || echo false
true
[[email protected] ~]# unset a                           #这里也可以说明未定义的变量,是什么也没有
[[email protected] ~]# [ "$a" = "$ax" ]&&echo true || echo false
true
[[email protected] ~]# a=""
[[email protected] ~]# [ "$a" = "$ax" ]&&echo true || echo false
true

linux许多的配置文件使用此方法,来判空!

[[ EXPRESSION ]] :支持扩展的正则表达式;

? =~:包含里使用正则;

? =~ 左侧字符串是否能够被右侧的PATTERN所匹配

? 注意: 此表达式一般用于[[ ]]中,不用正则表达式的话,就用[],这样不容易搞混;

? 扩展的正则表达式 -z (zero)"STRING“ 字符串是否为空,空为真,不空为假 -n(not zero) "STRING“ 字符串是否不空,不空为真,空为假

? 注意:用于字符串比较时的用到的操作数都应该使用引号

? 注意:EXPRESSION前后必须有空白字符,引号也一定要加;

[[email protected] ~]# a=haha;[[ "$a" =~ ha ]]&&echo true || echo false  
true
[[email protected] ~]# a=haha;[[ "$a" =~ haa ]]&&echo true || echo false
false
[[email protected] ~]# a=haha;[[ "$a" =~ h? ]]&&echo true || echo false  
true
[[email protected] ~]# a=haha;[[ "$a" =~ ha? ]]&&echo true || echo false
true
[[email protected] ~]# a=haha;[[ "$a" =~ a? ]]&&echo true || echo false 
true
3.2.5bash的文件测试:
存在性测试

? -a FILE:同-e

? -e FILE: 文件存在性测试,存在为真,否则为假

存在性及类别测试

? -b FILE:是否存在且为块设备文件

? -c FILE:是否存在且为字符设备文件

? -d FILE:是否存在且为目录文件

? -f FILE:是否存在且为普通文件

? -h FILE 或 -L FILE:存在且为符号链接文件

? -p FILE:是否存在且为命名管道文件

? -S FILE:是否存在且为套接字文件

注意:这里判断的文件类型是文件指向的类型,所以如果批量处理的话要过滤出软连接来处理;

所以判断逻辑应该是先判断是否是链接文件,再处理;

3.2.6bash的文件测试权限:
文件权限测试:

? -r FILE:是否存在且可读

? -w FILE:是否存在且可写

? -x FILE:是否存在且可执行

文件特殊权限测试:

? -u FILE:是否存在且拥有suid权限

? -g FILE:是否存在且拥有sgid权限

? -k FILE:是否存在且拥有sticky权限

注意:他看的是实际权限,如果给了acl权限那么他就是有的

Bash的文件属性测试

? 文件大小测试:

? -s FILE: 是否存在且非空

? 文件是否打开:

? -t fd: fd 文件描述符是否在某终端已经打开

? -N FILE:文件自从上一次被读取之后是否被修改过

? -O FILE:当前有效用户是否为文件属主

? -G FILE:当前有效用户是否为文件属组

? 双目测试:

? FILE1 -ef FILE2: FILE1是否是FILE2的硬链接

? FILE1 -nt FILE2: FILE1是否新于FILE2(mtime)

? FILE1 -ot FILE2: FILE1是否旧于FILE2

统计当前进程打开了多少个文件,因为没打开一个文件会对应有一个fd(文件描述符);

[[email protected] ~]# ls /proc/$$/fd
0  1  2  255
[[email protected] ~]# ls /proc/$$/fd|wc -l
4

4read交互式输入

使用read来把输入值分配给一个或多个shell变量

? -p 指定要显示的提示

? -s 静默输入,一般用于密码

? -n N 指定输入的字符长度N(达到指定个数就自动退出来了;)

? -d ‘字符’ 输入结束符 (指定结束标识符)

? -t N TIMEOUT为N秒(后面可以接等待的秒数,防止一直等待;)

? read 从标准输入中读取值,给每个单词分配一个变量所有剩余单词都被分配给最后一个变量

? read -p “Enter a filename: “ FILE

?

4.1多参数问题
[[email protected] ~]# read -p "please input a num:" a b
please input a num:23 45 56
[[email protected] ~]# echo $a
23
[[email protected] ~]# echo $b
45 56

[[email protected] ~]# read -p "please input a num:" a b
please input a num:23
[[email protected] ~]# echo $a
23
[[email protected] ~]# echo $b

[[email protected] ~]# echo $REPLY
                                    #有变量定义的时候$REPLY就为空了,默认是存放在REPLY中的
[[email protected] ~]# read -p "please input a num:" 
please input a num:23 123 21
[[email protected] ~]# echo $REPLY
23 123 21

注:但是多个参数会有问题,给几个输入几个是可以的,但是不对应,多给或者少给往往是会有问题的;

      read 后面接变量名是,把值放到这个变量名中,不然默认是放到REPLY中(后面可以跟多个变量,输入按空格为分隔符,依次对应填入,变量名多,后几个为空,变量名少,最后填入所有的信息;所以最好一次输一个;

4.2用于密码问题应该使用-s
[[email protected] ~]# read -s -p "please input a passwd:" passwd
please input a passwd:[[email protected] ~]# echo $passwd 
123456
4.3-d:指定结束符

结束符最好加引号,在交互中使用退出,直到输入指定的分隔符为止

[[email protected] ~]#  read -d "q" -s -p "please input a passwd:" passwd   
please input a passwd:[[email protected] ~]# 
[[email protected] ~]# echo $passwd 
dasdada
4.4-t等待时间
[[email protected] ~]# read -p "please input your name:" -t 5 named
please input your name:[[email protected] ~]# (5秒后超时)
[[email protected] ~]# read -p "please input your name:" -t 5 named
please input your name:wuyanzu
[[email protected] ~]# echo ${named}
wuyanzu
4.5read支持重定向

他仅仅只能读入文件的第一行;

[[email protected] ~]# read </etc/passwd
[[email protected] ~]# echo $REPLY
root:x:0:0:root:/root:/bin/bash

4.6简单模拟系统交互系统

#!/bin/bash
read -p "please input yes or no:" ANS
[[ "$ANS" =~ ([Yy]([eE][Ss])?)|([Nn][Oo]?) ]] &&echo true || echo false

#交互输出
[[email protected] ~]# ./y_n.sh
please input yes or no:y
true
[[email protected] ~]# ./y_n.sh
please input yes or no:YEs  
true
[[email protected] ~]# ./y_n.sh
please input yes or no:No
true
[[email protected] ~]# ./y_n.sh
please input yes or no:n
true
[[email protected] ~]# ./y_n.sh
please input yes or no:sda
false
[[email protected] ~]# 

5bash如何展开命令行(优先级顺序)

5.1扩展

? 把命令行分成单个命令词

? 展开别名

? 展开大括号的声明({})

? echo {1..100}

? 展开波浪符声明(~)

? 家目录

? 命令替换$() 和 ``)

? 再次把命令行分成命令词

? 展开文件通配(*、?、[abc]等等)

? 准备I/0重导向(<、>)

? 运行命令

?

5.2防止扩展

? 反斜线()会使随后的字符按原意解释(转义)

? $ echo Your cost: $5.00

? Your cost: $5.00

? 加引号来防止扩展

? ? 单引号(’)防止所有扩展

? ? 双引号(”)也防止所有扩展,但是以下情况例外:

? $(美元符号) - 变量扩展

? `(反引号) - 命令替换

? (反斜线) - 禁止单个字符扩展

? !(叹号) - 历史命令替换

5.3管道

管道顾名思义,上一条命令的输出作为下一条命令的输入

5.3.1用管道设置密码
[[email protected] ~]# echo "123456" | passwd --stdin wang
Changing password for user wang.
passwd: all authentication tokens updated successfully.

注意:command1|command2千万不要以为用管道串起来的2个命令是依次执行的,linux系统实际上是会同时运行这2个命令。在第一个命令产生输出的同时,输出会被立即送给第二个命令。数据的传输不会用到任何中间文件或者缓冲区。

5.3.2实例说明管道两步并非依次执行
[[email protected] ~]# ps aux |grep ssh
root       1066  0.0  0.0 105996   356 ?        Ss   Aug11   0:00 /usr/sbin/sshd -D
root       1397  0.0  0.0  51332     0 ?        Ss   Aug11   0:01 /usr/bin/ssh-agent /bin/sh -c exec -l /bin/bash -c "env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic"
root      34514  0.0  0.5 150400  5432 ?        Ss   10:05   0:00 sshd: [email protected]/0
root      38906  0.0  0.0 112664   972 pts/0    S+   14:42   0:00 grep --color=auto ssh
#如果是依次执行,是不可能看到grep进程,一定是先执行完ps,在去执行grep

6.bash的配置文件:

6.1按生效范围划分,存在两类:
全局配置

? /etc/profile

? /etc/profile.d/*.sh

? /etc/bashrc

个人配置

? ~/.bash_profile

? ~/.bashrc

6.2shell登录两种方式:
交互式登录

? (1)直接通过终端输入账号密码登录

? (2)使用“su - UserName” 切换的用户

? 执行顺序:/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc

非交互式登录

? (1)su UserName(交互非登入)

? (2)图形界面下打开的终端(交互非登入)

? (3)执行脚本(非交互,非登入,但是可以用bash -l执行,为非交互登入)

? (4)任何其它的bash实例

? 执行顺序: ~/.bashrc --> /etc/bashrc --> /etc/profile.d/*.sh

判断是否为交互

#判断是否为交互
#方法一:用$-变量
[[email protected] ~]# echo $-
himBH
[[email protected] ~]# ./a1.sh           #脚本中执行此变量,有i说明是交互式的,没有则非交互式
hB

#方法二:判断变量PS1是否为空,非空则为交互式
[[email protected] ~]# echo $PS1
[[email protected]h W]$
[[email protected] ~]# ./a1.sh        
                                #在脚本中执行为空

shopt login_shell

#shopt login_shell判断是否为登录式
[[email protected] ~]# shopt login_shell
login_shell     on                      #on说明是登录式

[[email protected] ~]# bash                  #再开一个子进程

[[email protected] ~]# shopt login_shell
login_shell     off

[[email protected] ~]# su wang

[[email protected] root]$ shopt login_shell
login_shell     off
[[email protected] root]$ exit
exit
[[email protected] ~]# su - wang 
Last login: Tue Aug 14 08:45:56 CST 2018 on pts/1

[[email protected] ~]$ shopt login_shell
login_shell     on

su有无-就可以看出,是否是登入式的

常见几种bash启动的方式

1、伪终端如ssh,或虚拟终端,为交互式登录shell

[[email protected] ~]# echo $PS1;shopt login_shell
[[email protected]h W]$
login_shell     on

2、su username为交互式非登录,su - username交互式登入,见上

-就是--login选项

3、执行带--login选项bash命令时为交互登入式,不带—login选项为交互非登入式(bash的—login选项为-l)

#带--login选项
[[email protected] ~]# bash -l 

[[email protected] ~]# echo $PS1;shopt login_shell
[[email protected]h W]$
login_shell     on

#不带--login选项
[[email protected] ~]# bash

[[email protected] ~]# echo $PS1;shopt login_shell
[[email protected]h W]$
login_shell     off

4、()开辟一个子shell,继承父shell的交互和登录属性

[[email protected] ~]# su - wang 
Last login: Tue Aug 14 09:14:57 CST 2018 on pts/1

[[email protected] ~]$ (echo $BASH_SUBSHELL;echo $PS1;shopt login_shell)
1
[[email protected]h W]$
login_shell     on

[[email protected] ~]# su wang

[[email protected] root]$ (echo $BASH_SUBSHELL;echo $PS1;shopt login_shell) 
1
[[email protected]h W]$
login_shell     off
6.3按功能划分,存在两类

? profile类和bashrc类

profile类

? 为交互式登录的shell提供配置

? 全局:/etc/profile, /etc/profile.d/*.sh

? 个人:~/.bash_profile

? 功用:

? (1) 用于定义环境变量

? (2) 运行命令或脚本

?

bashrc类

? 为非交互式和交互式登录的shell提供配置

? 全局:/etc/bashrc

? 个人:~/.bashrc

? 功用:

? (1) 定义命令别名和函数

? (2) 定义本地变量

6.4编辑配置文件生效:

? 修改profile和bashrc文件后需生效两种方法:

? 1重新启动shell进程

? 2 . 或source

注意:.和source是直接在本bash中执行的,而直接执行和bash 脚本是开一个子进程执行的,所以为了保证有些变量的有效性,多使用前者!

?

6.5bash 退出任务:

? 保存在~/.bash_logout文件中(用户)

? 在退出登录shell时运行

? 用于

? ? 创建自动备份

? ? 清除临时文件

7$-变量:

? h:hashall,打开这个选项后,Shell 会将命令所在的路径 hash下来,避免每次都要查询。通过set +h将h选项关闭。

? i:interactive-comments,包含这个选项说明当前的shell是一个交互式的 shell。所谓的交互式shell,在脚本中,i选项是关闭的。

? m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等。

? B:braceexpand,大括号扩展

? H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的一个历史命令, “!n”返回第 n 个历史命令

8SHLVL 和 BASH_SUBSHELL 两个变量的区别

这两者的区别,就是在于什么是子shell,也叫subshell,这是真正的子shell,是由父进程fork()出一份几乎一模一样的子进程。而用bash去执行再开启的shell,应该称为childshell,他是由fork()出一份子进程后,再执行exec()。函数exec() 函数会重新加载硬盘上的 bash 命令并执行,替换刚才 fork 出来的那个 shell 进程,除了传入的环境变量外,是个崭新的进程。

[[email protected] ~]$ (echo $BASH_SUBSHELL)
1
[[email protected] ~]$ ( ( ( ( (echo $BASH_SUBSHELL) ) ) ) )
5
[[email protected] ~]$  ( ( ( ( (echo $SHLVL) ) ) ) )              #()就是开辟subshell 
1
[[email protected] ~]$ bash                                     #bash去执行是开辟childshell

[[email protected] ~]$ bash

[[email protected] ~]$ bash

[[email protected] ~]$ echo $SHLVL
4

总结一下就是说,SHLVL 变量是记录了所谓的 child shell 的嵌套深度,而 BASH_SUBSHELL 是记录了 subshell 的嵌套深度。

9使用结构化命令

9.1条件结构(if)
if 判断条件;then 
    条件为真的分支代码
fi

一切都以返回状态码是否为0为判决条件。如果执行结果的退出状态码为0,执行,不然就执行else部分

9.1.1小试牛刀-if-else实例
#交互式判断年龄大于50为old man,小于等于50位young man
#!/bin/bash
read -p "please input your age!:" age
if [[ ! "$age" =~ ^[0-9]+$ ]] ;then
        echo "please input a digit"
        exit 2
elif [ "$age" -gt 50 ];then
        echo "old man"
else
        echo "young man"
fi
9.2条件结构(case)
case 变量引用 in 

PAT1)

分支1;; 

PAT2)

分支2;;

 ...

*)

;; 

esac

case支持glob风格的通配符:

? *: 任意长度任意字符

? ?: 任意单个字符

? []:指定范围内的任意单个字符

? a|b: a或b

9.2.1小试牛刀-case实例
#模拟系统交互输入yes或者no
#!/bin/bash
read -p "do you agree ?(yes or no):" ans
case $ans in
[yY]|[yY][eE][sS])
        echo yes
        ;;
[Nn]|[Nn][Oo])
        echo no
        ;;
*)
        echo warn
        ;;
esac
9.3循环结构(for)

for 变量名 in 列表

do

? 循环体

done

列表生成方式:

(1) 直接给出列表

(2) 整数列表:

? (a) {start..end}

? (b) $(seq [start [step]] end)

(3) 返回列表的命令

? $(COMMAND)

(4) 使用glob,如:*.sh

(5) 变量引用; [email protected], $*

9.3.1小试牛刀-for实例

实例:创建user1...user100,100个用户放到password.txt里面去,格式为user1:sdada 

9.3.2随便生成密码的方式
#方法一:
[[email protected] ~]# openssl rand -base64 8|head -c 8
thaCMHFe[[email protected] ~]# openssl rand -base64 8|head -c 8
awNEbx+g[[email protected] ~]# openssl rand -base64 8|head -c 8

#方法二(纯数字字母):
[[email protected] ~]# cat /dev/urandom | tr -dc "0-9a-zA-Z"|head -c 8    
Etf9uozp[[email protected] ~]# cat /dev/urandom | tr -dc "0-9a-zA-Z"|head -c 8
725iRlRA[[email protected] ~]# cat /dev/urandom | tr -dc "0-9a-zA-Z"|head -c 8
Uny4pnPL[r[email protected] ~]# 

#方法三(纯数字(0-32767)):
[[email protected] ~]# echo $RANDOM
23349
[[email protected] ~]# echo $RANDOM
30752

#方法四(纯数字)
[[email protected] ~]# seq 1 50 |sort -R | head -1
40
[[email protected] ~]# seq 1 50 |sort -R | head -1
15
#方法3、4用于处理比较小的数字



#创建user1...user100,100个用户放到password.txt里面去,格式为user1:sdada 
#!/bin/bash
for i in {1..100}
do
        a=`openssl rand -base64 8|head -c8`
        echo "user${i}:${a}"|tee -a passwd.txt

done
9.3.3定义一个ip扫描器(输出某个网段中可连通的ip地址)
#这里以ip:24为例,如果ping同输出,并存入到该网段的文件中;
#!/bin/bash
read -p "please input you need scan ip :" ip
[[ ! "$ip" =~(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0
-4][0-9]|25[0-5])  ]] && echo "please input a correct ip"&&exit 10
#取ip:24为例
ip=`echo ${ip} | cut -d"." -f 1-3`
>/app/${ip}.0.log
for i in {1..255}
do
        {
        if ping -c1 -w1 ${ip}.${i} &> /dev/null;then
                echo ${ip}.${i} is up | tee -a /app/${ip}.0.log
        fi
        }&              #1
done

#输出
[[email protected] ~]# bash o.sh
please input you need scan ip :192.168.241.1
192.168.241.1 is up
192.168.241.2 is up
192.168.241.149 is up
192.168.241.184 is up

由于一秒执行一个ip需要很多时间,所以这么使用了并行;

以上解决ip:n的判断就可以做一个真正的扫描器了;

9.4循环结构(while)

while CONDITION

do

? 循环体

done

9.4.1实例1:维持服务一直开启的脚本,以httpd为例,有待继续完善;
#!/bin/bash
sleeptime=30
>/app/httpd.log
while true 
do
        if killall -0 httpd &>/dev/null ;then
                true
        else
                systemctl restart httpd
                time=`date +"%F %T"`
                echo "At ${time} httpd is restarted " >> /app/httpd.log
        fi
        sleep ${sleeptime}
done

这里还是存在一个,这个脚本会一直站着终端,因为是死循环,所以最好是放到后台执行,最好放到screen或者nohup里,这样又能避免断网等问题

[[email protected] ~]# nohup ./o.sh &
[1] 45109
[[email protected] ~]# nohup: ignoring input and appending output to ‘nohup.out’

这样就不用管,他会在后台一直判断这个httpd是否开启

9.4.2实例2:监控网络连接数,大于10就拒绝掉
#!/bin/bash
sleeptime=10
file="connection.txt"
while true 
do
        > ${file}
        ss -nt |grep "ESTAB"| tr -s " " ":"|cut -d: -f6|sort|uniq -c >${file}
        while read num ip 
        do
                if [ ${num} -gt 10 ];then
                        iptables -A INPUT -s ${ip} -j REJECT
                fi
        done < ${file}
        sleep ${sleeptime}
done

iptables -vnL是看ip禁止什么的情况

[[email protected] ~]# iptables -vnL
Chain INPUT (policy ACCEPT 13 packets, 1040 bytes)
 pkts bytes target     prot opt in     out     source               destination         
  211 45228 REJECT     all  --  *      *       192.168.241.1        0.0.0.0/0            reject-with icmp-port-unreachable
    0     0 REJECT     all  --  *      *       192.168.241.1        0.0.0.0/0            reject-with icmp-port-unreachable
    0     0 REJECT     all  --  *      *       192.168.241.1        0.0.0.0/0            reject-with icmp-port-unreachable
    0     0 REJECT     all  --  *      *       192.168.241.1        0.0.0.0/0            reject-with icmp-port-unreachable
    0     0 REJECT     all  --  *      *       192.168.241.1        0.0.0.0/0            reject-with icmp-port-unreachable
    0     0 REJECT     all  --  *      *       192.168.241.1        0.0.0.0/0            reject-with icmp-port-unreachable
    0     0 REJECT     all  --  *      *       192.168.241.1        0.0.0.0/0            reject-with icmp-port-unreachable

iptables -F清空策略

[[email protected] ~]# iptables -F
[[email protected] ~]# iptables -vnL
Chain INPUT (policy ACCEPT 13 packets, 1252 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 7 packets, 1052 bytes)
 pkts bytes target     prot opt in     out     source               destination               
[[email protected] ~]# 
9.4.3实例3:lastb 中最近100条记录中拒绝失败次数超过5次的IP连接(如果此IP已经被拒绝,就不要再重复拒绝了)
[[email protected] ~]# lastb
root     ssh:notty    192.168.241.149  Tue Aug 14 19:46 - 19:46  (00:00)
root     ssh:notty    192.168.241.149  Tue Aug 14 19:46 - 19:46  (00:00)    
root     ssh:notty    192.168.241.149  Tue Aug 14 19:46 - 19:46  (00:00)    
root     ssh:notty    192.168.241.149  Tue Aug 14 19:46 - 19:46  (00:00)
  

#假设对wang用户错误的访问4次
[[email protected] ~]# lastb| grep "root" |wc -l
4
lastb|egrep -o "[0-9]{3}..*.[0-9]{3}"|uniq -c
4 192.168.241.149
方法同上
9.5循环结构(while的特殊用法)

while循环的特殊用法(遍历文件的每一行):

while read line;

do

? 循环体

done < /PATH/FROM/SOMEFILE

依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

双小括号方法,即((...))格式,也可以用于算术运算

双小括号方法也可以使bash Shell实现C语言风格的变量操作

? I=10

? ((I++))

for循环的特殊格式:

for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))

do

? 循环体

done

控制变量初始化:仅在运行到循环代码段时执行一次

控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

9.5.1扫描/etc/passwd文件每一行,如发现GECOS字段为空,则填 充用户名和单位电话为62985600,并提示该用户的GECOS信 息修改成功。
#!/bin/bash
while read line 
do
        gecos=`echo "$line" | cut -d: -f5`
        uname=`echo "$line" | cut -d: -f1`
        [ -z "$gecos" ] && chfn -f ${uname} -p 62985600 ${uname} &>/dev/null &&echo "$
{uname} is modify success!"
done < /etc/passwd

[[email protected] ~]# bash oo.sh   
abrt is modify success!
chrony is modify success!
ntp is modify success!
setroubleshoot is modify success!
gdm is modify success!
gnome-initial-setup is modify success!
postfix is modify success!
tcpdump is modify success!
mage is modify success!
li is modify success!
natasha is modify success!
harry is modify success!
sarah is modify success!
apache is modify success!
alice is modify success!
tom is modify success!
bash is modify success!
testbash is modify success!
basher is modify success!
sg is modify success!
sh is modify success!
nologin is modify success!

这里的[ -z "$gecos" ] 就是前面讲到的" ",是非常重要的;

9.5.2磁盘预警功能实现
#!/bin/bash
used=10             #这值可以随意修改
df -h |grep "/dev/sda*" |while read line
do
        per=`echo $line |sed -nr "s/.* (.*)%.*/1/p"`
        part=`echo $line|tr -s " " |cut -d" " -f1`
        if [ "$per" -gt "$used" ];then
                echo "${part} will be full,already over ${used},now is ${per}"
        
        fi
done

#目前磁盘的用量
[[email protected] ~]# df -h |grep "/dev/sda*"
/dev/sda2        50G  7.5G   43G  15% /
/dev/sda3        20G   33M   20G   1% /app
/dev/sda1      1014M  158M  857M  16% /boot

#输出
[[email protected] ~]# bash oo.sh         
/dev/sda2 will be full,already over 10,now is 15
/dev/sda1 will be full,already over 10,now is 16
[[email protected] ~]# 

在把他放到计划任务里,就更加完善

crontab -e
*/5 * * * * / /root/oo.sh

crontab -e中是不用写用户名的,在改配置文件是要写的,/etc/crontab,/etc/cron.d,一共这3个地方可以加定时;

10循环结构(unit)与退出

以for和while为主,unit就没什么必要;

until CONDITION; do 循环体

done

进入条件: CONDITION 为false

退出条件: CONDITION 为true

exit [n] :退出当前shell,在脚本中应用则表示退出整个脚本(子shell)。其中数值n表示退出状态码。

break [n] :退出整个循环,包括for、while、until和select语句。其中数值n表示退出的循环层次。

continue [n] :退出当前循环进入下一次循环。n表示继续执行向外退出n层的循环。默认n=1,表示继续当前层的下一循环,n=2表示继续上一层的下一循环。

return [n] :退出整个函数。n表示函数的退出状态码。

11select循环与菜单

select variable in list

do

循环体命令

done

select 循环主要用于创建菜单,按数字顺序排列的 菜单项将显示在标准错误上,并显示 PS3 提示符, 等待用户输入

用户输入菜单列表中的某个数字,执行相应的命令

用户输入被保存在内置变量 REPLY 中

select与case

select 是个无限循环,因此要记住用 break 命令退 出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环

select 经常和 case 联合使用

与 for 循环类似,可以省略 in list,此时使用位置 参量

PS1是控制命令行提示符;

PS2是控制<<

>

>这种多行重定向的提示符;

PS3是控制select的提示符;

#/bin/bash
PS3="please choose the menu number:"
select menu in apple paer water sjbr quit
do
        case $REPLY in
                1|apple)
                        echo " sell 5 yuan"
                        ;;
                2|paer)
                        echo " sell 6 yuan "
                        ;;
                3|water)
                        echo " sell 7 yuan "
                        ;;
                4|sjbr)
                        echo " sell 8 yuan " 
                        ;;
                5|quit)
                        break
                        ;;
                *)
                        echo "${REPLY},wrong ?"
                        echo "try choose again"
                        ;;
        esac
done

#输出
[[email protected] ~]# ./select.sh     
1) apple
2) paer
3) watermaile
4) sjbr
5) quit
please choose the menu number:1
 sell 5 yuan

12信号捕捉trap

trap ‘触发指令‘ 信号

自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作 

trap ‘‘ 信号

? 忽略信号的操作

trap ‘-‘ 信号

? 恢复原信号的操作

trap -p

? 列出自定义信号操作

12.1trap的执行在什么时候发生?
#!/bin/bash
trap ‘echo "INTERRUPTED!"; exit‘ INT
sleep 100

[[email protected] ~]# bash bb1.sh 
^CINTERRUPTED!
#在终端下按下ctrl+c会立即终止,打印信号

#但是,如果在运行的时候,在开一个终端用killall -2 bash,那么在100秒后才执行trap
[[email protected] ~]# bash bb1.sh 
INTERRUPTED!

由以上的实验可以说明,trap是等到命令结束后在处理信号量的,因为Bash 等终端的默认行为是这样的:当按下 CTRL-C 之后,它会向当前的整个进程组发出 SIGINT 信号。而 sleep 是由当前脚本调用的,是这个脚本的子进程,默认是在同一个进程组的,所以也会收到 SIGINT 并停止执行,返回主进程以后 trap 捕捉到了信号。

#!/bin/bash
trap "echo do not break" INT
trap -p
for i in {1..5}
do
        echo ${i}
        sleep 0.5
done

trap "" 2
for i in {6..10}
do
        echo ${i}
        sleep 0.5
done

trap "-" SIGINT
for i in {11..15}
do
        echo ${i}
        sleep 0.5
done

#输出
[[email protected] ~]# bash bb.sh
trap -- ‘echo do not break‘ SIGINT
1
2
3
^Cdo not break
4
5
6
7
^C8
9
10
11
12
13
^C
[[email protected] ~]#

13.函数

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程

它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分

函数和shell程序比较相似,区别在于:

? Shell程序在子Shell中运行

? 而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改(而且只对当前shell有效)

13.1定义函数

函数由两部分组成:函数名和函数体

help function

语法一:

f_name (){ ...函数体...

}

语法二:

function f_name {

...函数体... }

语法三:

function f_name () {

...函数体... }

13.2函数使用

函数的定义和使用:

? 可在交互式环境下定义函数

? 可将函数放在脚本文件中作为它的一部分

? 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行

调用:给定函数名

? 函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止

13.3函数返回值

函数有两种返回值:

函数的执行结果返回值:

(1) 使用echo等命令进行输出

(2) 函数体中调用命令的输出结果

函数的退出状态码:

(1) 默认取决于函数中执行的最后一条命令的退出状态码

(2) 自定义退出状态码,其格式为:

return 从函数中返回,用最后状态命令决定返回值

return 0 无错误返回。

return 1-255 有错误返回

return 与 exit的区别,return是退出一个函数,exit是退出整个脚本

13.4交互式环境下定义和使用函数:

[[email protected] ~]# dir (){
> ls -l
> }

[email protected] ~]# dir                #效果与ls -l相同
total 11692
-rw-------.   1 root root     1969 Jun  3 20:07 anaconda-ks.cfg
-rwxr-xr-x    1 root root      278 Aug 13 20:38 del.sh
-rwxr-xr-x    1 root root      309 Aug  5 17:20 dengyao.sh
drwxr-xr-x.   2 root root        6 Jun  3 20:28 Desktop
drwxr-xr-x.   2 root root        6 Jun  3 20:28 Documents
drwxr-xr-x.   2 root root        6 Jun  3 20:28 Downloads
...

[[email protected] ~]# unset dir
#该dir函数将一直保留到用户从系统退出,保证良好的习惯将其删除 
13.5在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至 shell首次发现它后才能使用

调用函数仅使用其函数名即可

示例:

#!/bin/bash 
#func1 
hello()
{
echo "Hello there today‘s date is `date +%F`" 
}
echo "now going to the function hello" 
hello
echo "back from the function"
13.6使用函数文件

可以将经常使用的函数存入函数文件,然后将函数文件载入shell

文件名可任意选取,但最好与相关任务有某种联系。例如 :functions.main

一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表 包括已经载入shell的所有函数

若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

13.6.1创建函数文件

函数文件示例:

#!/bin/bash 
#functions.main 
findit()
{
if [ $# -lt 1 ] ; then 
    echo "Usage:findit file" return 1
fi
find / -name $1 –print }
13.6.2载入函数

函数文件已创建好后,要将它载入shell

定位函数文件并载入shell的格式:

. filename 或 source filename

示例:

 . functions.main
 source functions.main
13.6.3检查载入函数

1.使用set命令检查函数是否已载入。set命令将在shell中显示所有的载入函数

2.执行shell函数

3.要执行函数,简单地键入函数名即可

4.删除shell函数

现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命令完成删除函数

命令格式为:

 unset function_name

示例:

 unset findit

再键入set命令,函数将不再显示

13.7环境函数

使子进程也可使用

声明:export -f function_name

查看:export -f 或 declare -xf

13.8函数参数

函数可以接受参数(跟脚本类似):

传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfunc arg1 arg2 ...”

在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用[email protected], $*, $#等特殊变量

13.9作用域
环境变量:当前shell和子shell有效

本地变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数

? 局部变量:函数的生命周期;函数结束时变量被自动销毁

在函数中定义局部变量的方法

local NAME=VALUE

13.10函数递归
函数直接或间接调用自身

考虑边界值是递归的入口

阶乘实例:

#!/bin/bash
fact(){
        [ "$1" -eq 0 ]&&echo 1 &&return
        echo $[ `fact $[ ${1}-1 ]`*${1} ]
}

read -p "please input a number:" n
fact ${n}

#输出
[[email protected] ~]# bash aaa.sh 
please input a number:3
6
[[email protected] ~]# bash aaa.sh
please input a number:7
5040
13.11fork炸弹

fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源

函数实现

:(){ :|:& };:

bomb() { bomb | bomb & }; bomb

脚本实现

cat Bomb.sh

#!/bin/bash

./$0|./$0&

14数组

? 变量:存储单个元素的内存空间

? 数组:存储多个元素的连续的内存空间,相当于多个变量的集合

14.1 数组名和索引

? 索引:编号从0开始,属于数值索引

? 注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持 bash的数组支持稀疏格式(索引不连续)

? 声明数组:

? declare -a ARRAY_NAME

? declare -A ARRAY_NAME: 关联数组 (必须先声明后使用,一旦没有-A处理会被当做是普通数组,也就是2者不可以转换;)

14.2数组赋值

? (1) 一次只赋值一个元素

ARRAY_NAME[INDEX]=VALUE 

weekdays[0]="Sunday" 

weekdays[4]="Thursday"

? (2) 一次赋值全部元素

ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...) 
#(列表形式都可以,且以空格	 
为分隔符)

? (3) 只赋值特定元素

ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...) 

? (4) 交互式数组值对赋值

read -a ARRAY

#显示所有数组:declare -a   
14.3引用数组

? 引用数组元素:

${ARRAY_NAME[INDEX]} 
#注意:省略[INDEX]表示引用下标为0的元素

? 引用数组所有元素:

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

? 数组的长度(数组中元素的个数):

${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

? 删除数组中的某元素:导致稀疏格式

unset ARRAY[INDEX]

? 删除整个数组:

unset ARRAY

数组的稀疏性质

[[email protected] ~]# a=(1 2 3 4 5 6 7 8 9 10)
[[email protected] ~]# echo ${a[*]}
1 2 3 4 5 6 7 8 9 10
[[email protected] ~]# unset a[4]
[[email protected] ~]# unset a[7]
[[email protected] ~]# echo ${a[*]}
1 2 3 4 6 7 9 10
[[email protected] ~]# echo ${a[9]}
10

删除数字的内容,不会导致数组前移,原来站什么位置还是站什么位置;

14.4数组数据处理
14.4.1引用数组中的元素:

? 数组切片:${ARRAY[@]:offset:number}

? offset: 要跳过的元素个数

? number: 要取出的元素个数

? 取偏移量之后的所有元素

? ${ARRAY[@]:offset}

14.4.2向数组中追加元素:
ARRAY[${#ARRAY[*]}]=value
#${#ARRAY[*]}就是数组的长度
14.5关联数组:
declare -A ARRAY_NAME

ARRAY_NAME=([idx_name1]=‘val1‘ [idx_name2]=‘val2‘...)

注意:关联数组必须先声明再调用

14.5.1关联数组用法(键值对):

再次使用磁盘预警的示例

#/bin/bash
declare -A disk
df|grep ‘/dev/sd‘ > file.txt
while read line 
do
        index=`echo $line|cut -d" " -f1`
        per=`echo $line |sed -r ‘s/.* ([0-9]+)% .*/1/‘`
        disk[${index}]=${per}
done < file.txt
echo ${disk[*]}
#这样就可以通过设备名去访问使用量了

注意这里会存在一个问题:不能直接用df|while,这样df会直接去开一个子进程,导致变量都是子进程的,父进程拿不到,所以要导入到文件中,才能存的住;

14.6数组示例
14.6.1生成10个随机数保存于数组中,并找出其最大值和最小值
#!/bin/bash
declare -a rand 
declare -i max=0 
declare –i min=32767                #这是因为$RANDOM的取值范围就是[0-32767]
for i in {0..9}; 
do
    rand[$i]=$RANDOM
    echo ${rand[$i]}
    [ ${rand[$i]} -gt $max ] && max=${rand[$i]} 
    [ ${rand[$i]} -lt $min ] && min=${rand[$i]}
done
echo "Max: $max Min:$min"
14.6.2编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以 .log结尾的文件;统计出其下标为偶数的文件中的行数之和
#!/bin/bash
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); 
do
    if [ $[$i%2] -eq 0 ];then
        let lines+=$(wc -l ${files[$i]} | cut -d‘ ‘ -f1)
    fi 
done
echo "Lines: $lines."

15字符串处理

15.1字符串切片

${#var}:返回字符串变量var的长度

${var:offset}:返回字符串变量var中从第offset个字符后(不包括 第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)

${var:offset:number}:返回字符串变量var中从第offset个字符后 (不包括第offset个字符)的字符开始,长度为number的部分

${var: -length}:取字符串的最右侧几个字符

? 注意:冒号后必须有一空白字符

${var:offset:-length}:从最左侧跳过offset字符,一直向右取到 距离最右侧lengh个字符之前的内容

${var: -length:-offset}:先从最右侧向左取到length个字符开始 ,再向右取到距离最右侧offset个字符之间的内容

? 注意:-length前空格(在centos6中不支持)

15.2基于模式取子串
15.2.1${var#*word}:其中word可以是指定的任意字符

? 功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符之间的所有字符(包含word)

? 处理密码

[[email protected] ~]# line=`getent passwd root`
[[email protected] ~]# echo ${line}
root:x:0:0:root:/root:/bin/bash
[[email protected] ~]# echo ${line#*root}
:x:0:0:root:/root:/bin/bash
15.2.2${var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
file="var/log/messages"
[[email protected] ~]# echo ${file#*/}
log/messages
[[email protected] ~]# echo ${file##*/}
messages       #为贪婪模式
15.2.3${var%word*}:其中word可以是指定的任意字符;

? 功能:自右而左,查找var变量所存储的字符串中,第一 次出现的word, 删除字符串最后一个字符向左至第一次出现 word字符之间的所有字符;

file="var/log/messages"
[[email protected] ~]# echo ${file%/*}
var/log
[[email protected] ~]# echo ${file%%/*}
var
15.2.4${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符;
[[email protected] ~]# url=http://www.baidu.com:80       
[[email protected] ~]# echo ${url##*:}
80
[[email protected] ~]# echo ${url%%:*}
http
15.3查找替换

? ${var/pattern/substr}:查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之

? ${var//pattern/substr}: 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之

? ${var/#pattern/substr}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之

? ${var/%pattern/substr}:查找var所表示的字符串中, 行尾被pattern所匹配到的字符串,以substr替换之

15.4查找并删除

? ${var/pattern}:删除var所表示的字符串中第一次被pattern所匹配到的字符串

? ${var//pattern}:删除var所表示的字符串中所有被pattern所匹配到的字符串

? ${var/#pattern}:删除var所表示的字符串中所有以pattern为行首所匹配到的字符串

? ${var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串

? 字符大小写转换(仅是显示的时候转换)

? ${var^^}:把var中的所有小写字母转换为大写

? ${var,,}:把var中的所有大写字母转换为小写

变量赋值

技术分享图片

16eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量.该命令对变量进行两次扫描

[[email protected] ~]# cmd=whoami
[[email protected] ~]# echo $cmd
whoami
[[email protected] ~]# eval $cmd
root

[[email protected] ~]# n=10 
[[email protected] ~]# echo {1..${n}}
{1..10}
[[email protected] ~]# eval echo {1..${n}}   
1 2 3 4 5 6 7 8 9 10
16.1间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变量引用 第二个变量的值就称为间接变量引用

variable1的值是variable2,而variable2又是变量名, variable2的值为value,间接变量引用是指通过variable1获 得变量值value的行为

variable1=variable2 
variable2=value

bash Shell提供了两种格式实现间接变量引用

eval tempvar=$$variable1 

tempvar=${!variable1}

示例

[[email protected] ~]# n=name
[[email protected] ~]# name=wuyanzu
#方式1
[[email protected] ~]# n1=${!n}
[[email protected] ~]# echo ${n1}
wuyanzu
#方式2
[[email protected] ~]# eval n2=$$n
[[email protected] ~]# echo $n2
wuyanzu

17创建临时文件(编脚本时需要通过文本存放内容)

mktemp命令:创建并显示临时文件,可避免冲突

mktemp [OPTION]... [TEMPLATE]

? TEMPLATE: filenameXXX

? X至少要出现三个

OPTION:

? -d: 创建临时目录

? -p DIR或--tmpdir=DIR:指明临时文件所存放目录位置

示例

[[email protected] ~]# mktemp /tmp/testXXX
/tmp/testDj4

[[email protected] ~]# mktemp -d /tempXXX
/temptwl

[[email protected] ~]# mktemp --tmpdir=/tmp  testXXXXX
/tmp/test1BJeE

18安装复制文件

install命令(具有 cp、chmod、chown、mkdir的功能):

? install [OPTION]... [-T] SOURCE DEST 单文件

? install [OPTION]... SOURCE... DIRECTORY

? install [OPTION]... -t DIRECTORY SOURCE...

? install [OPTION]... -d DIRECTORY...创建空目录

选项:

? -m MODE,默认755

? -o OWNER

? -g GROUP

示例

install -m 700 -o wang -g admins srcfile desfile 
install –m –d /testdir/installdir

19expect

expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助 Expect处理交互的命令,可以将交互过程如:ssh登录,ftp 登录等写在一个脚本上,使之自动化完成。尤其适用于需要对 多台服务器执行相同操作的环境中,可以大大提高系统管理人 员的工作效率

expect 语法:

expect [选项][ -c cmds ] [[ -[f|b] ] cmdfile ][ args ]

选项

-c:从命令行执行expect脚本,默认expect是交互地执行的

示例

[[email protected] ~]#  expect -c ‘expect "
" {send "pressed enter
"}‘

pressed enter

#捕获

-d:可以输出输出调试信息

示例

expect -d ssh.exp 

expect中相关命令

? spawn:启动新的进程

? send:用于向进程发送字符串

? expect:从进程接收字符串

? interact:允许用户交互

? exp_continue 匹配多个字符串在执行动作后加此命令

expect

expect最常用的语法(tcl语言:模式-动作)

单一分支模式语法:

[[email protected] ~]# expect                #交互式进入
expect1.1> expect "hi" {send "You said hi
"}
dsa
sda
hi
You said hi
expect1.2> 

#匹配到hi后,会输出“you said hi”,并换行 

多分支模式语法:

expect1.2> expect "hi" { send "You said hi
" } "hehe" { send "Hehe yourself
" } "bye" { send "Good bye
" }
das
hi
You said hi
expect1.3> expect "hi" { send "You said hi
" } "hehe" { send "Hehe yourself
" } "bye" { send "Good bye
" }
hehe
Hehe yourself

示例scp

[[email protected] ~]# scp /etc/fstab [email protected]:/app
The authenticity of host ‘192.168.241.149 (192.168.241.149)‘ can‘t be established.
RSA key fingerprint is SHA256:OVU57ARQFUwH+jjWjh/gLwfJlyNx+CeM1A9cJcL2IxU.
RSA key fingerprint is MD5:29:1f:e5:b8:49:0d:da:20:9e:cc:26:6a:3d:a4:9c:90.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added ‘192.168.241.149‘ (RSA) to the list of known hosts.
[email protected]‘s password: 
fstab                                               100%  595   767.0KB/s   00:00 

#需要交互输入yes和密码
#!/usr/bin/expect
spawn scp /etc/fstab [email protected]:/app 
expect {
    "yes/no" { send "yes
";exp_continue }
    "password" { send "123456
" } 
}
expect eof

#可以自动化完成
[[email protected] ~]# ./a2.exp  
spawn scp /etc/fstab [email protected]:/app
[email protected]‘s password: 
fstab                                               100%  595   622.6KB/s   00:00    
[[email protected] ~]# 

示例ssh远程登入

[[email protected] ~]# ssh [email protected]
[email protected]‘s password: 
#也需要交互式
#!/usr/bin/expect
spawn ssh 192.168.241.149 
expect {
    "yes/no" { send "yes
";exp_continue }
    "password" { send "123456
" } 
}
interact
#需要交互操作就要使用interact


[[email protected] ~]# ./a2.exp                
spawn ssh 192.168.241.149
[email protected]‘s password: 
Last login: Fri Aug 10 21:23:17 2018 from 192.168.241.1
[[email protected] ~]#

示例:变量实现

#!/usr/bin/expect
set ip 192.168.8.100 
set user root
set password 123456 
set timeout 10
spawn ssh [email protected]$ip expect {
    "yes/no" { send "yes
";exp_continue }
    "password" { send "$password
" } 
}
interact

示例:位置参数

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2] spawn ssh [email protected]$ip
expect {
    "yes/no" { send "yes
";exp_continue }
    "password" { send "$password
" } }
interact
#./ssh3.exp 192.168.8.100 root magedu

示例:执行多个命令

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2] set timeout 10
spawn ssh [email protected]$ip
expect {
    "yes/no" { send "yes
";exp_continue }
    "password" { send "$password
" } 
}
#登入之后,接下来就是来捕获”]#“
expect "]#" { send "useradd haha
" }
expect "]#" { send "echo magedu |passwd --stdin haha
" } send "exit
"
expect eof

#./ssh4.exp 192.168.8.100 root magedu

示例:shell脚本调用expect

#!/bin/bash
ip=$1
user=$2 
password=$3
expect <<EOF
set timeout 10
spawn ssh [email protected]$ip expect {
    "yes/no" { send "yes
";exp_continue }
    "password" { send "$password
" } 
}
expect "]#" { send "useradd hehe
" }
expect "]#" { send "echo magedu |passwd --stdin hehe
" } 
expect "]#" { send "exit
" }
expect eof
EOF

#./ssh5.sh 192.168.8.100 root magedu

20脚本处理小工具

20.1tr
tr: 
    tr [options] [SET1] [SET2]
    处理输入信息的(转换和删除字符串的,后换前);
    默认是键盘输入,所以你输入tr,跟cat同理,都是等待键盘的输入的;所以tr和cat也可能利用<(输入重定向来)来接收文件的信息;

    参数:
        -t:截断,使得SET1的长度和SET2的长度相同;
            tr -t 123 ab;后面只有2个所以会截断到前面2个,3不做替换;
        -d:指定删
            tr -d ‘a-z‘ < /etc/fstab;
                把所有小写的都删了;
            tr -dc ‘a‘
                会除了a都算,连按的回车也算,所以结束输入需要用ctrl+d;’a
‘这样是保留a和回车;  
        -s:把连续出现的自动内容压缩成一个;
        -c:取反;
            tr -sc ‘a‘;
                就是除了a都去重;   

        tr -s " " :
            压缩了之后再用:代替空格;
            这种方法在日后的操作中非常多用,当有很多分割符的时候可以用tr来改成一个来处理;    
20.1.1tr的映射

tr是映射而不是替换,是因为两个结果集替换的时候符号位置是一一对应的。如果SET1比SET2短,则SET2多余的部分会被忽略,如果SET1比SET2长,POSIX认为这是不合理的,但也能执行,只不过结果有些意料之外,见下文。例如下面的例子,因为SET1中只有一个符号" ",于是替换时SET2中的Y被忽略。

SET1<SET2

[[email protected] ~]# ls /app |tr ‘
‘ "XY" 
192.168.241.0.logXbackup.shXf1Xhaha.logXhttpd.logXi_f.shXissueXnohup.outXscan_ip.shXsysconfig12018-08-03.tar.xzX[[email protected] ~]# 

SET1>SET2

[[email protected] ~]# echo abcabcdfgqwedfg|tr ‘abc‘ ‘xy‘
xyyxyydfgqwedfg

以上分析来看,a—>x和b,c—>y,并非是你所想的abc一个整体对应xy

使用-t截断

[[email protected] ~]# echo abcabcdfgqwedfgafb|tr -t ‘abc‘ ‘xy‘
xycxycdfgqwedfgxfy

这样就是a—>x,b—>y,而c作为截断在外的

所以说tr不是以某一个字符串与某一个字符串的替换,而是字符之间的映射;

实现简单的加密和解密

[[email protected] ~]# echo "12345" |tr "0-9" "9876543210"               #加密
87654
[[email protected] ~]# echo "87654" |tr "0-9" "9876543210"                #解密
12345

tr的SET1到SET2的映射就是作为秘钥了,只要有一套自己的秘钥,那么就可以简单实现加解密了;

20.1.2tr的压缩

-s选项是用来压缩,压缩指定连续的字符一个或者多个;在文本处理中与cut配合来取出某一列是相当快捷,简单的

实例知道磁盘的使用率

[[email protected] ~]# df -h | tr -s " "
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 50G 7.5G 43G 15% /
devtmpfs 485M 0 485M 0% /dev
tmpfs 500M 0 500M 0% /dev/shm
tmpfs 500M 52M 449M 11% /run
tmpfs 500M 0 500M 0% /sys/fs/cgroup
/dev/sda3 20G 33M 20G 1% /app
/dev/sda1 1014M 158M 857M 16% /boot
tmpfs 100M 36K 100M 1% /run/user/0
/dev/sr0 8.1G 8.1G 0 100% /run/media/root/CentOS 7 x86_64

#不指定SET2,仅仅是压缩,也可压缩替换一起使用

[[email protected] ~]# df -h | tr -s " " ":"
Filesystem:Size:Used:Avail:Use%:Mounted:on
/dev/sda2:50G:7.5G:43G:15%:/
devtmpfs:485M:0:485M:0%:/dev
tmpfs:500M:0:500M:0%:/dev/shm
tmpfs:500M:52M:449M:11%:/run
tmpfs:500M:0:500M:0%:/sys/fs/cgroup
/dev/sda3:20G:33M:20G:1%:/app
/dev/sda1:1014M:158M:857M:16%:/boot
tmpfs:100M:36K:100M:1%:/run/user/0
/dev/sr0:8.1G:8.1G:0:100%:/run/media/root/CentOS:7:x86_64

#最终配合cut取出
[[email protected] ~]# df -h | tr -s " " ":"|cut -d: -f5
Use%
15%
0%
0%
11%
0%
1%
16%
1%
100%
20.1.3tr的删除与补集

如果SET2指定多个字符,将只取最后一个字符作为替换字符

[[email protected] ~]# echo dasdadad|tr -c "d
" "ac"  
dccdcdcd
#只对c生效

一般-d与-c连用,如实例从随机数中获取只要是随机数字字母的密码

9J8XZGk3[[email protected] ~]# openssl rand -base64 50|tr -dc "[0-9][a-z][A-Z]
"|head -c8
ZYS1uwNB[[email protected] ~]# openssl rand -base64 50|tr -dc "[0-9][a-z][A-Z]"|head -c8  
LODdrtL1[[email protected] ~]# openssl rand -base64 50|tr -dc "[0-9][a-z][A-Z]"|head -c8
cc0PZ9tO[[email protected] ~]# 
20.2cut

cut:按列抽取文本;
-d:指定分隔符,默认tab;
-f:
#:第#个字段;
#,#,[#]:离散的多个字段,例如1,3,6
#-#:连续的多个字段,例如1-6
混合使用:1-3,7
-c:按字符切割
--output-delimiter:
给输出指定分隔符;

? -b:按字节筛选

? -n:与"-b"选项连用,表示禁止将字节分割开来操作;

? -s:避免打印不包含分隔符的行

? --complement:补足被选择的字节、字符或字段(反向选择的意思或者说是补集);

?

20.2.1cut的切割功能
[[email protected] ~]# echo "a   b" |cut -d" " -f1
a
[[email protected] ~]# echo "a   b" |cut -d" " -f2

[[email protected] ~]# echo "a   b" |cut -d" " -f3

[[email protected] ~]# echo "a   b" |cut -d" " -f4
b
[[email protected] ~]# echo "a   b" |cut -d" " -f4

#当多个空格时他也是一个个区分的,所以当一列的空格数不一的时候,这就需要用tr来压缩,所以说,2者是最佳拍档
20.2.2-s:避免打印不包含分隔符的行
[[email protected] ~]# cut -d: -f2 -s a.txt
a
b
c
[[email protected] ~]# cut -d: -f2  a.txt  
a
b
c
asdfs
20.2.3--complement:补集;
[[email protected] ~]# df -h | tr -s " " ":"|cut -d: -f1,5 --complement 
Size:Used:Avail:Mounted:on
50G:7.5G:43G:/
485M:0:485M:/dev
500M:0:500M:/dev/shm
500M:52M:449M:/run
500M:0:500M:/sys/fs/cgroup
20G:33M:20G:/app
1014M:158M:857M:/boot
100M:36K:100M:/run/user/0
8.1G:8.1G:0:/run/media/root/CentOS:7:x86_64
21sort

默认的排序规则为字符集排序规则,通常几种常见字符的顺序为:"空字符串<空白字符<数值<a<A<b<B<...<z<Z",这也是字典排序的规则。

sort [OPTION]... [FILE]...
#加*为常用项
 
选项说明:
-c:检测给定的文件是否已经排序。如未排序,则会输出诊断信息,提示从哪一行开始乱序。
-C:类似于"-c",只不过不输出任何诊断信息。可以通过退出状态码1判断出文件未排序。
-m:对给定的多个已排序文件进行合并。在合并过程中不做任何排序动作。
-b:忽略字段的前导空白字符。空格数量不固定时,该选项几乎是必须要使用的。"-n"选项隐含该选项。
-d:按照字典顺序排序,只支持字母、数值、空白。除了特殊字符,一般情况下基本等同于默认排序规则。
--debug:将显示排序的过程以及每次排序所使用的字段、字符。同时还会在最前几行显示额外的信息。
*-f:将所有小写字母当成大写字母。例如,"b"和"B"是相同的。
  :在和"-u"选项一起使用时,如果排序字段的比较结果相等,则丢弃小写字母行。
*-k:指定要排序的key,key由字段组成。key格式为"POS1[,POS2]",POS1为key起始位置,POS2为key结束位置。
*-n:按数值排序。空字符串""或""被当作空。该选项除了能识别负号"-",其他所有非数字字符都不识别。
  :当按数值排序时,遇到不识别的字符时将立即结束该key的排序。
-M:按字符串格式的月份排序。会自动转换成大写,并取缩写值。规则:unknown<JAN<FEB<...<NOV<DEC。
-o:将结果输出到指定文件中。
*-r:默认是升序排序,使用该选项将得到降序排序的结果。
  :注意:"-r"不参与排序动作,只是操作排序完成后的结果。
-s:禁止sort做"最后的排序"。
*-t:指定字段分隔符。
  :对于特殊符号(如制表符),可使用类似于-t$‘	‘或-t‘ctrl+v,tab‘(先按ctrl+v,然后按tab键)的方法实现。
*-u:只输出重复行的第一行。结合"-f"使用时,重复的小写行被丢弃。

1、不加任何选项时,将对整行从第一个字符开始依次向后直到行尾按照默认的字符集排序规则做升序排序。而且以"空字符串<空白字符<数值<a<A<b<B<...<z<Z"的顺序排序

[[email protected] ~]# cat ./a.txt|sort

" "
1:a
2:b
3:c
a
A
Aadfs
asdfs       #单字符a与A的排序确实是a<A,如果a或者A后面还有字符,那么他们属于同级,看后面字符的顺序
b
B
c
C
dsddasda

单字符a与A的排序确实是a<A,如果a或者A后面还有字符,那么他们属于同级,看后面字符的顺序

2.以某一列为排序列进行排序。由于要划分字段,所以指定字段分隔符(sort中的k、t相对于cut中的f、d,如果比较的是数字应该加-n,不然会当做字符串依次比较)

[[email protected] ~]# cat a.txt 
1:a:300
2:b:100
3:c:200

[[email protected] ~]# sort -t: -k3 -n a.txt 
2:b:100
3:c:200
1:a:300

3.--debug可以调试过程

[[email protected] ~]# sort --debug -t: -k3 -n a.txt 
sort: using ‘en_US.UTF-8’ sorting rules
sort: key 1 is numeric and spans multiple fields
2:b:100
    ___
_______
3:c:200
    ___
_______
1:a:300
    ___
_______
[[email protected] ~]# 

4.若某一行排序有许多相同的值,需要二次排序的时候

[[email protected] ~]# sort -t: -k3n  -k4r  a.txt   
2:b:100:c
3:c:200:b
a:d:200:a
1:a:300:d

-n:按数值排序。空字符串""或""被当作空。该选项除了能识别负号"-",其他所有非数字字符都不识别。 :当按数值排序时,遇到不识别的字符时将立即结束该key的排序。

5.-u去除重复字段所在的行(只保留排在前面的第一行)

[[email protected] ~]# sort -t: -k3n  -k4r  -u a.txt 
2:b:100:c
3:c:200:b
a:d:200:a
1:a:300:d
[[email protected] ~]# sort -t: -k3n    -u a.txt     
2:b:100:c
3:c:200:b
1:a:300:d

说明第一次试验是2次排完在去重,那么重心在第4列了,所以-u是没有效果的;

22uniq

uniq是去重,不相邻的行不算重复值。

uniq [OPTION]... [INPUT [OUTPUT]]

选项说明:

*-c:统计出现的次数(count)。

*-d:只显示被计算为重复的

-D:显示所有被计算为重复的

*-u:显示唯一值,即没有重复值的

-i:忽略大小写。

-z:在末尾使用,而不是换行符。

-f:跳过多少个字段(field)开始比较重复值。

-s:跳过多少个字符开始比较重复值。

-w:比较重复值时每行比较的最大长度。即对每行多长的字符进行比较。

[[email protected] ~]# cat a.txt         
111
223
56
111
111
567
223
[[email protected] ~]# uniq a.txt 
111
223
56
111
567
223

所以说uniq去排除连续的重复值,跟tr的-s一样;

[[email protected] ~]# sort a.txt
111
111
111
223
223
56
567
[[email protected] ~]# sort a.txt |uniq
111
223
56
567

#使用-d参数来显示哪几行是重复的
[[email protected] ~]# sort a.txt |uniq -d
111
223

#-u正好相反显示没有重复的行
[[email protected] ~]# sort a.txt |uniq -u
56
567

#使用-c统计哪些记录出现的次数
[[email protected] ~]# sort a.txt |uniq -c
      3 111
      2 223
      1 56
      1 567
#sort与uniq必须要连用,不然会出现这样的情况
[[email protected] ~]# cat a.txt |uniq -c    
      1 111
      1 223
      1 56
      2 111
      1 567
      1 223

两者经常搭配使用,做到去重,很重要一点uniq的去重仅仅对相邻2行;

22seq

seq命令用于输出数字序列。支持正数序列、负数序列、小数序列。

seq [OPTION]... LAST                  # 指定输出的结尾数字,初始值和步长默认都为1
seq [OPTION]... FIRST LAST            # 指定开始和结尾数字,步长默认为1
seq [OPTION]... FIRST INCREMENT LAST  # 指定开始值、步长和结尾值

OPTION:
-s:指定分隔符,默认是
。
-w:使用0填充左边达到数字的最大宽度。
[[email protected] ~]# seq 5
1
2
3
4
5
#默认以
作为分隔符
#同样可以以-s指定分隔符
[[email protected] ~]# seq -s ‘-‘ 5
1-2-3-4-5

[[email protected] ~]# seq -s ‘-‘ 5 3 10 
5-8
#中间为指定的步长

[[email protected] ~]# seq -w 99 100 2000       
0099
0199
0299
0399
0499
0599
0699
0799
0899
0999
1099
1199
1299
1399
1499
1599
1699
1799
1899
1999
23wc

Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。

1.命令格式:

wc [选项]文件...

2.命令功能:

统计指定文件中的字节数、字数、行数,并将统计结果显示输出。该命令统计指定文件中的字节数、字数、行数。如果没有给出文件名,则从标准输入读取。wc同时也给出所指定文件的总统计数。

3.命令参数:

-c 统计字节数。

-l 统计行数。

-m 统计字符数。这个标志不能与 -c 标志一起使用。

-w 统计字数。一个字被定义为由空白、跳格或换行字符分隔的字符串。

-L 打印最长行的长度。

-help 显示帮助信息

--version 显示版本信息

以上是关于shell脚本的主要内容,如果未能解决你的问题,请参考以下文章

用于确保在任何给定时间仅运行一个 shell 脚本的 shell 片段 [重复]

常用python日期日志获取内容循环的代码片段

shell脚本引用expect

Shell脚本切割日志

Eclipse 中的通用代码片段或模板

Python如何调用别人写好的脚本