大数据定制篇 - Shell 编程
# 1. Shell 概述
# 1.1 为什么要学习 Shell 编程
- Linux 运维工程师在进行服务器集群管理时,需要编写 Shell 程序来进行服务器管理。
- 对于 JavaEE 和 Python 程序员来说,工作的需要,会要求你编写一些 Shell 脚本进行程序或者是服务器的维护,比如编写一个定时备份数据库的脚本。
- 对于大数据程序员来说,需要编写 Shell 程序来管理集群。
# 1.2 shell 是什么
Shell 是一个命令行解释器,它为用户提供了一个向 Linux 内核发送请求以便运行程序的界面系统级程序,用户可以用 Shell 来启动、挂起、停止甚至是编写一些程序。
# 2. Shell 脚本的执行方式
# 2.1 脚本格式要求
脚本以 #!/bin/bash
开头,需要有可执行权限。
# 2.2 编写第一个 shell 脚本
需求:创建一个 shell 脚本,输出 hello world!
vim hello.sh
#!/bin/bash
echo "hello,world~"
2
3
# 2.3 脚本的常用执行方式
# 🖊 方式一:输入脚本的绝对路径或相对路径
说明:首先要赋予脚本 +x
的权限,再执行脚本
# 🖊 方式二:sh + 脚本
说明:不用赋予脚本+x
权限,直接执行即可
比如 sh hello.sh
# 3. shell 的变量
# 3.1 Shell 变量介绍
- Shell 变量分为系统变量和用户自定义变量。
- 系统变量:
$HOME、$PWD、$SHELL、$USER
等等,比如:echo $HOME
等等。 - 显示 shell 所有系统变量:set 指令
# 3.2 Shell 变量定义
# 基本语法:
- 定义变量:
变量名=值
- 注意在 shell 编程中,这种等号的两边不要打空格。
- 撤销变量:
unset 变量
- 声明静态变量:
readonly 变量
,注意不能 unset
# 快速入门:
#!/bin/bash
# 案例 1:定义变量 A
A=100
# 输出变量需要加上 $
echo A=$A
echo "A=$A"
# 案例 2:撤销变量 A
unset A
echo "A=$A"
# 案例 3:声明静态的变量 B=2,不能撤销
readonly B=2
echo "B=$B"
# 此时如果 unset B,执行时会报错
2
3
4
5
6
7
8
9
10
11
12
13
14
# 补充:shell 脚本的注释:
- 单行注释:
#
- 多行注释:
:<<!
需要注释的第一行内容
需要注释的第二行内容
!
2
3
4
# 3.3 shell 变量的定义
# 定义变量的规则:
- 变量名称可以由字母、数字和下划线组成,但是不能以数字开头。
- 等号两侧不能有空格
- 变量名称一般习惯为大写,这是一个规范,遵守即可
# 将命令的返回值赋给变量:
- A=`date`,通过反引号,运行里面的命令,并把结果返回给变量 A
A=$(date)
等价于反引号
# 3.4 设置环境变量
# 基本语法:
export 变量名=变量值
:将 Shell 变量输出为环境变量source 配置文件
:让修改后的配置文件立即生效echo $变量名
:查询环境变量的值
# 快速入门:
# 通过编辑 /etc/profile 文件配置 JDK 环境变量,要增加以下命令
export JAVA_HOME=/usr/local/java/jdk1.8.0_261
export PATH=$JAVA_HOME/bin:$PATH
# 保存退出 /etc/profile 文件后,执行 source /etc/profile 命令使修改后的配置文件生效
2
3
4
在 /etc/profile 文件中设置的变量是全局变量。而 .bashrc文件(在用户的家目录下)则只对当前用户有用。~/.bashrc、~/.bash_file 是当前用户目录下的配置信息。修改后用 source 命令更新。
# 3.5 位置参数变量
当我们执行一个 Shell 脚本时,如果希望获取到命令行的参数信息,就可以使用到位置参数变量,比如 ./myshell.sh 100 200
, 这个就是一个执行 shell 的命令行,可以在 myshell 脚本中获取到参数信息。
# 3.5.1 基本语法
$n
:n 为数字,$0
代表命令本身,$1-$9
代表第一到第九个参数,十以上的参数需要用大括号,如${10}
$*
:代表命令行中所有的参数,$*
把所有的参数看成一个整体$@
:代表命令行中所有的参数,不过该命令是把每个参数区分对待$#
:代表命令行中所有参数的个数
# 3.5.2 案例
案例:在脚本中获取到命令行的各个参数信息
#!/bin/bash
echo "0=$0 1=$1 2=$2"
echo "命令行所有传入的参数=$*"
echo "$@"
echo "参数的个数=$#"
2
3
4
5
# 3.6 预定义变量
Shell 设计者事先已经定义好的变量,可以直接在 Shell 脚本中使用。不常用。
# 3.6.1 基本语法
$$
:当前进程的进程号$!
:后台运行的最后一个进程的进程号$?
:最后一次执行的命令的返回状态。如果这个变量的值为 0,证明上一个命令正确执行;如果这个变量的值为非 0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确
# 3.6.2 案例
#!/bin/bash
echo "当前进程号=$$"
# 后台方式运行 myShell.sh
./myShell.sh &
echo "最后的的进程号=$!"
echo "执行的值=$?"
2
3
4
5
6
# 4. 运算符
在 Shell 中进行各种运算操作。
# 4.1 基本语法
$((运算式))
或$[运算式]
- 或
expr m + n
。注意 expr 运算符间要有空格- 如果希望将 expr 的结果赋给某个变量,使用反引号 `` 将它们包围起来。
expr \*,/, %
分别代表乘,除,取余- 注意在使用 expr 时乘法前面有个转义符:
\*
- 注意在使用 expr 时乘法前面有个转义符:
# 4.2 案例
#!/bin/bash
# 案例1:计算(2+3)X4 的值
# 使用第一种方式
RES1=$(((2+3)*4))
echo "res1=$RES1"
# 使用第二种方式(推荐)
RES2=$[(2+3)*4]
echo "res2=$RES2"
# 使用第三种方式(较为复杂)
TEMP=`expr 2 + 3`
RES3=`expr $TEMP \* 4`
echo "temp=$TEMP"
echo "res3=$RES3"
echo "执行的值=$?"
# 案例2:请求出命令行的两个参数[整数]的和
SUM=$[$1+$2]
echo "sum=$SUM"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5. 条件判断
# 5.1 基本语法
[ condition ]
(注意 condition 前后要有空格),非空返回 true,可使用 $?
验证(0 为 true,>1 为 false)
# 5.2 常用判断条件
1)=
:字符串是否相等
2)两个整数的比较
选项 | 含义 |
---|---|
-lt | 小于 |
-le | 小于等于 |
-eq | 等于 |
-gt | 大于 |
-ge | 大于等于 |
-ne | 不等于 |
3)按照文件权限进行判断
选项 | 含义 |
---|---|
-r | 有读的权限 |
-w | 有写的权限 |
-x | 有执行的权限 |
4)按照文件类型进行判断
选项 | 含义 |
---|---|
-f | 文件存在并且是一个常规的文件 |
-e | 文件存在 |
-d | 文件存在并是一个目录 |
- 注意使用时,这些
-
不能丢。
# 3.8.3 案例
#!/bin/bash
# 案例1:“ok”是否等于“ok”
# 判断语句:是否 =
if [ "ok" = "ok" ]
then
echo "equal"
fi
# 案例2:23是否大于等于22
# 判断语句:使用 -ge
if [ 23 -ge 22 ]
then
echo "大于"
fi
# 案例3:/root/shcode/aaa.txt 目录中的文件是否存在
# 判断语句:使用 -f
if [ -f /root/shcode/aaa.txt ]
then
echo "存在"
fi
# 其他案例
if [ edu ]
then
echo "hello, edu"
fi
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 6. 流程控制
# 6.1 if 判断
if [ 条件判断式 ]
then
程序
elif [ 条件判断式 ]
then
程序
fi
2
3
4
5
6
7
- 注意:
[ 条件判断式 ]
中括号和条件判断式之间必须有空格,if 与 [ 之间也有空格。
For example:
#!/bin/bash
# 案例:编写一个 Shell 程序,如果输入的参数,大于等于60,则输出“及格了”,如果小于60,则输出 “不及格”
if [ $1 -ge 60 ]
then
echo "及格了"
elif [ $1 -lt 60 ]
then
echo "不及格"
fi
2
3
4
5
6
7
8
9
# 6.2 case 语句
case $变量名 in
"值 1")
如果变量的值等于值 1,则执行程序 1
;;
"值 2")
如果变量的值等于值 2,则执行程序 2
;;
...省略其他分支...
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac
2
3
4
5
6
7
8
9
10
11
12
也许 Shell 程序看起来比较诡异,但当初设计者就是这么设计的,我们也只需要承认即可。
For Example:
#!/bin/bash
# 案例:当命令行参数是1时,输出“周一”,是2时,就输出“周二”,其它情况输出“other”
case $1 in
"1")
echo "周一"
;;
"2")
echo "周二"
;;
*)
echo "other"
;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
# 6.3 for 循环
# 🖊 基本语法 1
for 变量 in 值1 值2 值3 ...
do
程序
done
2
3
4
For Example:
#!/bin/bash
# 案例:打印命令行输入的参数【可以看出 $* 和 $@ 的区别】
# 注意 $* 是把输入的参数,当作一个整体,所以只会输出一行
for i in "$*"
do
echo "num is $i"
done
# 使用 $@ 是把输入的参数,分别对待,所以有几个参数,就会输出几行
for j in "$@"
do
echo "num is $j"
done
2
3
4
5
6
7
8
9
10
11
12
- 从此案例可以看出
$*
和$@
的区别
# 🖊 基本语法 2
for (( 初始值;循环控制条件;变量变化 ))
do
程序
done
2
3
4
For Example:
#!/bin/bash
# 案例:从1加到100的值输出显示
# 定义一个变量 SUM
SUM=0
for (( i=1;i<=100;i++ ))
do
SUM=$[$SUM+i]
done
echo "总和SUM=$SUM"
2
3
4
5
6
7
8
9
# 6.4 while 循环
while [ 条件判断式 ]
do
程序
done
2
3
4
- 注意:
[ 条件判断式 ]
中括号和条件判断式之间必须有空格,while 与 [ 之间也有空格
For Example:
#!/bin/bash
# 案例:从命令行输入一个数 n,统计从 1+...+ n 的值
SUM=0
i=0
while [ $i -le $1 ]
do
SUM=$[$i+$SUM]
i=$[$i+1]
done
echo "总和SUM=$SUM"
2
3
4
5
6
7
8
9
10
# 7. read 读取控制台输入
read [选项] [参数]
- -p:指定读取值时的提示符;
- -t:指定读取值时等待的时间(秒),如果没有在指定的时间内输入,就不再等待了。
- 参数:指定读取值的变量名
For Example:
#!/bin/bash
# 案例1:读取控制台输入一个 NUM1 值
read -p "请输入一个数NUM1=" NUM1
echo "您输入数NUM1=$NUM1"
# 案例2:读取控制台输入一个 NUM2 值,在 10 秒内输入
read -t 10 -p "请输入一个数NUM2=" NUM2
echo "您输入数NUM2=$NUM2"
2
3
4
5
6
7
# 8. 函数
shell 编程有系统函数,也可以自定义函数。
# 8.1 系统函数
这里只抛砖引玉地介绍两个系统函数
# 8.1.1 basename 函数
功能:返回完整路径最后 / 的部分,常用于获取文件名
basename [pathname] [suffix]
basename [string] [suffix]
:会删掉所有的前缀包括最后一个(‘/’)字符,然后将字符串显示出来。
选项:suffix 为后缀,如果 suffix 被指定了,basename 会将 pathname 或 string 中的 suffix 去掉。
案例:请返回 /home/aaa/test.txt 的 test.txt 部分
basename /home/aaa/test.txt
- 如果我们指定后缀 suffix,即
basename /home/aaa/test.txt .txt
,那么显示的只有test
# 8.1.2 dirname 函数
功能:返回完整路径最后 / 的前面的部分,常用于返回路径部分。
dirname 文件绝对路径
:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分)。
案例:请返回 /home/aaa/test.txt 的 /home/aaa 部分
dirname /home/aaa/test.txt
# 8.2 自定义函数
[ function ] funname[()]
{
Action;
[return int;]
}
2
3
4
5
调用直接写函数名:funname [值]
For Example:
#!/bin/bash
# 案例1:计算输入两个参数的和(动态获取),getSum
# 定义函数 getSum
function getSum()
{
SUM=$[$NUM1+$NUM2]
echo "和是=$SUM"
}
# 输入两个值
read -p "请输入一个数NUM1=" NUM1
read -p "请输入一个数NUM2=" NUM2
# 调用自定义函数
getSum $NUM1 $NUM2
2
3
4
5
6
7
8
9
10
11
12
13
可以看出,shell 编程与我们常用的编程语言还是不太一样的,既然学了就看开一点。
# 9. 综合案例
需求:
- 每天凌晨2:10,备份数据库 hspedu 到 /data/backup/db
- 备份开始和备份结束能够给出相应的提示信息
- 备份后的文件要求以备份时间为文件名,并打包成 .tar.gz 的形式,比如:2018-03-12_230201.tar.gz
- 在备份的同时,检查是否有 10 天前备份的数据库文件,如果有就将其删除。
- 编写一个 Shell 脚本。
shell 脚本:
#!/bin/bash
#完成数据库的定时备份
#备份的路径
BACKUP=/data/backup/db
#当前的时间作为文件名
DATETIME=$(date +%Y-%m-%d_%H%M%S)
#可以输出变量调试
#echo ${DATETIME}
echo "======================开始备份=========================="
echo "========备份的路径为 $BACKUP/$DATETIME.tar.gz =========="
#数据库地址
HOST=localhost
#数据库用户名
DB_USER=root
#数据库密码
DB_PW=root
#备份数据库名
DATABASE=hspedu
#创建备份的文件夹
#如果该备份的文件夹有则使用,没有就重新创建一个。
#-p:表示递归创建目录,或者说创建多级目录。
[ ! -d "${BACKUP}/${DATETIME}"] && mkdir -p "${BACKUP}/${DATETIME}"
#执行 MySQL 备份数据库的指令
mysqldump -u${DB_USER} -p${DB_PW} --host=${HOST} ${DATABASE} | gzip > ${BACKUP}/${DATETIME}/$DATETIME.sql.gz
#将文件处理成 tar.gz
cd ${BACKUP}
tar -zcvf $DATETIME.tar.gz ${DATETIME}
# 删除对应的备份目录
rm -rf ${BACKUP}/${DATETIME}
#删除10天前的备份文件
find ${BACKUP} -atime +10 -name "*.tar.gz" -exec rm -rf {} \;
echo "=====================备份文件成功========================="
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
运行 crontab -e 指令,增加下面命令(结尾光标要停留在 h 上),用于定时调用上面的 Shell 脚本:
10 2 * * * /usr/sbin/mysql_db_backup.sh