您的位置:首页 > 运维架构 > Shell

Linux shell脚本编程详解及应用实例

2017-04-15 23:07 405 查看

什么是shell脚本?

1.shell脚本:是一种解释型语言,不需要提前进行编译,只需将代码转化成中间代码,边解释边运行,执行效率稍逊于编译型语言,跨平台性好.而编译型语言则需要提前进行编译转化为二进制文件,靠近底层硬件执行效率高,可移植性差.
2.shell的首行严格来说使用shebang机制:由#和!构成的字符序列,在类unix系统中程序的载入器将其后的内容,当做解释器的指令,并将载有shebang文件路径作为解释器的参数,且予以调用.

shell及其他解释型语言的一般格式?

#!/bin/bash            #shell脚本的首行格式
#!/usr/bin/python  #python脚本的格式
#!/usr/bin/perl        #perl脚本的格式
#Description:      #"#"开头的单个井字号是注释符,其后内容不当做脚本程序执行
COMMAND            #脚本的编程内容部分,命令的堆砌,单一命令的组合完成复杂任务

怎样让脚本运行起来?

1.绝对路径(全路径)   #/usr/local/test.sh             需要执行权限
2.相对路径          #在脚本文件路径下执行 ./test.sh 需要执行权限
3.bash test.sh      #bash /path/to/test.sh        不需要执行权限

shell中变量是怎样的?

变量 : 内存中的地址空间.

变量的类型:

1.强类型:类似于java,c#严格区分数值型和字符型,之间不能进行隐式转换,因此不同类型之间不可以直接计算,可进行手动显式转换
2.弱类型:变量定义时不需要指定类型也可计算,自动识别数据类型,但出于严谨尽量指明类型否则也可能出现不明错误

变量的命名规则?

1.不能使用命令或者是脚本语句结构中出现的关键字
2.不能数字开头,只能使用数字,字母,下划线
3.不包含特殊字符

变量相关概念?

本地变量(全局变量):仅对当前的shell有效,对其子shell无效的变量
环境变量 : 仅对当前shell及子shell有效(子shell继承父shell的环境变量)
局部变量(私有变量) : 当前shell脚本或代码的某个片段生效(比如函数),bash脚本在执行时,会开启一个新的shell执行完毕后回到父shell,其中定义的变量仅对子shell生效

怎样使得定义的变量生效?

1.~]# . /etc/profile        # 点后跟定义变量文件
2.~]# source /etc/profile   #直接在当前shell生效

什么是位置变量?

用来描述参数位置的变量.
$0:代表脚本本身的名字
$1:脚本的第一个参数
$2:脚本的第二个参数
...
${10}:脚本的第10个参数    #注:从第十个开始以后要加大括号
$*:脚本所有参数,以一个整体字符串存储
$@:脚本所有参数,以单个参数作为一个整体存储
$#:脚本所有参数的总个数

参数位移:

shift n:每处理一个参数参数整体向左位移n个,处于最前面的参数($0不会变),位移后被抛弃,不做处理

如何定义一个变量?

1.declare -i        #定义整型
-x      #定义环境
-r      #只读变量
2.export variable=expression #定义环境变量

变量如何赋值?

left=right 将右边的值赋值给左边的变量存储
"":双引号,弱引用其内的字符,可进行变量替换及命令替换
'':单引号,强引用其内的字符,不可进行变量替换及命令替换

变量如何引用?

1.$variable     #$后跟变量名
2.${variable}   #可加大括号

如何在脚本中调用命令的执行结果?

1.$(command)    #命令的执行结果可赋值给变量
2.`command`     #反引号同样是命令的执行结果

怎样得知系统定义了哪些变量?

1.set           #查看系统定义的所有变量
2.env/printenv  #查看定义的所有环境变量

该怎样取消一已定义好的变量?

1.unset variable#取消或者删除变量
注:variable=   #变量赋值为空不等同于变量被取消

查看bashell的版本?

