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 | vi helloworld.sh |
在这里,我们就写好了一个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 | # 1.注意, 变量中间不能有空格 |
2.1.3、变量高级用法
撤销变量:unset ABC
声明静态变量:readonly ABC= ‘abc’ 特点是这种变量是只读的,不能unset
在一个 .sh 中. 以绝对路径的形式调另一个 .sh
使用export关键字
export A=”A in a.sh”
意味着把变量提升为当前shell 进程中的全局环境变量,可供其他子shell 程序使用,
A 变量就成了a.sh 脚本所在bash 进程的全局变量,该进程的所有子进程都能访问到变量A
通过 . /root/bin/b.sh 或 source /root/bin/b.sh 来调用
总结:
- a.sh中直接调用b.sh,会让b.sh在A所在的bash进程的“子进程”空间中执行
- 而子进程空间只能访问父进程中用export定义的变量
- 一个shell进程无法将自己定义的变量提升到父进程空间中去
- 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 … \$n 的形式组成参数列表
当它们被双引号” “包含时
- “$*“ 会将所有的参数作为一个整体,以”\$1 \$2 … \$n”的形式组成一个整串;
- $* 会将各个参数分开,以”\$1” “\$2” … “\$n” 的形式组成一个参数列表
2.1.7 变量的其他注意点
使用变量
使用一个定义过的变量, 只需要在变量名前面加 $ 符号
如果变量后面直接跟上了字符串, 就必须要加花括号
推荐给所有变量加上花括号, 这个是好的编程习惯
已定义的非只读变量, 可以被重新定义
只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
1
2
3
4
5
6
7
myUrl="http://www.w3cschool.cc"
readonly myUrl
myUrl="http://www.runoob.com"
---
# 会报错
zsh: read-only variable: myUrl删除变量, 只读变量不能删除, 变量被删除后不能再次使用
1
unset variable_name
变量类型
- 1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
- 2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
- 3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
2.18 字符串
获取字符串长度
1 | string="abcd" |
提取子字符串
以下实例从字符串第 2 个字符开始截取 4 个字符:
1 | string="runoob is a great site" |
查找子字符串
查找字符 “i 或 s“ 的位置:
1 | string="runoob is a great company" |
2.19 Shell 数组
bash支持一维数组(不支持多维数组),并且没有限定数组的大小。
类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。
定义数组
在Shell中,用括号来表示数组,数组元素用”空格”符号分割开。定义数组的一般形式为:
1 | 数组名=(值1 值2 ... 值n) |
还可以单独定义数组的各个分量:
1 | array_name[0]=value0 |
可以不使用连续的下标,而且下标的范围没有限制。
获取数组的长度
获取数组长度的方法与获取字符串长度的方法相同,例如:
1 | # 取得数组元素的个数 |
2.2、运算符
2.2.1、算数运算符
1. 用expr
1 | 格式 expr m + n 注意expr运算符间要有空格 |
用expr
还可以计算字符串的长度,子字符串出现的位置,截取字符串等等
1 | cts1 ~ # name='woshishuaige' |
详情请翻阅: expr –help
2. 用(())
1 | ((1+2)) |
3. 用\$[]
1 | SS=$[2+3] |
4. 用let
1 | first=1 |
5. 用 | bc
以上命令都只对整形数值有效,不适用于浮点数
如果有浮点数参与运算,可以将echo与bc命令结合起来使用,代码如下
1 | echo "1.212*3" | bc ## 简单浮点运算 |
除了用bc做尽职转换以外,还可以这样做:
1 | echo $((base#number)) 表示把任意base进制的数number转换成十进制 |
使用bc还可以用来比较浮点数的大小:
1 | [root@hadoop02 bin]# echo "1.2 < 2" |bc |
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 | # if语法格式: |
三元运算符
1 | [ 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 | while expression |
2.3.3、case
Case语法(通过下面这个例子展示):
1 | case $1 in |
2.3.4、for
语法格式:
1 | for 变量 in 列表 |
三种方式
1 | # 方式1 |
2.3.5、util
语法结构:
1 | until expression |
2.4、数组
在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为
1 | array_name=(value1 ... valuen) |
2.5、函数使用
函数的语法使用示例 :
1 | [root@hadoop02 bin]# vi i.sh |
函数的调用方式就是直接写函数名就OK了
1 | 注意: |
脚本调试:
使用-x选项跟踪脚本调试shell脚本,能打印出所执行的每一行命令以及当前状态
sh -x i.sh
或者在代码中加入:set -x
1 | cts1 Desktop # sh -x i.sh |
2.6、函数参数
直接上代码
1 | vim funcWithParam.sh |
2.7、跨脚本调用函数
编写一个base.sh脚本,里面放放一个test函数
1 | vim base.sh |
再编写一个other.sh脚本,里面引入base.sh脚本,并且调用test函数:
1 | vim other.sh |
3、Shell综合案例
3.1、打印9*9乘法表
示例代码:
1 |
|
3.2、自动部署集群的JDK
1、 需求描述
1 | 公司内有一个N个节点的集群,需要统一安装一些软件(jdk) |
2、 思路
思考一下:我们现在有一个JDK安装包在一台服务器上。那我们要实现这个目标:
1 | 1、 把包传到每台服务器,或者通过本地yum源的方式去服务器取 |
3、 Expect**的使用**
蛋疼点:假如在没有配置SSH免密登录的前提下,我们要要是scp命令从一台机器拷贝文件夹到另外的机器,会有人机交互过程,那我们怎么让机器自己实现人机交互?
灵丹妙药:expect
命令 | 描述 |
---|---|
set | 可以设置超时,也可以设置变量 |
timeout | 超时等待时间,默认 10s |
spawn | 执行一个命令 |
send | 执行交互,相当于手动输入 |
expect “”(expect内部命令) | 匹配输出的内容 |
exp_continue | 继续执行下面匹配 |
思路:模拟该人机交互过程,在需要交互的情况下,通过我们的检测给输入提前准备好的值即可
示例:观看配置SSH免密登录的过程
实现脚本:
1 | vi testExpect.sh |
4、 脚本实现
启动脚本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
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
安装脚本installJDK.sh
1
2
3
4
5
6
7
8
9
10
11
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、写脚本一定先测试再到生产上。