Linux学习笔记-4

1、Linux shell简介

1.1、Shell概述

Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。

Shell既是一种命令语言,又是一种程序设计语言:

作为命令语言,它交互式地解释和执行用户输入的命令;

作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

Shell它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。Shell是用户与内核进行交互操作的一种接口,目前最流行的Shell称为bash Shell(Bourne Again Shell)

Shell是一门编程语言(解释型的编程语言),即shell脚本(就是在用linux的shell命令编程),Shell脚本程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行

Shell是一种脚本语言,那么,就必须有解释器来执行这些脚本

Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等,习惯上把它们称作一种Shell。我们常说有多少种Shell,其实说的是Shell脚本解释器,可以通过cat /etc/shells命令查看系统中安装的shell,不同的shell可能支持的命令语法是不相同的

sh是Unix 标准默认的shell,由Steve Bourne开发,是Bourne Shell的缩写。

bash是Linux标准默认的shell,本教程也基于bash讲解。bash由Brian Fox和Chet Ramey共同完成,是Bourne Again Shell的缩写。

Shell本身支持的命令并不多,内部命令一共有40个,但是它可以调用其他的程序,每个程序就是一个命令,这使得Shell命令的数量可以无限扩展,其结果就是Shell的功能非常强大,完全能够胜任Linux的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。

1.2、Shell基本格式

代码写在普通文本文件中,通常以.sh结尾,虽然不是强制要求,但希望大家最好这么做

1
2
3
4
5
vi helloworld.sh
--------------------------
#!/bin/bash
## 表示用哪一种shell解析器来解析执行我们的这个脚本程序,这句话只对自执行有效,对于使用sh helloworld.sh无效
echo "hello world" ## 注释也可以写在这里

在这里,我们就写好了一个shell脚本,第一行是固定需要的,表明用哪一种shell解析器来执行我们的这个脚本程序。本质上,shell脚本里面的代码都是就是一些流程控制语句加一些特殊语法再加shell**命令**组成。其中,我们可以当做每一个命令就是shell编程当中的关键字。

1.3、Shell执行方式

1 、sh 方式或者bash 方式

sh helloworld.sh

bash helloworld.sh ## 直接指定用系统默认的bash shell解释执行

2 、source 方式或者. 方式

source命令也称为“点命令”,也就是一个点符号(.),是bash的内部命令。

功能:使Shell读入指定的Shell程序文件并依次执行文件中的所有语句

source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录

用法:

. helloworld.sh

source helloworld.sh

3 、直接执行该脚本文件

可以有两种方式,不过这两种方式的执行,都需要该文件有执行权限

所以在执行之前,我们要更改他的执行权限

1、 切换到该文件所在的路径然后执行命令:

./helloworld.sh

2、 直接以绝对路径方式执行

/home/linux/hellworld.sh

1.4、Shell注释

单行注释:Shell脚本中以#开头的代码就是注释 # xxx

多行注释:Shell脚本中也可以使用多行注释::<<! xxx !

2、Shell基本语法

2.1、变量

2.1.1、系统变量

Linux Shell中的变量分为“系统变量”和“用户自定义变量”

系统变量可以通过set命令查看,用户环境变量可以通过env查看:

常用系统变量:\$PWD \$SHELL \$USER $HOME

2.1.2、自定义变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1.注意, 变量中间不能有空格
A=123
echo $A

# 2.变量中间有空格的话要加引号
- 双引号中间可以引用变量
a=zs
b="this is $a"
echo $b
this is zs
- 单引号引用变量会原样输出
b='this is $a'
echo $b
this is $a

# 3. 要在变量后直接连接字符, 要用{}把变量括起来
${变量名}其它字符
echo ${A}ddd
helloddd