echo $BASH_VERSION

几个常见的环境变量:

MAIL:存储mail文件
MAILCHECK:隔多长时间巡检一次并发送mail
SHLVL:当前处于多少层shell
RANDOM:软件模拟实现随机数变量

shell如何进行算术运算?

shell 运算符:
%  :  取余,取模
\* :  乘
** :  乘方
+  :  加法
-  :  减法
+= :  加等,在自身基础上加上第二个变量
-= :  减等,在自身基础上减去第二个变量
*= :  乘等,第一个变量乘第二个变量后再赋值给第一个变量
/= :  除等
%= :  取余等
i++:  先运算i的值后将i加1后赋值给i
++i:  先将i的值加1后运算
shell 运算表达式:
1.let num=算术表达式     let sum=8+8
2.expr 8 + 8            expr 8 \* 8     #乘法需要转义符
3.var=$( expression )   sum=$( 8 + 8 )
4.var=$(( expression))  sum=$(( 8 + 8 ))
5.echo "8+8"|bc         回显一个算术表达式,通过管道传递给bc计算机
逻辑运算:
true    1   永真
false   0   永假
&&:与运算:全真为真
||:或运算:全假为假
! :非运算:非真即假
以命令的执行成功为真
以命令的执行失败为假

如何得知命令是否成功or失败?

$?:变量存储上一条命令的执行执行退出状态返回码
状态返回码:
0 :    返回为0,则为成功执行
1-255: 为失败返回,失败返回可能退出的原因有很多所以需要不同的状态码标识
127:   命令未找到,系统的默认设置
130:   命令ctrl+c终止的状态返回码
exit   num:自定义脚本执行退出状态返回码,可以加在脚本可能退出的位置,用于判断脚本退出原因

shell脚本中如何测试某些条件是否符合我的需求?

条件测试:
1.test expression   test 1 -eq 1
2.[ expression ]    [ 1 -eq 1 ] 一般条件测试
3.[[ expression ]]  [[ 1 -eq 1 ]] 高级条件测试,支持扩展的正则表达式
1.整数测试: 用字符符号来测试数字
-eq : 等于            用法:[[ 1 -eq 1 ]]
-gt : 大于
-lt : 小于
-ge : 大于等于
-le : 小于等于
-ne : 不等于
字符串测试: 用运算符号来测试字符
=~  : 左侧的字符是否能够被右侧的pattern匹配到,左侧的范围要大于等于右侧的条件
==  : 是否相同,可使用通配符及正则表达式
!=  : 是否不同,
\>  : 根据ascii码表顺序进行比较,与sort程序不同
\<  : 注意:大于小于要进行转义,否则shell可能会解释为重定向
-z  : 其后的字符串是否为空,比较的是字符串的长度
-n  : 其后的字符是否不为空,比较的是字符串的长度
文件测试: 针对文件的测试(单目测试),比如文件的类型等
-e  : 是否存在
-d  : 是否是目录
-O  : 是否是文件属主
-G  : 是否是文件的默认组与当前用户名相同
-d  : 目录
-e  : 存在(也可以用 -a)
-f  : 普通文件
-L  : 符号连接(也可以用 -h)
-p  : 命名管道
-r  : 可读
-s  : 非空
-S  : 套接字
-w  : 可写
-N  : 从上次读取之后已经做过修改
文件的比较测试:(双目测试)
-nt : 比较file1是否比file2新
-ot : 比较file1是否比file2旧
-ef : 比较file1是否和file2是否是同一文件
联合测试:将多个条件与,或,非的逻辑关系组合在一起
-a  : 与   优先级最末
-o  : 或   优先级次之
!   : 非   优先级最优

shell脚本中的条件判断语句?

