首页 > 代码库 > Linux shell脚本编程详解及应用实例
Linux shell脚本编程详解及应用实例
什么是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 不需要执行权限 bash -n 语法结构检查 bash -x 调试模式运行,显示每一步的执行过程
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.ase语句:适合检测多个条件的情况
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 #for循环传递1到20的数字 if [[ $i -gt 5 ]] && [[ $i -lt 10 ]];then #如果传递的数字大于5小于10则执行下一句 continue #配合if语句,符合if的参数跳过循环体的处理 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` #取出参数1文件的所有空白行的总数赋值file1 file2=`grep -c "^$" $2` #取出参数2文件的所有空白行的总数赋值file2 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.正在登录2.登陆过 #allowip=`w -i | egrep -o "\<([0-2]?[0-9]{1,2}\.){3}[0-9]{1,3}\>" | uniq` #捕获已经成功登录过的用户IP地址 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 #循环比较成功过的用户ip地址是否在白名单中,不在则加入,注:添加黑名单时先确保自己在白名单之中(重要) if [[ -z `grep $i /etc/hosts.allow` ]];then#判断该ip地址白名单中是否存在 echo "sshd: $i">>/etc/hosts.allow #不存在则写入 fi done #捕获所有的失败登录ip地址及次数 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` #将捕获的失败ip及次数,传递给循环判断 echo $count_all|xargs -n2 | while read line;do #失败次数及ip分离 loginfailcount=`echo $line | awk ‘{print $1}‘` ipaddress=`echo $line | awk ‘{print $2}‘` #判断失败的ip是否在已经登录过的ip列表中,存在则判断下一个失败ip,排除允许登录的用户产生的误操作 if [[ $allowip =~ $ipaddress ]] ;then continue elif [ $loginfailcount -gt 15 ] ;then #判断失败次数的是否超过阀值 egrep -q $ipaddress /etc/hosts.allow #判断超过阀值的ip是否在白名单中添加过,是则判断下一个失败ip地址 if [ $? -eq 0 ] ;then #如果在白名单中 continue #跳过这个ip else echo "sshd: $ipaddress">/etc/hosts.deny #将超过阀值的失败登录ip加入黑名单 fi fi done
4.编写脚本实现,执行一个脚本带有不确定个数的参数,将这些参数以前面加序号的方式一一列举出?
#!/bin/bash count=1 #定义一个初始变量 while [[ -n $1 ]];do #判断第一个参数是否不为空,是则执行循环体 echo "$count = $1" #回显序号=参数的形式 count=$(( $count + 1 )) #每执行一次循环,序号+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在学习中遇到的一些坑多多拿来出交流,让探索这一领域的伙伴少走一些弯路!
我的51cto博客:http://cityx.blog.51cto.com/
本文出自 “老城小叙” 博客,请务必保留此出处http://cityx.blog.51cto.com/9857477/1916354
Linux shell脚本编程详解及应用实例