2.1.3、变量高级用法

  1. 撤销变量:unset ABC

  2. 声明静态变量:readonly ABC= ‘abc’ 特点是这种变量是只读的,不能unset

  3. 在一个 .sh 中. 以绝对路径的形式调另一个 .sh

    1. 使用export关键字

      export A=”A in a.sh”

      意味着把变量提升为当前shell 进程中的全局环境变量,可供其他子shell 程序使用,

      A 变量就成了a.sh 脚本所在bash 进程的全局变量,该进程的所有子进程都能访问到变量A

    2. 通过 . /root/bin/b.shsource /root/bin/b.sh 来调用

  4. 总结:

    1. a.sh中直接调用b.sh,会让b.sh在A所在的bash进程的“子进程”空间中执行
    2. 而子进程空间只能访问父进程中用export定义的变量
    3. 一个shell进程无法将自己定义的变量提升到父进程空间中去
    4. source或者“.”号执行脚本时,会让脚本在调用者所在的shell进程空间中执行

2.1.4、反引号赋值

a=`ls -l /root/bin` ##反引号,运行里面的命令,并把结果返回给变量a

另外一种写法:a=$(ls -l /root/bin)

2.1.5、变量有用技巧

形式 说明
${var} 变量本来的值
${var:-word} 如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值
${var:+word} 如果变量 var 被定义,那么返回word,但不改变 var 的值
${var:=word} 如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word
${var:?message} 如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。 若此替换出现在Shell脚本中,那么脚本将停止运行

2.1.6 特殊变量

$? 表示上一个命令退出的状态码

$$ 表示当前进程编号

$0 表示当前脚本名称

$n 表示n 位置的输入参数(n**代表数字,n>=1 )**

$# 表示参数的个数,常用于循环

$* 和$@ 都表示参数列表

注意:$*与$@区别

  1. $*$@ 都表示传递给函数或脚本的所有参数**

  2. 不被双引号” “包含时

    $*$@ 都以\$1 \$2 … \$n 的形式组成参数列表

  3. 当它们被双引号” “包含时

    • “$*“ 会将所有的参数作为一个整体,以”\$1 \$2 … \$n”的形式组成一个整串;
    • $* 会将各个参数分开,以”\$1” “\$2” … “\$n” 的形式组成一个参数列表

2.1.7 变量的其他注意点

  1. 使用变量

    使用一个定义过的变量, 只需要在变量名前面加 $ 符号

    如果变量后面直接跟上了字符串, 就必须要加花括号

    推荐给所有变量加上花括号, 这个是好的编程习惯

    已定义的非只读变量, 可以被重新定义

  2. 只读变量

    使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
    myUrl="http://www.w3cschool.cc"
    readonly myUrl
    myUrl="http://www.runoob.com"
    ---
    # 会报错
    zsh: read-only variable: myUrl
  3. 删除变量, 只读变量不能删除, 变量被删除后不能再次使用

    1
    unset variable_name
  4. 变量类型

    • 1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
    • 2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
    • 3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

2.18 字符串

获取字符串长度