1.if-then语句 适和一个条件产生两种不同的结果,或者判断一个条件是否符合需求,并执行相应的命令
if-then语句一般格式:
if  expression  ;then   #如果条件为真则执行statement1
statement1
else                    #否则执行statement2
statement2
fi                      #判断结束符
2.while语句 当条件成立时执行循环,当条件不成立时退出,并返回一个不为0的退出状态码(当型循环)
(1).while语句的一般格式:
while  [[ test expression ]];do #当条件成立时执行循环体1,当条件不成立时退出循环
statement1
done>> output.txt               #while语句循环结束符,可在其后重定向循环输出结果至文件
(2).while语句的死循环格式:
while  true;do                  #其中true为永真
statement1
done
(3).while语句读取文件:
while  read line;do             #通过read命令将文件的内容赋值给line变量存储
statement1
done<input.file                 #通过管道将文件传递入循环处理
3.until语句:与while刚好相反,当条件满足时退出,不满足执行(直到型循环).
until [[ test expression ]];do  #条件不满足时执行循环体,满足即退出.适用于未知循环次数情况
statement1
done
4.case语句:适合检测多个条件的情况
case语句一般格式:
case variable in                                #将某变量或参数传递给下面的语句
pattern1 | pattern2) commands1;;            #参数是否符合条件1或2,执行相应命令
pattern3) command2;;                        #如果不符合条件则继续判断下一个参数
*)  command3;;                              #星号*捕获所有不匹配的值
esac                                            #case语句的结束符

怎样去控制循环,在符合某一条件时就退出循环,或者符合某种情况时跳过这个参数去处理下一个参数?

(1) break命令跳出(停止)循环
1.跳出单个循环
在for循环嵌套if-then语句满足条件则break
2.跳出内部循环
两个循环嵌套可用if-then语句跳出内部循环
3.跳出外部循环
在内部循环停止外部循环:
break n   n指定跳出的层级数
(2) continue命令:符合某种条件跳过符合条件的这个参数继续判断下一个参数,而不退出循环
通过下面的例子很容易了解她的妙用:
for i in `seq 1 20`;do
if [[ $i -gt 5 ]] && [[ $i -lt 10 ]];then
continue
fi
echo $i
done
注:如果是循环多层嵌套,可跳过多级循环:continue n  跳过n层

shell脚本编写的实例应用.怎样利用shell脚本解决实际问题?

