NO.3 Shell脚本
编译型语言:
程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++
解释型语言:
程序不需要编译,程序在运行时由解释器翻译成机器语言,每执 行一次都要翻译一次。因此效率比较低。比如Python/JavaScript/ Perl /ruby/Shell等都是解释型语言。
总结:
编译型语言比解释型语言速度较快,但是不如解释性语言跨平台性好。如果做底层开发或者大型应用程序或者操作系开发一般都用编译型语言;如果是一些服务器脚本及一些辅助的接口,对速度要求不高、对各个平台的兼容性有要求的话则一般都用解释型语言。
回顾一下,Linux操作系统由什么组成的?
内核、shell、应用程序、文件系统
shell:命令解释器 人机交互的一个桥梁
终端——》命令
|
bash shell 解释器(shell)
|
kernel
|
硬件
什么是shell脚本?
简单来说就是将需要执行的命令保存到文本中,按照顺序执行它。它是解释型的,意味着它不需要编译。
若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= shell脚本
什么时候用到脚本?
重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。
如何学习脚本?
1、尽可能记忆更多的命令
2、掌握脚本的标准的格式(指定魔法字节、使用标准的执行方式运行脚本)
3、必须熟悉掌握脚本的基本语法(以下列表仅仅的基本要求,还有很多更深更难的语法需要自己扩充学习)
变量定义
条件判断
分支语句
函数
数组
循环语句
正则表达式
sed,awk命令的使用
学习脚本的秘诀:
多看,多写,多思考
看懂脚本——>模仿——>自己写
脚本的编写方法:
非标准:
source xxx.sh
. xxx.sh
bash xxx.sh
sh xx.sh
标准:
格式相对完整的脚本,建议的格式。
#!/bin/bash 脚本第一行 , #!魔法字符,指定脚本代码执行的程序。即它告诉系统这个脚本需要什么解释器来执行,也就是使用哪一种Shell
。。。。
脚本执行方法:
标准脚本执行方法(建议):(魔法字节指定的程序会生效)
非标准的执行方法(不建议):(魔法字节指定的程序不会运作)
总结:
./xxx.sh --要求有执行权限,并且一定要声明shell类型(#!/bin/bash)
. xxx.sh或者source xxx.sh或者bash xxx.sh或者sh xxx.sh --不需要有执行权限,也可以不声明shell类型
说明: bash -x xxx.sh 或者sh -x xxx.sh --可以显示执行过程,帮助排错
补充:
bash中的引号:
双引号 "" 会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
单引号 '' 会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
反撇号 `` 和$() 反撇号和括号里的命令会优先执行,如果存在嵌套,反撇号不能用。
; 可对一行命令进行分割,在执行过程中不考虑上一个命令执行是否是正确的
&& 逻辑与。可对一行命令进行分割,在执行过程中考虑上一个命令执行是否是正确的
|| 逻辑或
变量的分类:
本地变量:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。
unset a 取消变量
环境变量:当前进程有效,并且能够被子进程调用。
HI=hello 设置一个本地变量
查看当前用户的环境变量 env
env |grep HI
查询当前用户的所有变量<临时变量与环境变量>
set |grep HI
HI=hello
export HI 将当前变量变成环境变量
env |grep -i HI
HISTSIZE=1000
HI=hello
全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用。
/etc/profile
HI=Hello
export HI
/etc/profile
/etc/bashrc
~/.bash_profile
~/.bash_bashrc
user——>login——>/etc/profile——>~/.bash_profile——>/etc/bashrc——>~/.bashrc——>~/.bash_logout
局部变量:
~/.bash_profile
...
注意:需要重新登录才生效
$HOME/.bashrc 当前用户固定变量 eg:别名
$HOME/.bash_profile 当前用户的环境变量
/etc/bashrc 使用bash shell用户全局变量
/etc/profile 使用所有shell的全局变量
系统变量(内置bash中变量) : shell本身已经固定好了它的名字和作用。$@,$*,$# ,$$ ,$? ,$0
$#:脚本后面接的参数的个数
$*:脚本后面所有参数
$@: 脚本后面所有参数
$?:上一条命令执行后返回的状态,当返回状态值为0时表示执行正常,非0值表示执行异常或出错
若退出状态值为0 表示命令运行成功
若退出状态值为127 command not found
若退出状态值为126 找到了该命令但无法执行 ---权限不够
若退出状态值为1&2 没有那个文件或目录
$$ 当前所在进程的进程号
$! 后台运行的最后一个进程号 (当前终端)
!$ 调用最后一条命令历史中的参数
!! 调用最后一条命令历史
$0 当前执行的进程/程序名
$1~$9 位置参数变量
${10}~${n} 扩展位置参数变量 第10个位置变量必须用{}大括号括起来
vim 3.sh
#!/bin/bash
#xxxxx
echo "\$0 = $0"
echo "\$# = $#"
echo "\$ = $"
echo "\$@ = $@"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$10 = ${10}"
什么时候用到变量?
如果某个内容需要多次使用,并且在代码中重复出现,那么可以用变量代表该内容。这样在修改内容的时候,仅仅需要修改变量的值
在代码运作的过程中,可能会把某些命令的执行结果保存起来,后续代码需要使用这些结果,就可以直接使用这个变量
变量定义的规则:
1、默认情况下,shell里定义的变量是不分类型的,可以给变量赋与任何类型的值;等号两边不能有空格,对于有空格的字符串做为赋值时,要用引号引起来
955 A=hello
956 echo $A
957 A= hello
958 A =hello
959 A = hello
960 A=hello world
961 A='hello world'
962 echo $A
963 A="hello world"
964 echo $A
2、变量的获取方式: $变量名 ${变量名}
966 echo $A
967 echo ${A}
968 a=123456
969 echo $a
970 echo ${a}
971 echo ${a:2:3}
972 a=123456789
973 echo ${a:3:5} 3代表从第3位开始截取;5代表截取5个数
975 echo ${a:3} 代表截取从第3位开始以后所有的
3、取消变量的命令 unset 变量名
4、区分大小写,同名称但大小写不同的变量名是不同的变量
5、变量名可以是字母或数字或下划线,但是不能以数字开头或者特殊字符
[root@node1 shell01]# a=1abc
[root@node1 shell01]# 1a=hello
bash: 1a=hello: command not found
[root@node1 shell01]# ?a=hello
bash: ?a=hello: command not found
[root@node1 shell01]# /a=hello
bash: /a=hello: No such file or directory
[root@node1 shell01]# _a=777
[root@node1 shell01]# echo $_a
777
6、命令的执行结果可以保存到变量
注意:
$( ) 等同于 执行符号 `,但是如果要嵌套使用,使用
符号就不行,要用$();但如果不是嵌套的使用
是可以的,如a="
which mount`which yum
"
7、数组
数组定义:用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为:
array=(var1 var2 var3 var4)
或者
array[0]=v1
array[1]=v2
array[3]=v3
读取数组:
${array [i]} i表示元素
使用@ 或 * 可以获取数组中的所有元素:
hello,stu1
hello,stu2
hello,stu3
#!/bin/bash
array=(stu1 stu2 stu3)
for var in ${array[*]}
do
echo hello,$var
done
[root@node1 shell01]# var[0]=user1
[root@node1 shell01]# var[1]=user2
[root@node1 shell01]# var[2]=user3
[root@node1 shell01]# for i in ${var[@]};do echo hello,$i;done
hello,user1
hello,user2
hello,user3
获取第n个元素
echo "${user[N-1]}"
获取数组指定元素
echo ${user[@]:1:3} 从数组下标为1开始,读取3个元素
示例:
定义一组用户u01~u05,分别在屏幕上显示hello,username
8、有类型变量
declare
-i 将变量看成整数
-r 使变量只读 readonly
-x 标记变量通过环境导出 export
-a 将变量看成数组
[root@node1 shell01]# a=10
[root@node1 shell01]# b=2
[root@node1 shell01]# c=$a+$b
[root@node1 shell01]# echo $c
10+2
[root@node1 shell01]# declare -i a=5
[root@node1 shell01]# declare -i b=2
[root@node1 shell01]# declare -i c=$a+$b
[root@node1 shell01]# echo $c
7
[root@node1 shell01]#
[root@node1 shell01]# declare -i a=10 b=2
[root@node1 shell01]# declare -i c=$a*$b
[root@node1 shell01]# echo $c
20
1040 echo $a
1041 declare -r a=hello
1042 echo $a
1043 declare -r A=hello
1044 A=888
1045 echo $A
1046 declare -i A=123
1047 AB=hello
1048 export AB
1049 env|grep AB
1050 declare -x ABC=hahaha
1051 env|grep ABC
9、交互式定义变量的值 read 主要用于让用户去定义变量值
1054 read -p "Input your name:" name
1055 echo $name
1056 read -s -p "Input your password:" pass
1057 echo $pass
1058 read -n 5 -p "Input your name:" name
1059 echo $name
1060 read -t 3 -p "Input your name:" name
1061 echo $name
1062 read -t 3 -p "Input your name:" name
1063 echo $name
10、其他变量:
一个“#”代表从左往右去掉一个指定字符
两个“#”代表从左往右最大去掉指定字符
一个“%”代表从右往左去掉一个指定字符
两个“%”代表从右往左最大去掉指定字符
取出一个目录下的目录和文件
/root/Desktop/shell/mem.txt
/root/Desktop/shell
mem.txt
/root/Desktop/shell
/root/Desktop/shell/mem
/root/Desktop/shell/mem
mem.txt
Desktop/shell/mem.txt
1071 aaa=/shell/shell01/dir1/file.txt
1072 echo $aaa
1073 dirname $aaa
1074 basename $aaa
1075 echo ${aaa#}
1077 echo ${aaa%.*}
1078 echo ${aaa%%.*}
1079 echo ${aaa%/*/}
1080 echo ${aaa%/*}
1081 echo ${aaa%%/*}
===变量内容的替换===
[root@vm1 Desktop]# a=www.taobao.com
[root@vm1 Desktop]# echo ${a/taobao/baidu}
www.baidu.com
[root@vm1 Desktop]# a=www.taobao.com
[root@vm1 Desktop]# echo ${a/a/A}
www.tAobao.com
[root@vm1 Desktop]# echo ${a//a/A} 贪婪替换
www.tAobAo.com
===变量的替代===
aaaaa
111
${变量名:-新的变量值}
变量没有被赋值(包括空值):都会使用“新的变量值“ 替代
变量有被赋值: 不会被替代
简单的四则运算
算术运算:
默认情况下,shell就只能支持简单的整数运算
-
-
- / %(取模,求余数)
-
$(()) | $[] | expr | let
Bash shell 的算术运算有四种方式:
1、使用 $(( ))
2、使用$[ ]
3、使用 expr 外部程式
4、使用let 命令
加法:
n=10
let n=n+1
或者let n+=1
echo $n
乘法:
let m=n*10
echo $m
除法:
let r=m/10
echo $r
求余数:
let r=m%7
echo $r
乘冪:
let r=m**2
echo $r
注意:
n=1
let n+=1 等价于let n=n+1
思考:能不能用shell做小数运算?
2.5
i++ 和 ++i (了解)
对变量的值的影响:
[root@vm1 Desktop]# i=1
[root@vm1 Desktop]# let i++
[root@vm1 Desktop]# echo $i
2
[root@vm1 Desktop]# j=1
[root@vm1 Desktop]# let ++j
[root@vm1 Desktop]# echo $j
2
对表达式的值的影响:
[root@vm1 Desktop]# unset i j
[root@vm1 Desktop]# i=1
[root@vm1 Desktop]# j=1
[root@vm1 Desktop]# let x=i++ 先赋值,再运算
[root@vm1 Desktop]# echo $i
2
[root@vm1 Desktop]# echo $x
1
[root@vm1 Desktop]# let y=++j 先运算,再赋值
[root@vm1 Desktop]# echo $j
2
[root@vm1 Desktop]# echo $y
2
条件判断
语法结构:
if [ condition ];then
command
command
fi
if [ condition ];then
command1
else
command2
fi
if [ condition1 ];then
command1 结束
elif [ condition2 ];then
command2 结束
else
command3
fi
如果条件1满足,执行命令1后结束;如果条件1不满足,再看条件2,如果条件2满足执行命令2;如果条件1和条件2都不满足执行命令3.
if [ condition1 ];then
command1
if [ condition2 ];then
command2
fi
else
if [ condition3 ];then
command3
elif [ condition4 ];then
command4
else
command5
fi
fi
如果条件1满足,执行命令1;如果条件2也满足执行命令2,如果不满足就只执行命令1结束;
如果条件1不满足,不看条件2;直接看条件3,如果条件3满足执行命令3;如果不满足则看条件4,如果条件4满足执行命令4;否则执行命令5
判断语法:
1、test 条件表达式
2、[ 条件表达式 ]
3、[[ 条件表达式 ]] 匹配正则 =~
#!/bin/bash
aaa=$1
[[ $aaa = 'hello' ]] && echo world
[root@node1 shell01]# bash -x if3.sh
-
aaa=
- '[' = hello ']'
if3.sh: line 3: [: =: unary operator expected
[root@node1 shell01]# bash -x if3.sh hello
-
aaa=hello
-
'[' hello = hello ']'
- echo world
world
[root@node1 shell01]# vim if3.sh
[root@node1 shell01]# bash -x if3.sh
-
aaa=
- [[ '' = \h\e\l\l\o ]]
man test去查看,很多的参数都用来进行条件判断
与文件存在与否的判断
-e 是否存在 不管是文件还是目录,只要存在,条件就成立
-f 是否为普通文件
-d 是否为目录
-S socket
-p pipe
-c character
-b block
-L 软link
文件权限相关的判断
-r 当前用户对其是否可读
-w 当前用户对其是否可写
-x 当前用户对其是否可执行
-u 是否有suid
-g 是否sgid
-k 是否有t位
-s 是否为空白文件 说明:-s表示非空,! -s 表示空文件
[ -s file1 ] file1文件内容不为空,条件成立
[ ! -s file1 ] file1文件内容为空,条件成立
两个文件的比较判断
file1 -nt file2 比较file1是否比file2新
file1 -ot file2 比较file1是否比file2旧
file1 -ef file2 比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode
整数之间的判断
-eq 相等
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
字符串之间的判断
-z 是否为空字符串 字符串长度为0,就成立
-n 是否为非空字符串 只要字符串非空,就是成立
string1 = string2 是否相等
string1 != string2 不等
! 结果取反
多重条件判断
逻辑判断符号:
-a 和 && 逻辑与 [ 条件1 -a 条件2 ] 只有两个条件都成立,整个大条件才成立
[ 1 -eq 1 ] && [ 2 -ne 3 ]
[ 1 -eq 1 -a 2 -ne 3 ]
-o 和 || 逻辑或 [ 条件1 -o 条件2 ] 只要其中一个条件成立,整个大条件就成立
[ 1 -eq 1 -o 2 -ne 2 ]
[ 1 -eq 1 ] || [ 2 -ne 2 ]
! 逻辑非 优先级最低
-a 优先级 比 -o 优先级要高
#!/bin/bash
aaa=id -u
[ $aaa -eq 0 ] && echo "当前是超级用户" || echo "you不是超级用户"
$ [ $UID -eq 0 ] && echo "当前是超级用户" || echo "you不是超级用户"
demo1:
判断一个IP是否通ping通
方法1:
#!/bin/bash
read -p "请输入你要ping额IP地址:" ip
ping -c1 $ip &>/dev/null 或者 >/dev/null 2>&1
if [ $? -eq 0 ];then
echo "当前主机与所输入的IP网络ok"
else
echo "当前主机与所输入的IP网络不ok"
fi
方法2:
ping -c1 $1 &>/dev/null
test $? -ne 0 && echo "当前主机与所输入的IP网络不ok" || echo "当前主机与所输入的IP网络ok"
demo2:
判断一个进程是否存在
1、ps -ef|grep vsftpd |grep -v grep
2、pidof 程序名称
3、pgrep -l 程序名称
#!/bin/bash
ps -ef|grep vsftpd|grep -v grep &>/dev/null
if [ $? -eq 0 ];then
echo "该进程存在"
else
echo "该进程不存在"
fi
read -p "请输入你需要判断的进程名字:" name
pidof $name &>/dev/null
[ $? -eq 0 ] && echo "该进程存在" || echo "该进程不存在"
pidof $1 &>/dev/null
test $? -ne 0 && echo "该进程不存在" || echo "该进程存在"
需求:使用该脚本判断所输入的进程是否存在(多个进程名,至少2个)
#!/bin/bash
[ $# -eq 0 ] && echo "该脚本$0的用法是:$0 pidname" || pidname=($*)
for i in ${pidname[@]}
do
pidof $i &>/dev/null
test $? -ne 0 && echo "该进程不存在" || echo "该进程存在"
done
pgrep命令:以名称为依据从运行进程队列中查找进程,并显示查找到的进程id
选项
-o:仅显示找到的最小(起始)进程号;
-n:仅显示找到的最大(结束)进程号;
-l:显示进程名称;
-P:指定父进程号;pgrep -p 4764 查看父进程下的子进程id
-g:指定进程组;
-t:指定开启进程的终端;
-u:指定进程的有效用户ID。
demo3:
判断一个服务是否正常(以httpd为例):
1、可以判断进程是否存在,用/etc/init.d/httpd status判断状态等方法
2、最好的方法是直接去访问一下,通过访问成功和失败的返回值来判断
#!/bin/bash
wget -P /tmp http://localhost &>/dev/null
[ $? -eq 0 ] && echo "该服务正常" || echo "该服务不正常"
课堂练习:
1、写一个脚本判断一个用户是否存在
2、完善上一个脚本的bug,要求当没有给脚本传参数或者参数个数不等于1个时,提示脚本的用法:usage:xxx.sh ip
#!/bin/bash
[ $# -ne 1 ] && echo "usage:basename $0
username" && exit
id $1 &>/dev/null
test $? -eq 0 && echo "该用户$1存在" || echo "该用户$1不存在"
#!/bin/bash
[ $# -ne 1 ] && echo "usage:basename $0
ipaddr" && exit
wget http://$1 &>/dev/null
[ $? -eq 0 ] && echo 'httpd服务正常' || echo "httpd服务不正常"
3、判断vsftpd软件包是否安装,如果没有则自动安装
4、判断vsftpd服务是否启动,如果启动,输出以下信息:
vsftpd服务器已启动...
vsftpd监听的地址是:
vsftpd监听的端口是:
#!/bin/bash
ftp=vsftpd
rpm -q $ftp &>/dev/null
[ $? -ne 0 ] && yum -y install $ftp &>/dev/null
pgrep -l $ftp &>/dev/null
[ $? -ne 0 ] && service $ftp start &>/dev/null && echo "服务已经启动..."
ip=netstat -nltp|grep $ftp|cut -d: -f1|cut -c21-
port=netstat -nltp|grep $ftp|cut -d: -f2|cut -d' ' -f1
echo "vsftpd监听的地址是:$ip"
echo "vsftpd监听的端口是:$port"
作业:
1、 判断/tmp/run文件是否存在,如果不存在就建立,如果存在就删除目录里所有文件
#!/bin/bash
dir=/tmp/run
[ -d $dir ] && rm -rf $dir/* || mkdir $dir
dir=/tmp/run
[ -f $dir ] && mv $dir $dir.bak
[ -d $dir ] && rm -rf $dir/* || mkdir $dir
2、 输入一个路径,判断路径是否存在,而且输出是文件还是目录,如果是字符连接,还得输出是有效的连接还是无效的连接
#!/bin/bash
read -p "请输入绝对路径:" path
if [ -e "$path" -a -L "$path" ];then
echo "该文件是一个有效连接文件"
elif [ ! -e "$path" -a -L "$path" ];then
echo "该文件是一个无效连接文件"
elif [ -d $path ];then
echo "该文件是一个目录"
elif [ -f $path ];then
echo "该文件是一个普通文件"
elif [ -e $path ];then
echo "其他文件"
else
echo "路径不存在"
fi
3、交互模式要求输入一个ip,然后脚本判断这个IP 对应的主机是否 能ping 通,输出结果类似于:
Server 10.1.1.20 is Down! 最后要求把结果邮件到本地管理员root@localhost和mail01@localhost
4、写一个脚本/home/program,要求当给脚本输入参数hello时,脚本返回world,给脚本输入参数world时,脚本返回hello。而脚本没有参数或者参数错误时,屏幕上输出“usage:/home/program hello or world”
#!/bin/bash
if [ "$1" = "hello" ];then
echo world
elif [ "$1" = "world" ];then
echo hello
elif [ $# -ne 1 ];then
echo "usage:$0 hello or world"
else
echo "usage:$0 hello or world"
fi
5、写一个脚本自动搭建nfs服务
#!/bin/bash
#1.安装软件
#2.确认软件是否安装
#3.配置
#(1).新建共享目录,授本地权限
#(2).发布共享目录/etc/exports
#4.启动服务
#5.设置下次开机自动启动
#配置网络,测试网络
ping -c 1 192.168.0.254 &> /dev/null && echo "##############网络OK###############"
#配置network yum
rm -fr /etc/yum.repos.d/*
cat > /etc/yum.repos.d/dvd.repo << EOT
[base]
baseurl=ftp://192.168.0.254/rhel6_dvd
gpgcheck=0
EOT
#1.安装软件
yum -y install nfs* rpcbind &> /dev/null && echo "##############软件安装OK###############"
#2.xx
#3.配置
#(1).新建共享目录,授本地权限
#read -p "请输入你的共享目录" dir
mkdir -p $1
chmod 777 $1 && echo "##############本地授权OK###############"
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341