1
2
string="abcd"
echo ${#string} #输出 4

提取子字符串

以下实例从字符串第 2 个字符开始截取 4 个字符:

1
2
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo

查找子字符串

查找字符 “is“ 的位置:

1
2
string="runoob is a great company"
echo `expr index "$string" is` # 输出 8

2.19 Shell 数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。

类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。

定义数组

在Shell中,用括号来表示数组,数组元素用”空格”符号分割开。定义数组的一般形式为:

1
数组名=(值1 值2 ... 值n)

还可以单独定义数组的各个分量:

1
2
3
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

可以不使用连续的下标,而且下标的范围没有限制。

获取数组的长度

获取数组长度的方法与获取字符串长度的方法相同,例如:

1
2
3
4
5
6
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

2.2、运算符

2.2.1、算数运算符

1. 用expr

1
2
3
4
5
6
7
8
格式 expr m + n 注意expr运算符间要有空格
例如计算(2+3)×4 的值
1、分步计算
S=`expr 2 + 3`
expr $S \* 4 ## *号需要转义
2、一步完成计算
expr `expr 2 + 3 ` \* 4
echo `expr \`expr 2 + 3\` \* 4`

expr还可以计算字符串的长度子字符串出现的位置截取字符串等等

1
2
3
4
5
6
7
8
9
cts1 ~ # name='woshishuaige'
cts1 ~ # expr length name
4
cts1 ~ # expr length $name
12
cts1 ~ # expr index $name shuai
3
cts1 ~ # expr substr $name 6 2
sh

详情请翻阅: expr –help

2. 用(())

1
2
3
4
5
6
7
8
((1+2))
(((2+3)*4))
count=1
((count++))
((++count))
echo $count
# 但是要想取到运算结果,需要用$引用
a=$((1+2))

3. 用\$[]

1
2
3
4
5
SS=$[2+3]
echo $SS
SS=$[2*3]
echo $SS
echo $[(2 + 3)*3]

4. 用let

1
2
3
4
first=1
second=2
let third=first+second
echo ${third}

5. 用 | bc

以上命令都只对整形数值有效,不适用于浮点数

如果有浮点数参与运算,可以将echo与bc命令结合起来使用,代码如下

1
2
3
4
5
6
echo "1.212*3" | bc         ## 简单浮点运算
echo "scale=2;3/8" | bc ##将输出结果设置为2位
echo "obase=2;127" | bc ##输出运算结果的二进制
echo "obase=10;ibase=2;101111111" | bc ##将二进制转换成十进制
echo "10^10" | bc ##求幂指数
echo "sqrt(100)" | bc ##开平方

除了用bc做尽职转换以外,还可以这样做:

1
2
3
4
5
echo $((base#number))  表示把任意base进制的数number转换成十进制
例子:
echo $((8#377)) 返回255
echo $((025)) 返回21 , 八进制
echo $((0xA4)) 返回164 , 十六进制

使用bc还可以用来比较浮点数的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@hadoop02 bin]# echo "1.2 < 2" |bc
1
[root@hadoop02 bin]# echo "1.2 > 2" |bc
0
[root@hadoop02 bin]# echo "1.2 == 2.2" |bc
0
[root@hadoop02 bin]# echo "1.2 != 2.2" |bc
1
看出规律了嘛?运算如果为真返回 1,否则返回 0,写一个例子:
[root@hadoop02 bin]# [ $(echo "2.2 > 2" |bc) -eq 1 ] && echo yes || echo no
yes
[root@hadoop02 bin]# [ $(echo "2.2 < 2" |bc) -eq 1 ] && echo yes || echo no
no

2.2.2、关系运算符

下面给出一张关系运算符的列表:

运算符 等同运算符 说明
-eq = 检测两个数是否相等,相等返回true
-ne != 检测两个数是否相等,不相等返回true
-ge >= 检测左边的数是否大等于右边的,如果是,则返回true
-gt > 检测左边的数是否大于右边的,如果是,则返回true
-le <= 检测左边的数是否小于等于右边的,如果是,则返回true
-lt < 检测左边的数是否小于右边的,如果是,则返回true

2.2.3、布尔运算符

运算符 等同运算符 说明
! ! 非运算,表达式为 true 则返回false,否则返回true
-a && 与运算,两个表达式都为true 才返回true
-o \ \ 或运算,有一个表达式为true 则返回true

2.2.4、字符串运算符

运算符 说明
= 检测两个字符串是否相等,相等返回true
!= 检测两个字符串是否相等,不相等返回true
-z 检测字符串长度是否为0,为0返回true
-n 检测字符串长度是否为0,不为0返回true
str 检测字符串是否为空,不为空返回true

2.2.5、文件运算符

运算符 说明
-d 检测文件是否是目录,如果是,则返回true
-f 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回true
-e 检测文件(包括目录)是否存在,如果是,则返回true
-s 检测文件是否为空(文件大小是否大于0),不为空返回true
-r 检测文件是否可读,如果是,则返回true
-w 检测文件是否可写,如果是,则返回true
-x 检测文件是否可执行,如果是,则返回true
-b 检测文件是否是块设备文件,如果是,则返回true
-c 检测文件是否是字符设备文件,如果是,则返回true

2.3、流程控制

2.3.1、if

if.. then ..elif.. then.. else..fi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# if语法格式:
if condition
then
statements
[elif condition
then statements. ..]
[else
statements ]
fi

# 示例程序:

[root@hadoop02 bin]# vi g.sh
#!/bin/bash
## read a value for NAME from stdin
read -p "please input your name:" NAME
## printf '%s\n' $NAME
if [ $NAME = root ]
then
echo "hello ${NAME}, welcome !"
elif [ $NAME = hadoop ]
then
echo "hello ${NAME}, welcome !"
else
echo "I don’t know you !"
fi


## 规则解释
[ condition ] (注:condition前后要有空格)
#非空返回true,可使用$?验证(0为true,>1为false)
[ hadoop ]
#空返回false
[ ]

# 注意[ ]内部的=周边的空格

三元运算符

1
2
[ condition ] && echo OK || echo notok
# 条件满足,执行&&后面的语句;条件不满足,执行||后面的语句

条件判断组合

条件判断组合有两种使用方式: [][[]]

注意它们的区别:

[[ ]] 中逻辑组合可以使用 && || 符号

[] 里面逻辑组合可以用 -a -o

常用判断运算符

1. 字符串比较

= 判断相等
!= 判断不相等
-z 字符串长度是为0返回true
-n 字符串长度是不为0返回true

2. 整数比较

-lt 小于 less than

-le 小于等于

-eq 等于

-gt 大于 great than

-ge 大于等于

-ne 不等于

3. 文件判断

-d 是否为目录

if [ -d /bin ]; then echo ok; else echo notok;fi

-f 是否为文件

if [ -f /bin/ls ]; then echo ok; else echo notok;fi

-e 是否存在

if [ -e /bin/ls ]; then echo ok; else echo notok;fi

2.3.2、while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
while expression
do
command
……
done
----------------------

i=1
while ((i<=3))
do
echo $i
let i++
done
----------------------

#!/bin/bash
i=1
while [ $i -le 3 ]
do
echo $i
let i++
done

# 命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假
# 换种方式:循环体会一直执行,直到条件表达式expression为false
# 注意:上述let i++ 可以写成 i=$(($i+1))或者i=$((i+1))

2.3.3、case

Case语法(通过下面这个例子展示):

1
2
3
4
5
6
7
8
9
10
case $1 in
start)
echo "starting"
;;
stop)
echo "stoping"
;;
*)
echo "Usage: {start|stop}"
esac

2.3.4、for

语法格式:

1
2
3
4
5
6
7
for 变量 in 列表
do
command
……
done

# 列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量

三种方式

1
2
3
4
5
6
7
8
# 方式1
for N in 1 2 3; do echo $N; done

# 方式2
for N in {1..3}; do echo $N; done

# 方式3
for ((i=0; i<=2; i++)); do echo "welcome $i times"; done

2.3.5、util

语法结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
until expression
do
command
……
done

# expression一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。
# 换种方式说:循环体会一直执行,直到条件表达式expression为true

## 示例

#!/bin/bash
## vi util.sh
a=0
until [ ! $a -lt 3 ]
do
echo $a
a=`expr $a + 1`
done
----
输出:
0
1
2

2.4、数组

在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
array_name=(value1 ... valuen)
例子:mingxing=(huangbo xuzheng wangbaoqiang)
也可以单独定义:
mingxing[3]=liujialing
读取数组元素的格式是:
${array_name[index]}
============================================

# 获取数组下标: 前面加 `!`
[linux@linux ~]$ echo ${!mingxing[@]} 或者
[linux@linux ~]$ echo ${!mingxing[*]}
---
输出:
0 1 2 3
============================================

# 输出数组的所有元素:直接取值
[linux@linux ~]$ echo ${mingxing[*]}
[linux@linux ~]$ echo ${mingxing[@]}
============================================

# 获取数组的长度:`#`
[linux@linux ~]$ echo ${#mingxing[*]}
[linux@linux ~]$ echo ${#mingxing[@]}
============================================

# 数组对接: 添加新的值
[linux@linux ~]$ mingxing+=(liuyifei liuyufeng)
============================================

# 删除数组元素,但是会保留数组对应位置,就是该值的下标依然会保留,会空着,之后,还可以填充其他的值进来。
# 删除第一个元素:之后 ${mingxing[0]} 就是空值了
[linux@linux ~]$ unset mingxing[0]
============================================

# 遍历数组:
#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
# 第一种方式
for ((i=0;i<${#IP[*]};i++))
do
echo ${IP[$i]}
done
#第二种方式
for ip in ${IP[*]}
do
echo $ip
done
============================================

# 数组的分片:
${arr[@]:number1:number2}
这里number1从下标number1开始取值,number2往后取几个元素,即取到的新的数组的长度
---
cts1 ~ # arr=(1 2 3 4 5 6 7)
cts1 ~ # echo "{arr[@]:0:3} --- ${arr[@]:0:3} "
{arr[@]:0:3} --- 1 2 3
cts1 ~ # echo "{arr[@]:3:3} --- ${arr[@]:3:3} "
{arr[@]:3:3} --- 4 5 6
cts1 ~ # echo "{arr[@]:4:3} --- ${arr[@]:4:3} "
{arr[@]:4:3} --- 5 6 7

2.5、函数使用

函数的语法使用示例 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@hadoop02 bin]# vi i.sh 
#!/bin/sh
hello(){
echo "`date +%Y-%m-%d`"
# return 2
}
hello
echo “huangbo”
# echo $?
A="mazhonghua"
echo $A
---执行结果----
“huangbo”
mazhonghua

函数的调用方式就是直接写函数名就OK了

1
2
3
注意:
1、必须在调用函数地方之前,先声明函数,shell脚本是逐行运行。不会像其它语言一样先预编译
2、函数返回值,只能通过$? 系统变量获得,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)

脚本调试:

使用-x选项跟踪脚本调试shell脚本,能打印出所执行的每一行命令以及当前状态

sh -x i.sh

或者在代码中加入:set -x

1
2
3
4
5
6
7
8
9
10
11
12
13
cts1 Desktop # sh -x i.sh
+ hello
++ date +%Y-%m-%d
+ echo 2018-05-29
2018-05-29
+ return 2
+ echo $'\342\200\234huangbo\342\200\235'
“huangbo”
+ echo 0
0
+ A=mazhonghua
+ echo mazhonghua
mazhonghua

2.6、函数参数

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vim funcWithParam.sh
--------sh文件--------
#!/bin/bash
# filename=funcWithParam
funcWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funcWithParam 1 2 3 4 5 6 7 8 9 34 73
--------------------
# 调用
cts1 Desktop # sh funcWithParam.sh
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

2.7、跨脚本调用函数

编写一个base.sh脚本,里面放放一个test函数

1
2
3
4
5
6
vim base.sh
------
#!/bin/bash
test(){
echo "hello"
}

再编写一个other.sh脚本,里面引入base.sh脚本,并且调用test函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
vim other.sh
--------
#!/bin/bash
source base.sh ## 引入脚本
test ##调用引入脚本当中的test函数
============================================

# 执行
cts1 Desktop # sh other.sh
other.sh: line 2: ./base.sh: 权限不够
cts1 Desktop # chmod 755 other.sh base.sh
cts1 Desktop # ./other.sh
hello

3、Shell综合案例

3.1、打印9*9乘法表

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
for((i=1;i<=9;++i))
do
for((j=1;j<=i;j++))
do
echo -ne "$i*$j=$((i*j))\t"
done
echo
done

# 解释
-n 不加换行符
-e 解释转义符
echo 换行

效果:
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81

3.2、自动部署集群的JDK

1、 需求描述

1
2
公司内有一个N个节点的集群,需要统一安装一些软件(jdk)
需要开发一个脚本,实现对集群中的N台节点批量自动下载、安装jdk

2、 思路

思考一下:我们现在有一个JDK安装包在一台服务器上。那我们要实现这个目标:

1
2
3
1、	把包传到每台服务器,或者通过本地yum源的方式去服务器取
2、 给每台一台机器发送一个安装脚本,并且让脚本自己执行
3. 要写一个启动脚本,用来执行以上两部操作

3、 Expect**的使用**

蛋疼点:假如在没有配置SSH免密登录的前提下,我们要要是scp命令从一台机器拷贝文件夹到另外的机器,会有人机交互过程,那我们怎么让机器自己实现人机交互?

灵丹妙药:expect

命令 描述
set 可以设置超时,也可以设置变量
timeout 超时等待时间,默认 10s
spawn 执行一个命令
send 执行交互,相当于手动输入
expect “”(expect内部命令) 匹配输出的内容
exp_continue 继续执行下面匹配

思路:模拟该人机交互过程,在需要交互的情况下,通过我们的检测给输入提前准备好的值即可

示例:观看配置SSH免密登录的过程

实现脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vi testExpect.sh

## 定义一个函数
sshcopyid(){
expect -c "
spawn ssh-copy-id $1
expect {
\"(yes/no)?\" {send \"yes\r\";exp_continue}
\"password:\" {send \"$2\r\";exp_continue}
}
"
}

## 调用函数执行
sshcopyid $1 $2

# 注意:如果机器没有expect,则请先安装expect
yum install -y expect

4、 脚本实现

  1. 启动脚本initInstallJDK.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #!/bin/bash

    SERVERS="192.168.123.201"
    PASSWORD=hadoop
    BASE_SERVER=192.168.123.202

    auto_ssh_copy_id() {
    expect -c "set timeout -1;
    spawn ssh-copy-id $1;
    expect {
    *(yes/no)* {send -- yes\r;exp_continue;}
    *password:* {send -- $2\r;exp_continue;}
    eof {exit 0;}
    }";
    }

    ssh_copy_id_to_all() {
    for SERVER in $SERVERS
    do
    auto_ssh_copy_id $SERVER $PASSWORD
    done
    }

    ssh_copy_id_to_all

    for SERVER in $SERVERS
    do
    scp installJDK.sh root@$SERVER:/root
    ssh root@$SERVER chmod 755 installJDK.sh
    ssh root@$SERVER /root/installJDK.sh
    done
  1. 安装脚本installJDK.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash

    BASE_SERVER=192.168.123.202
    yum install -y wget
    wget $BASE_SERVER/soft/jdk-8u73-linux-x64.tar.gz
    tar -zxvf jdk-8u73-linux-x64.tar.gz -C /usr/local

    cat >> /etc/profile <<EOF
    export JAVA_HOME=/usr/local/jdk1.8.0_73
    export PATH=\$PATH:\$JAVA_HOME/bin
    EOF

4、总结

写脚本注意事项:

1、开头加解释器: #!/bin/bash,和注释说明。

2、命名建议规则:变量名大写、局部变量小写,函数名小写,名字体现出实际作用。

3、默认变量是全局的,在函数中变量 local 指定为局部变量,避免污染其他作用域。

4、set -e 遇到执行非 0 时退出脚本, set -x 打印执行过程。

5、写脚本一定先测试再到生产上。

如果帮到你, 可以给我赞助杯咖啡☕️
0%