gnosis of shell 深入理解shell编程

  

  1. shell中if while等后必须接命令,利用命令的exit code作为判断,而其他高级语言是利用return值作为判断
    对于非命令变量,可以利用test or [ ] or [[ ]] 进行判断
    如果确实需要用命令或函数的返回值作为判断,可以如下
    function p(){
        echo $$
        exit 55
    }
    
    if ! [ `p` ];then
        echo bbbb
    else
        echo pppppppppppp
    fi

     

    利用命令替换获取函数p的返回值(由echo提供),而后利用 [ ] 进行判断
    在执行函数p的时候,没有因为exit 55 退出整个脚本,而是打印了pppppppp,所以据此判断shell中命令引用是开辟了一个新的shell进程来执行命令引用,所以命令引用中的exit不会影响到父shell

     

    function p(){
        echo $$
        exit 55
    }
    
    if ! p;then
        echo bbbb
    else
        echo pppppppppppp
    fi

     

     直接用函数p的exit code : 55进行判断,但是函数p中包含了exit,结束了当前shell进程,所以整个shell脚本退出了,故此种方式函数p的执行是在当前shell进程,跟命令引用是不同的
    所以改为return



    function p(){
        echo $$
        return 55
    }
    
    if ! p;then
        echo bbbb
    else
        echo pppppppppppp
    fi

     

     函数返回值没有变量接收,直接打印了,exit code 为 55 ,取反后,为真
     

     

  2. 直接ps只会显示当和当前shell关联的进程和其父进程

     

     

  3. function b(){
        echo $$
        exit 55
    }
    
    echo 'current shell PID: '$$
    
    p=`b`
    
    echo
    echo 'function b PID: '$p
    echo 
    
    "ps"

     

     命令引用后在函数b中$$依然为 20933,难道命令引用没有开辟子shell吗?
    $       扩展为shell的进程ID。在一个()子shell,它扩展为当前shell的进程ID,而不是子shell。

    shell中的函数凡是输出stdout & stderr的都是返回值,也可以利用echo明确返回

    看如下例子:

    function b(){
        "ps"
        exit 55
    }
    
    echo 'current shell PID: '$$
    
    p="`b`"
    
    echo
    echo -e 'function b ps result: \n'"$p"
    echo 
    
    "ps"

     

     明显看到执行函数b的命令引用时,多出 21476 这个子shell进程,这个时候不能通过变量 $ 进行判断了

     

    function b(){
        "ps"
        return 55 # substitution exit to return to prevent the whole script exit
    }
    
    echo 'current shell PID: '$$
    
    echo
    
    b
    
    echo 'function b exit code: '$?
    
    echo
    
    "ps"

     

     

     直接调用b,可以看到并没有开辟子shell执行函数b

     

    function p(){
            "ps"
    }
    
    echo -e '\033[7mscript ps\033[0m':
    "ps"
    
    echo
    
    echo -e '\e[7mfunction ps\e[0m':
    "p"

     

     

  4.  

     shell内置变量BASHPID与 $ 相同,显示当前shell的PID

    #!/bin/sh
    
    echo $BASHPID
    
    function b(){
            echo 'function b: '$BASHPID
    }
    
    b

     

     

     直接调用函数b,显示相同的PID

     

    #!/bin/sh
    
    echo $BASHPID
    
    function b(){
            echo 'function b: '$BASHPID
    }
    
    `b`

     

     

     上面出错的原因是`b`再次把函数b的返回值(echo 部分),作为命令执行了,相当于 在shell中输入   function b: $BASHPID

    此时shell会把function当作可执行命令,故报错

    #!/bin/sh
    
    echo $BASHPID
    
    function b(){
            echo 'function b: '$BASHPID
    }
    
    echo `b`

     

    直接把函数 b 的返回值用echo打印

     

  5. BASH_SUBSHELL变量可以查看从当前shell开始的shell层数

     

     

     当前shell显示为0,最顶级

     

    echo 'current shell:' $BASH_SUBSHELL
    
    function p(){
            echo 'inner function p:' $BASH_SUBSHELL
    }
    
    function b(){
    
            echo "`p`"
    
            echo 'function b:' $BASH_SUBSHELL
    }
    
    echo "`b`"

     

     

     

  6.  

    shell脚本进程名
    reap.sh

    #!/bin/env sh
    
    echo $BASHPID
    for((;;));do
            echo `basename $0`
            sleep 1
    done

     

    #!/bin/env sh    此种情况的脚本进程名:

    ./reap.sh运行的,脚本名进程名就是 env 后面的部分
    sh 运行 脚本进程名是sh
    bash 运行脚本进程名是bash
    
    因为sh or bash运行脚本 shebang将不再发挥作用
    但是 basename $0永远是脚本文件名

     


     

     

     

     

     


     

     

    #!/bin/bash
    
    echo $BASHPID
    for((;;));do
            echo `basename $0`
            sleep 1
    done


    #!/binbash or #!/bin/sh 的脚本进程名

    ./reap.sh 运行的脚本进程名就是脚本文件名
    
    bash reap.sh    or        sh reap.sh 的脚本进程名则是 bash 或者 sh
    
    但是basename $0 还同样是 脚本文件名

     


     

     

     

     

     

     

     

     

     

     

     

  7. #!/bin/sh
    
    echo $BASHPID
    
    sleep 50 &
    
    sleep 10
    
    
    chmod u+x garble.sh
    ./garble.sh

     

      当前shell进程BASHPID 为 2129, garble.sh开辟的子shell BASHPID为6416

     

      10s内pstree -p观察,garble.sh生成了两个sleep子进程 6417 & 6418
    10s后pstree -p观察

     

     只剩下后台的sleep进程6417,挂靠在一号进程systemd下

    #!/bin/sh
    
    echo $BASHPID
    
    sleep 50 &
    
    sleep 10
    
    kill $!

    可以改进脚本,kill $!可以杀掉脚本中最后一个运行的后台进程


    再来看一种特殊效果

    #!/bin/sh
    
    echo $BASHPID
    
    commence=`date +'%Y-%m-%d %H:%M:%S'`
    echo $commence
    
    function uiop(){
            sleep 10 &
    }
    
    `uiop`
    
    sleep 15
    
    kill $!
    
    closure=`date +'%Y-%m-%d %H:%M:%S'`
    echo $closure
    
    commence_seconds=$(date --date="$commence" +%s)
    closure_seconds=$(date --date="$closure" +%s)
    
    echo -e "\e[7;36mscript elapsed: $[closure_seconds-commence_seconds]\e[0m"

     

     前10S的状态

     

     15S后台的状态

     

     
    函数uiop内sleep是后台运行的,函数uiop结束后,函数uiop几乎在瞬间就结束了,sleep就直接挂在systemd下,但是函数uiop不是在后台运行的,所以garble.sh (28546)脚本主进程会等sleep(28549)结束后才向下执行


    #!/bin/sh
    
    echo $BASHPID
    
    commence=`date +'%Y-%m-%d %H:%M:%S'`
    echo $commence
    
    function uiop(){
            sleep 10
            echo $BASH_SUBSHELL
    }
    
    echo `uiop`
    
    sleep 15
    
    kill $!
    
    closure=`date +'%Y-%m-%d %H:%M:%S'`
    echo $closure
    
    commence_seconds=$(date --date="$commence" +%s)
    closure_seconds=$(date --date="$closure" +%s)
    
    echo -e "\e[7;36mscript elapsed: $[closure_seconds-commence_seconds]\e[0m"

    前10s

     

     

     后10s

     

     
    sleep放在前台运行的话,就很好理解了,开辟子shell,运行函数
    需要注意的是函数名为uiop,但是子shell进程名是和父shell相同,都是脚本名

  8. #!/bin/sh
    
    echo $BASHPID
    
    for((;;));do
            echo `basename $0`
            sleep 5
    done &
    
    
    sleep 10

    前10s

     

     

     后10s - ~

     

     

    分析:前10s,for loop由于是后台执行,故开辟子shell(24626),和 reap.sh的sleep(24637)几乎同时运行,10s后,reap.sh(24625)结束,reap.sh(24626)被systemd收养
    需要注意的是子shell默认进程名和父shell相同,都是脚本名

  9. #!/bin/sh
    
    echo $BASHPID
    
    for((;;));do
            echo `basename $0`
            sleep 5
    done &
    
    killall sleep
    kill $BASHPID

     

     

     

     killall sleep & kill $BASHPID 都不能杀死 & 生成子shell进程 killall sleep只能杀死现存的sleep,而kill $BASHPID杀死了父shell,都与 & 生成的子shell无关

  10. 30958
    reap.sh
    Terminated
    [root@perpetual zxc]# cat reap.sh
    #!/bin/sh
    
    echo $BASHPID
    
    for((;;));do
            echo `basename $0`
            sleep 5
    done &
    
    killall `basename $0`

     


    killall `basename $0`能杀死所有 reap.sh进程,这只适用于 ./reap.sh的运行方法

     

     

     

     

     

  11.  

    再考虑一个问题,如果脚本已经执行到了while中的后台任务,但在执行到killall命令之前按下了CTRL+C,这时由于没有执行killall,后台任务也将挂在新的脚本进程下。我们的目的是保证脚本终止,其内进程一定终止。所以我们需要对这种情况做出合理的处理。

     

    #!/bin/sh
    
    trap "killall `basename $0`" SIGINT
    
    echo $BASHPID
    
    for((;;));do
            echo `basename $0`
            sleep 5
    done &
    
    killall `basename $0`


    如果使用bash reap.sh 或者 sh reap.sh则进程名为bash or sh , reap.sh是一个参数

     

     

    这个时候要借助pkill的 -f选项     -f, --full                use full process name to match

    #!/bin/sh
    
    
    trap "pkill -f `basename $0`" SIGINT
    
    echo $BASHPID
    
    for((;;));do
            echo `basename $0`
            sleep 5
    done &
    
    pid=$!
    
    kill $pid
    sleep 5
    
    pkill -f `basename $0`

     

     第一个Terminated 是 kill $pid的



     

     

posted @ 2020-10-25 12:54  ascertain  阅读(182)  评论(0编辑  收藏  举报