1.先简单了解脚本编写形式.编写脚本计算/etc/passwd 的20和10个用户的用户ID 的和(仅实现要求)?
#!/bin/bash        #首行shebang机制
#                  #注释符,其后的内容不做执行代码解释
#从/etc/passwd文件中取出第10个用户uid并赋值给uid1
uid1=`cat -n /etc/passwd|egrep -o [^" "].* |egrep -e "^10\>" -e "^20\>" | cut -d: -f3 | head -1`
#从/etc/passwd文件中取出第20个用户uid并赋值给uid2
uid2=`cat -n /etc/passwd|egrep -o [^" "].* |egrep -e "^10\>" -e "^20\>" | cut -d: -f3 | tail -1`
let uidsum=$uid1+$uid2                          #将取得的两个值进行算术运算
echo "user10 and user10 uidsum is $uidsum"  #执行完成打印回显一段话
2.编写脚本计算以/etc/profile 和/etc/fstab作为参数,文件中的的空白行的总行数?
#!/bin/bash
if [ $# -eq 0 ];then
echo -e "Usage:COMMAND ARG1 ARG2  \n      Ener your two ARGs,please!"
else
file1=` grep -c "^$" $1`
file2=`grep -c  "^$" $2`
sumspace=$(( $file1 + $file2 ))
echo "/etc/profile and /etc/inittab sumspace is $sumspace"   #回显结果
fi
3.将lastb命令中的中ip取出写入/etc/hosts.deny需要后台运行(对于登录失败次数超过15次的恶意攻击封ip)?
#!/bin/bash
#
#我判定可信任的ip地址,有两种情况:
#                                1.当前正在登录的ip地址
#                                2.成功登录过的ip地址
#allowip=`w -i  | egrep -o  "\<([0-2]?[0-9]{1,2}\.){3}[0-9]{1,3}\>" | uniq`
allowip=`last -i | grep  -v  "0.0.0.0"|egrep  -o  "\<([0-2]?[0-9]{1,2}\.){3}[0-9]{1,3}\>"  | uniq`
for i in `echo $allowip|xargs -n1`;do
if [[ -z `grep $i /etc/hosts.allow` ]];then
echo "sshd: $i">>/etc/hosts.allow
fi
sleep 0.5
sync
done
count_all=`lastb -i -n 5000|egrep -v "\<tty[0-9]?\>"|egrep -o "\<([0-2]?[0-9]{1,2}\.){3}[0-9]{,3}"| uniq -c| sort -nr`
echo $count_all|xargs -n2 |  while read line;do
loginfailcount=`echo $line | awk '{print $1}'`
ipaddress=`echo $line | awk '{print $2}'`
if [[ $allowip =~ $ipaddress ]] ;then
continue
elif [ $loginfailcount -gt 3 ] ;then
egrep -q $ipaddress /etc/hosts.allow
if   [ $? -eq 0 ] ;then
continue
else
egrep -q $ipaddress /etc/hosts.deny
if  [ $? -eq 0 ] ;then
continue
else
echo "sshd: $ipaddress">>/etc/hosts.deny
fi
fi
fi
sleep 0.5
sync
done
4.编写脚本实现,执行一个脚本带有不确定个数的参数,将这些参数以前面加序号的方式一一列举出?
#!/bin/bash
count=1
while [[ -n $1 ]];do
echo  "$count = $1"
count=$(( $count + 1 ))
shift
done
5.给脚本传递一个或多个用户名作为参数,显示该用户是管理员还是系统用户或是普通用户(centos 7),并显示其默认shell?
#!/bin/bash
#
#这里我用的是id命令作为判断依据,也可用/etc/passwd这个文件中的字段来判断
#由于id命令的某些特性,需要考虑输入的参数为数字的情况,id会将数字当做uid来判断
for i in $@;do                #将每个参数传递给变量i
uid=`id -u $i 2>/dev/null`    #注意id -u <已存在用户的uid号,则返回该uid用户uid>,用户名转化为uid
user=`id -un $i 2>/dev/null`  #将该uid转化为用户名
getshell()                    #这里我用到了函数来获得用户的默认shell
{
#需要考虑两种情况
#1.用户名和uid相同
#2.用户名和uid不同
if [[ $uid -ne $user ]];then     #如果输入参数是数字,且和uid相同则回显用户名及默认shell
echo  "user :$user shell is `cat /etc/passwd | egrep "^($user:)" | cut -d: -f 7 `"
else                #否则输入的是数字但和uid不同,则证明该数字不是uid,而是以数字命名的用户
echo  "user :$i shell is `cat /etc/passwd | egrep "^(${i}:)" | cut -d: -f 7 `"
fi
}
if ! `id $i &>/dev/null` ;then       #判断这个用户是否存在,不存在则回显下面的提示
echo -e "user :\033[31m$i\033[0m is not exist!"
elif [ $uid -eq 0 ] ;then            #如果uid=0则回显该用户为管理员
echo -e "user :\033[31m$i\033[0m is \033[32m$user\033[0m Administrator"
getshell                        #这里直接调用moreshell的方法
elif [ $uid -gt 0 -a $uid -lt 1000 ] ;then  #判断uid是否大于0小于1000是则为系统用户
echo -e "user :\033[31m$i\033[0m is \033[32m$user\033[0m OS user"
getshell                        #显示默认shell
elif [ $uid -ge 1000 ] ;then         #大于1000则是普通用户
echo -e "user :\033[31m$i\033[0m is \033[32m$user\033[0m Common user"
getshell
fi
done

就先到这里了,有哪个地方错误请多多指教,希望linuxer在学习中遇到的一些坑多多拿来出交流,让探索这一领域的伙伴少走一些弯路!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息