shell脚本编程之路3

2022-09-28 20:32:24 浏览数 (2)

[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环境变量来建立

代码语言:javascript复制
#!/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

0 人点赞