[TOC]
0x09 shell编程函数
描述:Bash(Bourne Again shell)也跟其他编程语言一样也支持函数
,一般在编写大型脚本中需要用到,函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高,像其他编程语言一样,Shell 也支持函数。但是bash作为一种解释性语言,bash 在编程能力方面提供的支持并不像其他编译性的语言(例如 C 语言)那样完善,执行效率也会低很多。
- Shell 函数
必须先定义后使用
- Shell 函数与其他高级语言的函数有相似之处,也有
返回值、删除函数、在终端调用函数(传参和递归)
等等.
函数的定义格式如下:
代码语言:javascript复制#常用格式
funname () {
list of commands
[ return value ]
}
#在函数名前加上关键字 function:
function function_name () {
list of commands
[ return value ] # 函数返回值,可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值。
}
#调用函数只需要给出函数名,不需要加括号
function_name
Shell 函数返回值只能是整数
,一般用来表示函数执行成功与否[ 用 $? 接收返回得数值 ],0表示成功,其他值表示失败
- 如果 return 其他数据比如一个字符串,往往会得到错误提示:“numeric argument required”。
- 如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值
实际案例:
代码语言:javascript复制#!/bin/bash
#功能:使用函数Hello直接调用函数与带有return语句的函数
#定义函数
Hello () {
echo "Url is http://see.xidian.edu.cn/cpp/shell/"
}
funWithReturn(){
echo "The function is to get the sum of two numbers..."
echo -n "Input first number: "
read aNum
echo -n "Input another number: "
read anotherNum
echo "The two numbers are $aNum and $anotherNum !"
return $(($aNum $anotherNum)) #返回值为整数
}
#调用函数
Hello
funWithReturn
ret=$? # Capture(捕获) value returnd by last command
echo "The sum of two numbers is $ret !"
#执行结果#
Url is http://see.xidian.edu.cn/cpp/shell/
WeiyiGeek.shell函数返回值
全局与局部变量
描述:在shell函数中也存在局部和全局变量的说法,以下面的案例来看使用了local关键字;
代码语言:javascript复制#!/bin/bash
aa="this is aa" #全局变量 $aa 和 $bb
bb="this is bb"
function name() { #定义函数name
local cc="this is cc" #定义局部变量$cc
local dd="this is dd" #定义局部变量$dd
echo $aa, $bb #访问参数1和参数2
echo $cc #打印局部变量
return 10 #shell函数返回值是整形,并且在0~257之间。
}
echo $dd #这里将会打印不生效,因为dd是局部变量。
name #函数调用
echo "函数返回值为:$?"
#执行结果#
[返回空值]
this is aa, this is bb
this is cc
函数返回值为:0
函数参数
在Shell中,调用函数时可以向其传递参数,在函数体内部,通过 n 的形式来获取参数的值,例如,1表示第一个参数,
函数示例:
代码语言:javascript复制#!/bin/bash
funWithParam(){
echo "The value of the first parameter is $1 !"
echo "The value of the second parameter is $2 !"
echo "The value of the tenth parameter is $10 !"
echo "The value of the tenth parameter is ${10} !" #注意获取第10个参数时候的必须采用这样的形式;
echo "The value of the eleventh parameter is ${11} !"
echo "The amount of the parameters is $# !" # 参数个数 (前面所说的特殊变量)
echo "The string of the parameters is $* !" # 传递给函数的所有参数
echo "The string of the Single parameters is $@ !" # 传递给函数的单个参数
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73 #注意这里传递给函数的参数
WeiyiGeek.函数参数传递
递归函数
bash也支持递归函数(能够调用自身的函数),那什么是递归函数? 答:说白了就是函数本身自我调用;
简单实例:
代码语言:javascript复制#实例1.脚本不断自我调用打印hello function,结束请按Ctrl C结束。
function name() {
echo $1
sleep 1
name "hello fucntion" #自己调用自己own
}
name
# 执行结果 ##
$ ./test.sh
hello fucntion
hello fucntion
hello fucntion
#实例2.再来看一个函数嵌套的例子
number_one () {
echo "Url_1 is http://see.edu.cn/cpp/shell/"
number_two
}
number_two () {
echo "Url_2 is http://see.edu.cn/cpp/"
}
number_one #调用需要放在number_two后
# 执行结果 ##
Url_1 is http://see.edu.cn/cpp/shell/
Url_2 is http://see.edu.cn/cpp/
补充知识点:
递归经典:可能很多人都曾经听说过 fork 炸弹
,它实际上只是一个非常简单的递归程序,程序所做的事情只有一样:这个递归函数能够调用自身,不算的生成新的进程,这会导致这个简单的程序迅速耗尽系统里面的所有资源,造成拒绝服务攻击!(denial of service attack)
它定义了一个叫”.”的函数,调用了自己两次,一次是在前台,一次是在后台;
代码语言:javascript复制.()
{
.|.&
}
;
.
#或者
:( ){:|:&};: # :的函数
WeiyiGeek.递归函数炸弹
shell脚本函数常用脚本编写方法 (重点)
代码语言:javascript复制#!/bin/bash
# 函数实现输入格式效验与web应用监测
RETVAL=0
#包含脚本 让后面的函数聚可以使用里面的 action 函数与变量 (值得学习)
[ -f /etc/init.d/functions ] && . /etc/init.d/functions #由于ubuntu中没有所以下面直接简单的写了一个
# Run some action. Log its output.
# action() {
# local STRING rc
# STRING=$1;$2
# rc=$?
# if [ $rc -eq 0 ];then
# echo -n "$STRING [成功]"
# else
# echo -n "$STRING [失败]"
# fi
# echo .
# }
function usage(){
echo -e "Usage: $0 USERNAME URL nUsername:只能是子母大小写数字以及下划线nURL:支持https与http协议"
exit 1
}
function usernameFormat(){
[ 0 -lt $(echo "$1" | grep -E '[^a-zA-Z0-9_]'|wc -l) ] && return 1 || return 0
}
function urlFormat(){
format=$(echo "$1"|grep -oE "(^http|^https)://w. .w " | grep -oE "(^http|^https)") # -o 选项值得学习
if [ "$format" == "" ];then
echo "url Format Error!"
usage
elif [ "$format" == "https" ];then
return 1
elif [ "$format" == "http" ];then
return 2
else
return 3
fi
}
function checkweb(){
if [ $2 -eq 1 ];then
wget -T 10 --spider --no-check-certificate -t 2 $1 &>/dev/null #https
elif [ $2 -eq 2 ];then
wget -T 10 --spider -t 2 $1 &>/dev/null #http #值得学习的地方探测web服务
else
echo "checkweb Error!"
exit 1
fi
RETVAL=$?
if [ $RETVAL -eq 0 ];then
action "$1 url" /bin/true #值得学习的地方
else
action "$1 url" /bin/false
fi
return $RETVAL
}
function main(){
usernameFormat $1
check=$?
if [ 1 -eq "$check" ];then
echo "Username format Error!"
usage
fi
urlFormat "$2"
check=$?
checkweb "$2" "$check"
}
#关键点(这里是$*)
main $*
执行结果:
代码语言:javascript复制./webcheck.sh weiyegeek "https://www.baidu.com"
https://www.baidu.com url [成功].
./webcheck.sh weiyegeek "http://www.baidu.com"
http://www.baidu.com url [成功].
./webcheck.sh weiyegeek "http://www.baiducom.com"
http://www.baiducom.com url [失败].
#Centos7执行
./demo.sh weiyigeek http://www.baidu1.com
http://www.baidu1.com url [FAILED]
函数总结:
- shell的位置参数(1,1,{n},#,#,*,@,@,?)都是函数的参数;
- shell返回值是 exit 输出返回值(并且退出当前shell),函数里用 return 输出返回值(退出当前函数),都采用$?来获取执行的结果;
- shell中函数传参与脚本传参是一致的,但是得注意 $0 代表任然是父脚本的名称;
删除函数
描述:像删除变量一样,删除函数也可以使用 unset 命令,不过要加上 .f 选项,如下所示:
代码语言:javascript复制unset -f function_name #放在调用的前面!!!
如果你希望直接从终端调用函数可以将函数定义在主目录下的 .profile 文件,这样每次登录后在命令提示符后面输入函数名字就可以立即调用。
WeiyiGeek.函数嵌套删除
0x09 shell脚本包含
描述:Shell文件包含像其他语言一样,Shell 也可以包含外部脚本,将外部脚本的内容合并到当前脚本,也能在函数中进行调用外部变量
脚本包含:
代码语言:javascript复制#两种方式效果相同,一般常使用点号(.),但是注意号(.)与文件名中间有一空格
. filename
source filename
基础示例:
代码语言:javascript复制#例如,创建两个脚本,一个是被调用脚本 subscript.sh,内容如下:
echo "sub Script"
url="weiyigeek.github.io"
一个是主文件 main.sh,内容如下:
#!/bin/bash
. ./subscript.sh #注意:被包含脚本不需要有执行权限
echo "我的个人网站地址: ${url}" # 调用subscript里面的变量.
#执行脚本:
$chomd x main.sh
./main.sh
#执行结果
http://see.edu.cn/2738.html
注意事项:
- 如果subscript.sh没有权限main.sh将不会被执行;
0x10 补充知识
shell 脚本调试
描述:脚本调试功能是每一种编程语言具备得特性之一,出现一些始料未及得情况;使用调试功能可以弄清除是声明原因发生了错误或者异常; shell脚本自身已经包含调试选项,能打印出脚本接收得参数和输入;
方法1:使用 _DEBUG
环境变量:如果需要自定义格式显示调式信息可以通过_DEBUG
环境变量来建立
#!/bin/bash
#shell脚本调试
DEBUG() {
[ "$_DEBUG" = "on" ] && $@ || :
}
for i in {1..5}
do
DEBUG echo $i
done
将调试功能设置为“on”来运行脚本:_DEBUG=on ./script.sh
将需要调式的行前加上DEBUG,运行脚本前没有加_DEBUG=on就不会显示任何信息,脚本中“:”告诉shell不要进行任何操作。
WeiyiGeek.debug
方法2:使用shebang调式方法:
把注释头从#!/bin/bash 修改成 #!/bin/bash -xv
,其他就不用做任何操作了,这是最便捷的方法.
shell切分和提取
在进行切分文件名,提取文件名 与 提取文件扩展名
,需要用到的几个操作符有:%、%%、#、##。
符号 | 说明 |
---|---|
${VAR%[通配符]} | % 属于非贪婪操作符,他是从右向左匹配最短结果 |
${VAR%%[通配符]} | %% 属于贪婪操作符,会从右向左匹配符合条件的最长字符串 |
${VAR#[通配符]} | # 属于非贪婪操作符,他是从左向右匹配最短结果 |
${VAR##[通配符]} | %% 属于贪婪操作符,会从左向右匹配符合条件的最长字符串 |
实际案例1:
代码语言:javascript复制#示例1.从右向左匹配 :% 和 %% 操作符的示例(<<)
#!/bin/bash
#提取文件名或者删除后缀
file_name="text.gif"
#从$VAR中删除位于 % 右侧的通配符左右匹配的字符串,通配符从右向左进行匹配,现在给变量 name 赋值,name=text.gif,那么通配符从右向左就会匹配到 .gif,所有从 $VAR 中删除匹配结果
name=${file_name%.*}
echo file name is: $name #file name is: test
file_name="text.gif.bak.2012"
name=${file_name%.*}
name2=${file_name%%.*} # 操作符 %% 使用 .* 从右向左贪婪匹配到 .gif.bak.2012
echo file name is: $name #file name is: test.gif.bak #使用 %,匹配到right到left得第一个"." <<
echo file name is: $name2 #file name is: test #使用 %%,匹配到从left到right得第一个"." >>
实际案例2:
代码语言:javascript复制#示例2.从左向右匹配:# 和 ## 操作符示例(<<)
#!/bin/bash
#提取后缀,删除文件名。
file_name="text.gif"
# ${VAR#*.} 含义:从 $VAR 中删除位于 # 右侧的通配符所匹配的字符串,通配符是左向右进行匹配。
suffix=${file_name#*.}
echo suffix is: $suffix #suffix is: gif
file_name="text.gif.bak.2012.txt"
suffix=${file_name#*.} # 与 %%.* 结果 相反
suffix2=${file_name##*.} # 与 %.* 结果 相反
echo suffix is: $suffix
echo suffix is: $suffix2 #操作符 ## 使用 *. 从左向右贪婪匹配到 text.gif.bak.2012
# suffix is: text.gif.bak.2012 使用 # ,取第一小数点开始到结尾的进行匹配 >>
# suffix2 is: txt 使用 ## ,取最后一个小数点的suffix(后缀名) <<
WeiyiGeek.名称切分案例
实际案例3:
代码语言:javascript复制// ## 取最后 一个 / 到末尾的字符串
// # 取第一个 / 到末尾的字符串
demo=1
for i in $(ls /nas_log/logs/student.log.2021-06-{27,28,29,30}.*.gz);do echo "${i##*/}";demo=$(($demo 1));cp ${i} /tmp/backup/${demo}-${i##*/};done
gzip -d student.log.2021-06-30-0.gz
输出说明:
代码语言:javascript复制/tmp/backup$ ls | more
1000-student.log.2021-06-30.6
1001-student.log.2021-06-30.7
1002-student.log.2021-06-30.8
1003-student.log.2021-06-30.9
1004-student.log.2021-06-27.0