打怪升级之小白的大数据之旅(四十)
Shell编程
上次回顾
上一章对Linux的常用命令进行了总结分享,命令比较多,VIM是必须要首先熟练的,其次就是按照优先级去练习,本章节对Shell编程进行分享,当然了,我只将后面大数据中可能用到的知识点进行分享,Shell编程真的展开来讲,至少得半个月到一个月。。。我们不是运维,不要纠结,只需要关系我在博客里提到过的知识点哈
Shell概述
- Shell就是一个命令行解释器,就如同我们在Windows中的cmd一样,它用于接收应用程序或者用户的命令,然后再调用操作系统内核
- 比如我们java刚开始在命令行使用的javac编译与java运行命令,就是通过Windows的命令行解析器调用操作系统内核,然后执行我们的java代码
- Linux的shell和Windows的Cmd做的事情是一样的,只不过Linux的Shell更加强大
- 上面说过,Shell是一个命令行解析器,既然是解析器,那么自然就有很多的版本了,在Linux中提供的Shell解析器有:
# 查看shell的解释器 cat /etc/shells /bin/sh /bin/bash /usr/bin/sh /usr/bin/bash /bin/tcsh /bin/csh
- 在我们使用的CentOS中一般默认使用的是bash 解析器,路径是/bin/bash
- Shell也是一个功能相当强大的编程语言,具有易编写、易调试、灵活性强的特点
Shell脚本入门程序
- 入门程序是什么?当然就是hello world啦,Shell编写的程序称为Shell脚本
- 一般我们编写脚本通常使用脚本的后缀名使用
.sh
,sh是bash解析器的一个硬链接,我们可以通过bash xxx.sh进行脚本的运行 - 脚本的格式(我们习惯性的开头指定解析器的脚本)
#!/bin/bash 脚本....
- 第一个脚本程序,首先创建sh文件
vim hello.sh
#!/bin/bash echo "hello world"
- 命令行运行:
bash hello.sh
- 运行脚本的方式还可以直接输入脚本的路径进行运行,但是需要设置可执行的权限,例如我直接
chmod -u+x hello
,然后直接输入这个文件的路径就可以运行 - 使用路径进行脚本运行可以使用相对路径和绝对路径两种方式,我建议使用绝对路径,不过更建议使用bash xx.sh脚本的方式运行
Shell变量
如同java一样,Shell也有变量,只不过它有一点点不同
系统定义的变量
- 我就介绍几个前面遇到的,一般不怎么使用它们,最多使用set这个变量查看一下当前Shell中所有的变量,具体的大家测试一下就懂了
$HOME $PWD $SHELL $USER $PASH set
- set命令可以查看当前shell中所有的变量,不过通常是通过管道过滤来查看某个变量的值的,下面我介绍完自定义变量后会掩饰set的用法
自定义变量
- 自定义变量,就跟java自定义定义变量一样,只不过它不需要声明变量的数据类型
- 基本语法
(1)定义变量:变量=值 (2)撤销变量:unset 变量 (3)声明静态变量:readonly变量,注意:不能unset
- 变量定义规则
(1)变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写。 (2)等号两侧不能有空格 (3)在bash中,变量默认类型都是字符串类型,无法直接进行数值运算。 (4)变量的值如果有空格,需要使用双引号或单引号括起来。
- 注意事项:
- 当我们使用readonly时,这个变量就无法撤销了…
- 另外,在shell中,定出来的变量值都是字符串,因此无法做运算,假如我定义了一个变量num=1+1,在输出后,它的值会是字符串的1+1
- 如果我们定义的变量值有空格,那么我们需要双引号或者单引号包起来
- 当我们定义变量后,它的作用域只是在当前的目录下,如果想定义全局的变量就需要使用
export变量名
,这个知识点我们在搭建hadoop服务器的时候会用到
- 下面进行示例演示,然后演示一下前面提到的set用法,
set
通常配合|grep管道符来查看变量的值# 自定义一个变量name,值是hello,然后使用set变量查看变量name的值 name=hello set grep|$name # 定义变量A A=5 # 给变量A重新赋值 A=8 # 撤销变量A unset A # 声明静态的变量B=2,不能unset\ readonly B=2
特殊变量
- 特殊变量的作用是命令行传参,假设我们需要通过用户来自定义参数,就需要下面的特殊变量
$n
- $n用于接收参数的个数
- 基本语法
$n (功能描述:n为数字,$0代表该脚本名称,$1-$9代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10})
- 示例
# 1. 创建一个sh脚本文件 vim parameter.sh # 2.通过vim进行脚本编写,接收两个参数 #!/bin/bash echo "$0 $1 $2" # 运行脚本,向该文件传递参数 parameter.sh cls xz # 输出的内容 ./parameter.sh cls xz
- 注意啦,参数是从1开始的,我们定义的0代表的是该脚本文件的名称
$#
- $#用于统计我们传递参数的个数,通常用于配合循环使用,别急后面会讲到循环
- 基本语法
$# (功能描述:获取所有输入参数个数,常用于循环)
- 示例
# 创建脚本文件 vim parameter.sh # 编写脚本文件 #!/bin/bash echo "$0 $1 $2" echo $# # 运行脚本,向该文件传递参数 bash parameter.sh cls xz # 输出的内容 parameter.sh cls xz 2
$*和$@
这两个变量都是代表命令行中所有的参数,具体的区别,我会在循环中演示
- 基本语法
$* (功能描述:这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体) $@ (功能描述:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待)
- 示例
# 创建sh脚本 vim parameter.sh # 编写脚本文件 #!/bin/bash echo "$0 $1 $2" echo $# echo $* echo $@ # 运行脚本并传递参数 bash parameter.sh 1 2 3 # 输出的内容 parameter.sh 1 2 3 1 2 3 1 2 3
$?
- 这个参数用于返回最后一次执行命令的返回状态,它有两个返回值0和1,通常情况下我们可以将它理解为布尔值,如果我们最后一次执行命令没有问题,成功了,它返回的值就是0,反之就是1
- 基本语法
$? (功能描述:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。)
- 示例
# 执行脚本 bash helloworld.sh # 输出结果 hello world # 判断helloworld.sh脚本是否正确执行 $? # 输出结果 0
Shell的运算符
- 在前面介绍变量的时候说过,Shell默认的变量值都是字符串,那么我们要想做运行就需要通过运算符,运算符比较简单,通过案例就可以明白了
- 基本语法
“$((运算式))”或“$[运算式]”
- 我建议使用
$[运算式]
的方法,更加清爽易读 - 示例
# 定义变量S,计算(2+3)X4的值
S=$[(2+3)*4]
# 使用echo输出S变量的值
echo $S
# 输出结果
20
条件判断
-
我们在java中学习运算符时不仅仅是算术运算符,还有比较运算符等等,Shell中同样也有自己的比较运算符,但它有很多的特点,因此我单独将它抽出来
-
基本语法
(1)test 表达式 (2)[ 表达式](注意表达式前后要有空格) 注意:条件非空即为true,[ 表达式 ]返回true,[] 返回false。
-
常用的判断条件
比较两个参数
条件参数 参数说明 = 字符串比较 -eq 等于(equal) -lt 小于(less than) -le 小于等于(less equal) -gt 大于(greater than) -ge 大于等于(greater equal) -ne 不等于(Not equal) 按照文件权限进行判断
条件参数 参数说明 -r 有读的权限(read) -w -w 有写的权限(write) -x 有执行的权限(execute) 按照文件类型进行判断
条件参数 参数说明 -f 文件存在并且是一个常规的文件(file) -e 文件存在(existence) -d 文件存在并是一个目录(directory) -
比较两个参数是不是有些不适应,没关系,我们理解就好,lt就是less than小于;gt 就是greater 大于, eq就是equal等于,如果组合就是le小于等于…
-
示例:
# 23是否大于等于22
[ 23 -ge 22 ]
echo $?
# helloworld.sh是否具有写权限
[ -w helloworld.sh ]
echo $?
# /home/xiyou/sunwukong.txt目录中的文件是否存在
[ -e /home/atguigu/cls.txt ]
echo $?
Shell流程控制
- 学到这里,是不是熟悉感越来越强了?其实所有的编程语言都是大同小异的,基本上的逻辑都一样,只是不同的编程语言有自己的语法规则
- Shell的流程控制分为 if判断,case语句,for循环和while循环,它们的逻辑就和java一样,只是语法有所区别
if判断
- 基本语法一
if [ 条件判断式 ];then 程序 fi
- 基本语法二
if [ 条件判断式 ] then 程序 elif [ 条件判断式 ] then 程序 else 程序 fi
- 注意事项
- [ 条件判断式 ],中括号和条件判断式之间必须有空格
- if后要有空格
- 我建议使用基本语法二,同样是因为看起来更加清爽
- if一定要使用fi结束,因为它是通过if的反写来判断是否结束
- 示例
运行结果:# 输入一个数字,如果是1,则输出: 博主大大真帅,如果是2,则输出博主大大不是一般的帅,如果是其它,输出博主大大好自恋 # 创建脚本文件 vim ifTest.sh # 编写脚本文件 #!/bin/bash if [ $1 -eq "1" ] then echo "博主大大真帅" elif [ $1 -eq "2" ] then echo "博主大大不是一般的帅" else echo "博主大大好自恋" fi
case语句
- case的用法和java的Switch一样,就是有一些语法区别哈
- 语法格式
case $变量名 in "值1") 如果变量的值等于值1,则执行程序1 ;; "值2") 如果变量的值等于值2,则执行程序2 ;; …省略其他分支… *) 如果变量的值都不是以上的值,则执行此程序 ;; esac
- 注意事项
- case行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束
- 双分号“;;”表示命令序列结束,相当于java中的break
- 最后的“*)”表示默认模式,相当于java中的default
- 结尾同样必须使用case的反写esac表示结束
- 示例
运行结果# 输入一个数字,如果是1,则输出春天,如果是2,则输出夏天,如果是3,输出秋天,如果是4,输出冬天,如果是其他则输出:地球上没有这个季节 # 创建脚本文件 vim caseTest.sh # 编写脚本文件 #!/bin/bash case $1 in "1") echo "春天" ;; "2") echo "夏天" ;; "3") echo "秋天" ;; "4") echo "冬天" ;; *) echo "地球上没有这个季节" esac
for循环
for循环的语法格式和Java就很像了
-
基本语法一
for (( 初始值;循环控制条件;变量变化 )) do 程序 done
-
基本语法二
for 变量 in 值1 值2 值3… do 程序 done
-
示例一
# 累加1到10的和 # 创建脚本文件 vim forTest1.sh # 编写脚本文件 #!/bin/bash sum=0 for((i=0;i<=10;i++)) do sum=$[$sum+$i] done echo $sum
运行结果
-
示例二
# 打印所有输入参数
# 创建脚本文件
vim forTest2.sh
# 编写脚本
#!/bin/bash
for i in $*
do
echo "参数的值是$i"
done
运行结果
- 下面我来说明一下$*和$@在循环中的区别,还是上面的案例,只是加了一点改动
运行结果#!/bin/bash for i in "$*" do echo "参数的值是$i" done echo "---------------------分割线---------------------" for j in "$@" do echo "参数的值是$j" done
- 在上面的案例中可以知道,$*会将所有的参数当作一个整体,$@会将参数分开,一定注意了,要想让它们有区别,就需要使用双引号将它们包裹起来
"\$*"
,"\$@"
while循环
- 基本语法
while [ 条件判断式 ] do 程序 done
- 示例
运行结果# 累加1到10的和 #!/bin/bash sum=0 i=1 while [ $i -le 10 ] do sum=$[$sum+$i] i=$[$i+1] done echo $sum
Shell控制台输入
- 控制台输入的好处是可以设置定时和提示信息
- 基本语法
read(选项)(参数) 选项: -p:指定读取值时的提示符; -t:指定读取值时等待的时间(秒)。 参数 变量:指定读取值的变量名
- 示例
运行结果(咳咳,今天有些自恋了…)# 提示7秒内,读取控制台输入的名称 # 创建脚本文件 # 编写脚本文件 #!/bin/bash read -t 7 -p "Enter your name in 7 seconds " NAME echo $NAME
Shell函数
- Shell函数分类系统系统函数和自定义函数,系统函数我们常用的两个就是获取文件名称和文件的路径,自定义函数的作用就不用说了吧,我们学了这么久了…
系统函数
basename
- 基本语法
basename [string / pathname] [suffix] (功能描述:传递一个包含文件的路径,它会返回文件的名称,suffix为后缀,如果suffix被指定了,那么就会删除后缀
- 示例
运行结果# 获取/opt下的shelldomo中的test的文件名称,带后缀 basename /opt/shelldemo/test.sh # 不带后缀获取上面的文件名称 basename /opt/shelldemo/test.sh .sh
dirname
- 基本语法
dirname 文件绝对路径 (功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))
- 示例
# 还是上面的那个文件路径,我只想要test.sh文件的路径,并不想要文件名
dirname /opt/shelldemo/test.sh
运行结果
自定义函数
- 语法格式
[ function ] funname[()] { Action; [return int;] } funname
- 注意事项:
- 必须在调用函数地方之前,先声明函数,shell脚本是逐行运行。不会像其它语言一样先编译
- 函数返回值,只能通过$?系统变量获得,可以显示加:return返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)
- 我们定义完这个函数后,还需要在末尾写入该函数的名称用于调用该函数
- 示例一
# 计算两个参数的和,普通传参数
#!/bin/bash
function sumNum(){
sum=0
sum=$[ $1 + $2 ]
echo "$sum"
}
echo "计算两个数字的和"
sumNum $1 $2;
运行结果
- 示例二
# 计算两个参数的和,使用read传参
#!/bin/bash
function sumNum(){
sum=0
sum=$[ $1 + $2 ]
echo "$sum"
}
echo "计算两个数字的和"
read -p "请输入第一个数字: ": num1;
read -p "请输入第二个数字: ": num2;
sumNum $num1 $num2;
运行结果
Shell工具
- Shell工具我主要就介绍两个我们可能会用到的两个,一个cut一个是awk,它们的作用是切割数据,区别就是切割的结果有一点点不同
- 切割数据就是根据指定的格式将一个文件中的数据进行切割,切割后的数据会变成类似excel表一样的结构,我们切割后就可以使用行和列来获取所需要的数据
cut
- 语法格式
cut [选项参数] filename 说明:默认分隔符是制表符
- 参数说明
-f
列号,提取第几列-d
分隔符,指定分隔符来切割数据-c
指定具体的字符
- 测试数据(注意:第二列最后的大和帅是两个空格)
# 创建测试数据 vim cutData.txt # 编写数据 大 博 数 主 据 大 我 大 来 真 了 帅 ! ~
示例一:切割第一列
- 使用cut按照空格进行切割数据,并获取第一列的数据
运行结果cut -d " " -f 1 cutData.txt
示例二:切割第二、三列
- 使用cut按照空格进行切割数据,并获取第二列和第三列的数据
cut -d " " -f 2,3 cutData.txt
运行结果
示例三:切割出帅这个字
- 使用cut按照空格进行切割数据,然后配合管道获取所需的数据
运行结果cat cutData.txt | grep "帅" | cut -d " " -f 3
- 这种切割的逻辑是,首先切割出帅这一行,然后再切割出帅这个字
awk
- 在上面使用cut切割时,我们发现,如果空格是多个,它默认其他的空格也会占用一列,通常我们使用的时候,推荐使用的都是awk,它会将所有的空格都切割掉
- 语法格式
awk [选项参数] ‘pattern1{action1} pattern2{action2}...’ filename pattern:表示AWK在数据中查找的内容,就是匹配模式 action:在找到匹配内容时所执行的一系列命令
- 选项参数
-F
指定输入文件的分隔符-v
赋值一个自定义的变量
- 数据准备
# 我们复制当前系统中的所有用户这个文件用于测试数据 sudo cp /etc/passwd /opt/demo
- 示例一:搜索passwd文件以root关键字开头的所有行,并输出该行的第7列
运行结果awk -F: '/^root/{print $7}' passwd
- 示例二:搜索passwd文件以root关键字开头的所有行,并输出该行的第1列和第7列,中间以“,”号分割
awk -F: '/^root/{print $1","$7}' passwd
- 示例三:只显示/etc/passwd的第一列和第七列,以逗号分割,且在所有行前面添加列名user,shell在最后一行添加"bozhudada,/bin/zuishuai"
运行结果awk -F: 'BEGIN{print "user,shell"} {print $1 "," $7} END{print "博主大大,/bin/zuishuai"}' passwd
有点长。。。省略
- 注意:BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行
- 示例四:将passwd文件中的用户id增加数值1并输出
awk -v i=1 -F: '{print $3+i} ' passwd
运行结果
- 示例五:统计passwd文件名,每行的行号,每行的列数
awk -F: '{print "filename:" FILENAME ", linenumber:" NR ",columns:" NF}' passwd 运行结果: filename:passwd, linenumber:1,columns:7 filename:passwd, linenumber:2,columns:7 filename:passwd, linenumber:3,columns:7
- 示例六:切割IP
ifconfig ens33 | grep netmask | awk -F " " '{print $2}' 运行结果: 192.168.1.100
总结
Shell编程的内容就是这么多,Linux命令和Shell的知识点,勤加练习,不用担心,我们大数据的知识点中会经常遇到,好了,下一期,我将为大家正式带来大数据的知识点