• 函数


    一 函数介绍

    什么是函数???

    函数就是用来盛放一组代码的容器,函数内的一组代码完成一个特定的功能,称之为一组代码块,调用函数便可触发函数内代码块的运行,这可以实现代码的复用,所以函数又可以称之为一个工具
    

    为何要用函数

    #1、减少代码冗余
    #2、提升代码的组织结构性、可读性
    #3、增强扩展性
    

    二 函数的基本使用

    具备某一功能的工具=>函数
    事先准备好哦工具=>函数的定义
    遇到应用场景,拿来就用=>函数的调用
    
    所以函数的使用原则:先定义,后调用
    

    定义函数

    #语法:
    [ function ] funname [()]
    {
        命令1;
        命令2;
        命令3;
        ...
        [return int;]
    }
    
    # 示例1:完整写法
    function 函数名() {
    	函数要实现的功能代码
    }
    
    # 示例2:省略关键字(),注意此时不能省略关键字function
    function 函数名 {
    	函数要实现的功能代码
    }
    
    # 示例3:省略关键字function
    函数名() {
    	函数要实现的功能代码
    }
    

    调用函数

    # 语法:
    函数名  # 无参调用
    函数名 参数1 参数2  # 有参调用
    
    # 示例
    function test1(){
        echo "执行第一个函数"
    }
    
    
    function test2 {
        echo "执行第二个函数"
    }
    
    
    test3(){
        echo "执行第三个函数"
    }
    
    test1
    test2   # 调用函数:直接引用函数名字即调用函数,会触发函数内代码的运行
    test3
    
    # ps:关于有参调用见下一小节
    

    三 函数参数

    如果把函数当成一座工厂,函数的参数就是为工厂运送的原材料

    • 调用函数时可以向其传递参数

      # 调用函数test1,在其后以空格为分隔符依次罗列参数 
      test1 111 222 333 444 555
      
    • 在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...当n>=10时,需要使用${n}来获取参数

      [root@egon ~]# cat b.sh 
      function test1(){
          echo "...start..."
          echo $1
          echo $2
          echo $3
          echo "...end..."
      }
      
      test1 111 222 333 444 555  # 为函数体传参
      
      [root@egon ~]# ./b.sh 
      ...start...
      111
      222
      333
      ...end...
      

      ps:在脚本内获取脚本调用者在命令行里为脚本传入的参数,同样使用的是$n,不要搞混

      [root@egon ~]# cat b.sh 
      function test1(){
          echo "...start..."
          echo "这是函数内:$1"
          echo "这是函数内:$2"
          echo "这是函数内:$3"
          echo "...end..."
      }
      
      # test1 111 222 333 444 555
      test1
      
      echo "这是脚本级的参数$1"
      echo "这是脚本级的参数$2"
      echo "这是脚本级的参数$3"
      
      [root@egon ~]# 
      [root@egon ~]# 
      [root@egon ~]# ./b.sh xxx yyy zzz mmm nnn
      ...start...
      这是函数内:
      这是函数内:
      这是函数内:
      ...end...
      这是脚本级的参数xxx
      这是脚本级的参数yyy
      这是脚本级的参数zzz
      [root@egon ~]# 
      

    温故知新

    参数处理 说明

    $# 传递到脚本或函数的参数个数

    $* 所有参数

    $@ 所有参数,与$*类似

    $$ 当前脚本进程的ID号

    $? 获取上一条命令执行完毕后的退出状态。0表示正确,非0代表错误,如果执行的是函数那么$?取的是函数体内return后的值

    ps:

    #1、当$*和$@没有被引号引用起来的时候,它们确实没有什么区别,都会把位置参数当成一个个体。
    
    #2、"$*" 会把所有位置参数当成一个整体(或者说当成一个单词),如果没有位置参数,则"$*"为空,如果有两个位置参数并且分隔符为空格时,"$*"相当于"$1 $2"
    
    #3、"$@"  会把所有位置参数当成一个单独的字段,如果没有位置参数,则"$@"展开为空(不是空字符串,而是空列表),如果存在一个位置参数,则"$@"相当于"$1",如果有两个参数,则"$@"相当于"$1"  "$2"等等
    

    示例

    [root@egon ~]# cat b.sh 
    echo "=======函数test1==========="
    function test1(){
        echo "$*"  # 111 222 333 444 555
        echo "$@"  # 111 222 333 444 555
        echo $#    # 5
        echo $$    # 87380
        echo $?    # 0
    }
    
    test1 111 222 333 444 555
    
    
    echo "=======函数test2==========="
    function test2(){
        for i in "$*"  # 注意:$*不加引号结果与$@一模一样
        do
            echo $i
        done
    }
    
    test2 111 222 333 "444 555"  # 注意"444 555"被引号引成了一个参数
    # 运行结果为:111 222 333 444 555
    
    echo "=======函数test3==========="
    function test3(){
        for i in "$@"  # 注意:$*不加引号结果与$@一模一样
        do
            echo $i
        done
    }
    
    test3 111 222 333 "444 555"  # 注意"444 555"被引号引成了一个参数
    # 运行结果为:
    # 111
    # 222
    # 333
    # 444 555
    

    四 函数的返回值

    如果把函数当成一座工厂,函数的返回值就是工厂的产品,在函数内使用return关键字返回值,函数内可以有多个return,但只要执行一个,整个函数就会立刻结束

    [root@egon ~]# cat b.sh 
    function test1(){
        echo 111
        return
        echo 222
        return 
        echo 333
    }
    
    test1
    
    [root@egon ~]# chmod +x bbbb.sh 
    [root@egon ~]# ./b.sh 
    111
    

    需要注意的是shell语言的函数中,通常用return返回函数运行是否成功的状态,0代表成功,非零代表失败,需要用$?获取函数的返回值

    • 1、如果函数内没有return,那么将以最后一条命令运行结果(命令运行成功结果为0,否则为非0)作为返回值

      [root@egon ~]# cat b.sh 
      function test1(){
          echo 111
          echo 222
          echo 333
          xxx  # 运行该命令出错
      }
      
      test1
      echo $?
      [root@egon ~]# ./b.sh 
      111
      222
      333
      ./b.sh:行5: xxx: 未找到命令
      127
      
    • 2、如果函数内有return,那么return后跟的只能是整型值并且范围为0-255,用于标识函数的运行结果是否正确, 与C 语言不同,shell 语言中 0 代表 true,0 以外的值代表 false

      [root@egon ~]# cat b.sh 
      function test1(){
          echo 111
          echo 222
          echo 333
          return 0
      }
      
      test1
      echo $?  # 用$?获取函数的返回值
      [root@egon ~]# ./b.sh 
      111
      222
      333
      0
      

    五 变量的作用域

    Shell 变量的作用域(Scope),就是 Shell 变量的有效范围(可以使用的范围)。

    1、局部变量:只能在函数内访问

    使用local关键字定义在函数内的变量属于局部范围,只能在函数内使用,如下所示

    [root@localhost shell]# cat hello.sh 
    #!/bin/bash
    
    # 定义函数
    function test(){
        local x=111 
        echo "函数内访问x:$x"
    }
    
    # 调用函数
    test
    
    echo "在函数外即全局访问x:$x"  # 无法访问函数内的局部变量
    

    执行结果

    [root@localhost shell]# ./hello.sh 
    函数内访问x:111
    在函数外即全局访问x:
    

    2、全局变量:可以在当前shell进程中使用

    所谓全局变量,就是指变量在当前的整个 Shell 进程中都有效。每个 Shell 进程都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认就都是全局变量。

    [root@localhost shell]# cat hello.sh 
    #!/bin/bash
    x=2222
    
    function test(){
        echo "函数内访问x:$x"
    }
    test
    echo "在函数外即全局访问x:$x" 
    

    执行结果

    [root@localhost shell]# ./hello.sh 
    函数内访问x:2222
    在函数外即全局访问x:2222
    

    请注意!!!

    • 1、在函数内定义的变量,如果没有用 local声明,那么默认也是全局变量,shell变量语法的该特性与js的变量是类似的(在js函数内部定义的变量,默认也是全局变量,除非加上关键字var)

      [root@localhost shell]# cat hello.sh 
      #!/bin/bash
      function test(){
          x=2222  # 全局变量
      }
      test
      echo "在函数外即全局访问x:$x"  
      
      [root@localhost shell]# ./hello.sh 
      在函数外即全局访问x:2222
      
    • 2、每执行一个解释器,都会开启一个解释的shell进程,每个shell进程都有自己的作用域彼此互不干扰

      [root@localhost shell]# x=111  # 该变量仅仅只在当前shell进程中有效,对新的shell进程无影响
      [root@localhost shell]# echo $x
      111
      [root@localhost shell]# bash  # 执行bash解释器,则开启一个新的进程,或者干脆直接打开一个新的终端
      [root@localhost shell]# echo $x
      
      [root@localhost shell]# 
      
    • 3、需要强调的是:

      全局变量的作用范围是当前的 Shell 进程,而不是当前的 Shell 脚本文件,它们是不同的概念。打开一个 Shell 窗口就创建了一个 Shell 进程,打开多个 Shell 窗口就创建了多个 Shell 进程,每个 Shell 进程都是独立的,拥有不同的进程 ID。在一个 Shell 进程中可以使用 source 命令执行多个 Shell 脚本文件,此时全局变量在这些脚本文件中都有效。

      [root@localhost shell]# echo $x
      
      [root@localhost shell]# cat hello.sh 
      #!/bin/bash
      function test(){
          x=2222  # 全局变量
      }
      test
      
      [root@localhost shell]# source hello.sh  # 在当前shell进程中执行,产生一个全局变量x
      [root@localhost shell]# echo $x  # 在当前shell进程中访问全局变量x,可以看到
      2222
      [root@localhost shell]# 
      [root@localhost shell]# 
      [root@localhost shell]# cat aaa.sh 
      #!/bin/bash
      echo $x
      
      [root@localhost shell]# source aaa.sh # 在当前shell进程中访问全局变量x,同样可以看到
      2222
      

      结论:函数test内的全局变量x早已超越了文件,即全局变量是超越文件的,作用范围是整个当前bash进程

    3、环境变量:在当前进程的子进程中都可以使用

    全局变量只在当前 Shell 进程中有效,对其它 Shell 进程和子进程都无效。如果使用export命令将全局变量导出,那么它就在所有的子进程中也有效了,这称为“环境变量”。

    环境变量被创建时所处的 Shell 进程称为父进程,如果在父进程中再创建一个新的进程来执行 Shell 命令,那么这个新的进程被称作 Shell 子进程。当 Shell 子进程产生时,它会继承父进程的环境变量为自己所用,所以说环境变量可从父进程传给子进程。不难理解,环境变量还可以传递给孙进程。

    [root@localhost shell]# export y=333  # 爷爷
    [root@localhost shell]# bash  # 爸爸
    [root@localhost shell]# echo $y
    333
    [root@localhost shell]# bash  # 孙子
    [root@localhost shell]# echo $y
    333
    
    ps:通过exit命令可以一层一层地退出 Shell。
    

    ps:命令set 和 env

    set:显示所有变量
    env:环境变量
    

    注意

    • 1、环境变量只能向下传递而不能向上传递,即“传子不传父”。

    • 2、两个没有父子关系的 Shell 进程是不能传递环境变量的

      我们一直强调的是环境变量在 Shell 子进程中有效,并没有说它在所有的 Shell 进程中都有效;如果你通过终端创建了一个新的 Shell 窗口,那它就不是当前 Shell 的子进程,环境变量对这个新的 Shell 进程仍然是无效的。
      
    • 3、环境变量也是临时的

      [root@localhost ~]# ps aux |grep bash$ |grep -v grep
      root     123436  0.0  0.1 116356  2960 pts/0    Ss   21:52   0:00 -bash
      root     123492  0.0  0.1 116472  2932 pts/0    S    21:54   0:00 bash
      root     123520  0.0  0.1 116440  2988 pts/0    S    21:54   0:00 bash
      
      注意:
      # -开头的bash代表是在登录终端登录的顶级shell进程
      # 非-开头的bash代表的是子shell进程
      一旦退出了在终端登录的顶级shell,那么该终端下开启的所有子shell都会被回收,export设置的环境变量随即消失
      

      所以说环境变量也是临时的,如果想设置成永久的,需要将变量写入shell配置文件中才可以,Shell 进程每次启动时都会执行配置文件中的代码做一些初始化工作,如果将变量放在配置文件中,那么每次启动进程都会定义这个变量。请看下一小节

    六 登录shell与非登录shell

    承接上一小节末尾留下的疑问,我们首先需要了解一下BASH的两种类型

    • 1、登录shell:就是通过输入用户名 密码后 或 su - 获得的shell
    • 2、非登录shell:则是通过bash命令和脚本开启的shell环境

    那么他们有什么区别呢?和我们永久设定环境变量又有什么关系呢?

    我们知道在linux里一切皆为文件,同样,shell的属性加载也是写到文件里的
    在登陆时就会加载对应文件的内容来初始化shell环境,
    非登录与登录区别就在于加载的文件不同 从而导致获得的shell环境不同
    我们看看登录shell都加载了那些文件
    --> /etc/profile
    --> /etc/profile.d/*.sh
    --> $HOME/.bash_profile
    --> $HOME/.bashrc
    --> /etc/bashrc
    再看非登录shell加载的文件,非登录shell加载的文件要少很多
    --> $HOME/.bashrc
    --> /etc/bashrc
    --> /etc/profile.d/*.sh
    

    通常,我们会将环境变量设置在 $HOME/.bash_profile

    但如果不管哪种登录shell都想使用的变量 可以考虑设置在 $HOME/.bashrc或者/etc/bashrc中,因为它们都属于无论如何都会执行的文件,但是,如果我们真的在这类文件中添加了变量,那么意味着每次执行shell都会重新定义一遍该变量,而定义变量是要耗费内存资源的,这非常不可取,所以我们通常会结合export在/etc/profile文件中声明一个全局变量,这样在每次登录用户时产生的顶级shell里会有一个全局变量,所有的子shell都可以看到了,无需重复定义

    [root@localhost ~]# vim /etc/profile
    [root@localhost ~]# head -2 /etc/profile
    # /etc/profile
    name="egon"
    [root@localhost ~]# echo $name
    
    [root@localhost ~]# source /etc/profile  # 可以在当前shell中立即生效,也可以退出后重新登录终端
    [root@localhost ~]# echo $name
    egon
    
    

    七 案例

    开发一个计算器程序如下,引入函数减少代码冗余

    [root@egon ~]# cat b.sh 
    #!/bin/bash
    
    echo " ----------------------------------"
    echo "|这是一个简单的整数计算器,biubiu  |"
    echo " ----------------------------------"
    echo
    
    # 接收第一个整数,并校验
    while :
    do
        read -p  "请输入第一个整数: " num1
        expr $num1 + 0 &> /dev/null
        if [ $? -eq 0 ];then
            break
        else
            echo "必须输入整数"
        fi
    done
    
    # 接收第二个整数,并校验
    while :
    do
        read -p  "请输入第二个整数: " num2
        expr $num2 + 0 &> /dev/null
        if [ $? -eq 0 ];then
            break
        else
            echo "必须输入整数"
        fi
    done
    
    # 接收输入的操作
    echo "-------------------"
    echo "|  1.加法         |"
    echo "|  2.减法         |"
    echo "|  3.乘法         |"
    echo "|  4.除法         |"
    echo "-------------------"
    read -p "请输入您想执行的操作:" choice
    case $choice in
        "1")
            res=`expr $num1 + $num2`
            echo "$num1+$num2=$res"
            ;;
        "2")
            res=`expr $num1 - $num2`
            echo "$num1+$num2=$res"
            ;;
        "3")
            res=`expr $num1 * $num2`
            echo "$num1*$num2=$res"
            ;;
        "4")
            res=`expr $num1 / $num2`
            echo "$num1/$num2=$res"
            ;;
        *)
            echo "未知的操作"
    esac
    
  • 相关阅读:
    Spring注解开发系列Ⅵ --- AOP&事务
    Spring注解开发系列Ⅴ --- 自动装配&Profile
    Spring注解开发系列Ⅲ --- 生命周期
    Spring注解开发系列Ⅱ --- 组件注册(下)
    .net Core在过滤器中获取 系统接口方法(以IMemoryCache 为例) 及HttpContext 获取系统接口
    【旧文章搬运】炉子给的SYSTEM_HANDLE_TYPE有点错误
    【旧文章搬运】超级无敌大炉子的LzOpenProcess
    【旧文章搬运】PE重定位表学习手记
    【旧文章搬运】ZwQuerySystemInformation枚举内核模块及简单应用
    【旧文章搬运】ZwQuerySystemInformation枚举进线程信息
  • 原文地址:https://www.cnblogs.com/caodan01/p/14949093.html
Copyright © 2020-2023  润新知