美文网首页
Shell编程极简入门

Shell编程极简入门

作者: Ricky_Zeng | 来源:发表于2021-05-30 19:53 被阅读0次

    Shell编程不花里胡哨,一篇就够~

    一、基础入门

    1.1 shell脚本的第一行代码

    #!/bin/bash
    

    Linux环境下的任何脚本语言,都是以这样一个被称为shebang的特殊行作为起始的。

    1.2 脚本的运行方式

    将脚本作为sh的命令行参数

    $ sh script.sh
    

    将脚本作为具有执行权限的可执行文件(这种方式shebang行可以不添加)

    $ chmod a+x script.sh
    $ /Users/xxPath/xxPath/script.sh
    

    1.3 终端打印

    $ echo "Hello shell..." # 无情的打印
    

    在终端中生成彩色输出还是比较好玩的,系统日志看起来总那么“无情”,何不给日志来点有温度的颜色

    $ echo -e "\033[30m 黑色字 \033[0m"
    $ echo -e "\033[31m 红色字 \033[0m"
    $ echo -e "\033[32m 绿色字 \033[0m"
    $ echo -e "\033[33m 黄色字 \033[0m"
    $ echo -e "\033[34m 蓝色字 \033[0m"
    $ echo -e "\033[35m 紫色字 \033[0m"
    $ echo -e "\033[36m 天蓝字 \033[0m"
    $ echo -e "\033[37m 白色字 \033[0m"
    

    1.4 变量的使用

    在shell中,每个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式储存。

    $ days1=1
    $ echo $days1
    

    有时候我们想把一个命令的执行输出结果赋值给一个变量,可以通过反引号字符 `` 或 $() 的格式,比如

    $ s1=`date`
    $ s2=$(date)
    

    如果shell脚本里头又启动了其它子shell脚本进程的话,很可能涉及环境变量,shell中设置环境变量通过export,使用演示如下:

    $ export env_name=env_val
    

    当环境不需要的时候可以取消:

    $ unset env_name
    

    1.5 通过shell进行数学运算

    在shell中,可以通过let、(( ))、[ ]执行基本的算数操作,如果进行高级操作时,expr和bc这两个工具也会很有用。

    #!/bin/bash
    num1=1
    num2=2
    # ------------------- 求和 -----------------
    let sum=num1+num2
    echo $sum
    # ------------------- 自增/自减 -----------------
    let num1++
    echo $num1
    let num1--
    echo $num1
    # ------------------- 加/减某个值 -----------------
    let num1+=1 # 或let num1=num1+1
    echo $num1
    let num1-=1 # 或let num1=num1-1
    echo $num1
    
    # ---- 可以留意到,用let运算,操作变量不需要加$ ----
    

    作为快速入门,要学先做减法,因此这里运算符只简单介绍一种,其他方式可以在真正用到的时候查,大同小异。当然要注意的是:let、(( ))、[ ]、expr这些方式都只能用于整数运算,不支持浮点数,此时bc工具就派上用场了,举个栗子:

    $ echo "4 * 5.54" | bc
    22.16
    

    1.6 普通数组

    # 普通数组赋值的两种方式:
    # 1)创建变量时赋值
    arr=(t1 t2 t3 t4)
    
    # 2)索引-值赋值
    arr[0]="test1"
    arr[1]="test2"
    arr[2]="test3"
    arr[3]="test4"
    arr[4]="test5"
    
    # 打印数组所有值的方式
    echo ${arr[*]}
    echo ${arr[@]}
    
    # 打印数组的长度
    echo ${#arr[*]}
    

    1.7 关联数组(可以用字符串作为索引)

    关联数组的定义:declare -A ass_array,关联数组从Bash 4.0版本开始被引入,并非所有shell版本支持,看情况用吧

    # 关联数组的赋值的两种方式:
    # 1)创建变量时赋值
    declare -A ass_array 
    ass_array=([name1]=ricky1 [name2]=ricky2)
    
    # 2)键值对的形式赋值
    declare -A ass_array2 
    ass_array2[name3]=ricky3
    ass_array2[name4]=ricky4
    
    echo ${ass_array[*]}
    

    1.8 调试脚本

    使用选项-x,启动shell脚本,能打印出所执行的每一行命令以及当前状态。

    $ sh -x script.sh
    

    当然啦,有时候不需要打印所有行的执行情况,可以用:

    set -x : 在执行时显示参数和命令

    set +x : 禁止调试

    arr=(t1 t2 t3 t4)
    set -x
    echo ${#arr[*]}
    set +x
    echo "Ohter code"
    

    在上面的脚本中,仅在-x和+x所限制的区域内的调试信息才会被打印出来,打印结果如下:

    $ sh script.sh
    + echo 4
    4
    + set +x
    Ohter code
    

    1.9 状态码

    shell每个命令执行完后都有一个退出状态码,表示它已运行完毕,退出状态码是一个0~255的整数值。在脚本中可以捕获上一个命令的状态码做一些逻辑处理。Linux提供了一个专门的变量$?来保存上个已执行命令的退出状态码,使用演示:

    $ date
    2020年12月25日 星期五 00时10分02秒 CST
    $ echo $?
    0
    

    一般命令成功执行时,退出状态码是0,如果命令结束时有错误,退出状态码一般是一个正数值,如:

    $ asdfas
    zsh: command not found: asdfas
    $ echo $?
    127
    

    常见退出状态码以及描述(当然,表格里的状态码我没都遇到过,但比如127的退出状态码就比较容易常见了)

    状态码 描述
    0 命令成功执行
    1 一般性未知错误
    2 不适合的shell命令
    126 命令不可执行
    127 没找到命令
    128 无效的退出参数
    130 通过CTRL+C终止命令
    255 正常范围之外的退出状态码

    那我们整份脚本文件运行结束的退出状态码呢?


    Shell脚本会以脚本中的最后一个命令的退出状态码退出,你也可以改变这种默认行为,返回自己的退出状态码,exit命令就派上用场了:

    #!/bin/bash
    s1=10
    s2=20
    let sum=s1+s2
    echo $sum
    exit 5
    

    假设上述代码为script.sh文件的脚本内容,则在终端的执行结果为:

    $ sh script.sh
    30
    $ echo $?
    5
    

    如果我自定义的退出状态码大于前面的0~255的范围呢?比如exit 300(将上述script.sh文件的exit 5改为exit 300),则输出结果为:

    $ sh script.sh
    30
    $ echo $?
    $ 44
    

    shell中会对最终结果除以256进行求模,300/256的模为44(即整除后的余数)

    1.10 函数的定义和调用

    1.10.1 函数的定义和调用
    #!/bin/bash
    
    function first_func_demo
    {
        echo "函数的定义就这样了..."
    }
    
    # 或者
    
    second_func_demo()
    {
        echo "函数也可以这么定义..."
    }
    
    # 调用函数
    first_func_demo
    second_func_demo
    

    直接复制上面的代码跑一下吧,一秒就会。如果要向函数传递参数,又如何呢,继续上面的脚本,向函数传递两个参数,演示如下:

    function third_func_demo
    {
        echo "调用函数时传递参数以及读取参数就这么简单..."
        # 读取第一个参数值
        echo $1
        # 读取第二个参数值
        echo $2
    }
    
    # 1) 向函数传递两个参数,如果参数更多,空格分界,以此类推...
    num1=10
    num2=100
    third_func_demo $num1 $num2 
    

    直接复制上面的代码跑一下吧,又是一秒就会。

    注意,你可能经常看到shell脚本里头有0、1、2一直到n、@、*、$#...emmm,别慌,总结在下面:

    $0:    表示程序名
    $1...n:表示第1个...第n个参数
    $#:    表示输入参数的个数
    $@:    获取传入的参数数组,可用于for循环遍历处理场景
    $*:    把所有命令行参数当做一个整体
    
    1.10.2 函数的返回值

    shell会把函数当作一个小型脚本,运行结束时会返回一个状态码,默认情况,函数的退出状态码是函数中最后一条命令返回的退出状态码,但这样你就无法得知函数里头的代码是否都被正确的执行,因此我们也可以用return命令返回函数值。

    function func1 {
        result=200
        return $result
    }
    
    func1
    echo "func1 run result: $?"
    

    当然我们也可以通过echo输出来返回函数值

    function func2 {
        result=200
        echo $result
    }
    
    echo "func2 run result: $(func2)"
    
    1.10.3 在.bashrc文件中定义函数

    shell每次启动时会重新载入.bashrc(因为我的Mac是用zsh,所以是.zshrc,为了名字通用,我们都先同意叫bashrc吧。。。),bashrc文件一般放在/home/linux用户名下,我们可以定义自己工具类函数,用于处理日常一些繁琐的工作流程,使其自动化,这里举两个栗子吧:


    1.比如每天都要更新所有的git仓库并进行备份;

    2.常常很多命令没记错,比如dumpsys package、meminfo、activity,am,pm等等以及其他串口命令,可以自定整理一份文档之后,之后通过调用函数直接打印这些常用命令


    下面开始演示,我就定义个简单的栗子吧,在我的.zshrc文件中添加:

    # 在.zshrc追加函数
    function addem {
        echo $[ $1 + $2 ]
    }
    

    重启zsh终端,然后运行试试:

    $ addem 10 20
    30
    

    1.11 命令行参数的处理

    上面展示了调用函数如何输入参数和读取参数,那如果执行某个脚本文件或者执行某个命令行的参数输入、读取呢?其实跟函数参数的输入、读取基本一致,直接上小结吧

    1.命令行参数的读取,$0:程序名,$1,$2...表示第一个、第二个参数依次类推
    2.特殊参数变量:$#(返回命令参数的个数)
    3.获取所有命令行参数:$*(把所有命令行参数当做一个整体)、$@(获取命令行数组,主要用于for循环)
    

    这3点问题不大,直接上个小demo演示,scirpt.sh脚本内容如下:

    #!/bin/bash
    echo $0
    echo $1
    echo $2
    

    在终端执行script.sh脚本结果演示:

    $ sh /Users/xxx/Desktop/script.sh  HaHa 123
    /Users/xxx/Desktop/script.sh
    HaHa
    123
    

    1.12 命令行选项的处理

    命令行选项也没什么特殊的。在命令行上,它们紧跟在脚本名之后,就跟命令行参数一样。实际上,如果愿意,你可以像处理命令行参数一样处理命令行选项。选项是跟在单破折线后面的单个字母,根据不同的选项执行不同的逻辑,有点想switch语句。script.sh演示脚本如下:

    #!/bin/bash
    while [ -n "$1" ]
    do
        case "$1" in
            -a) echo "Found -a option" ;;
            -b) echo "Found -b option" ;;
             *) echo "$1 option not found" ;;
        esac 
        shift
    done
    

    在终端执行script.sh脚本并输入选项试试:

    $ sh script.sh  -a -b -c -d
    Found -a option
    Found -b option
    -c option not found
    -d option not found
    

    那命令行参数和命令行选项同时用呢?通过双破折线(--)来实现参数和选项的分离。shell会用双破折线来表明选项列表的结束,在双破折线之后,脚本就可以放心地读剩下的命令行参数了,而不是当选项处理。

    #!/bin/bash
    while [ -n "$1" ]
    do
        case "$1" in
            -a) echo "Found -a option" ;;
            -b) echo "Found -b option" ;;
            --) shift
                break ;;
             *) echo "$1 option not found" ;;
        esac 
        shift
    done
    
    count=1
    for param in $@
    do
        echo "Param: $param"
        let count+=1
    done
    

    我们执行上面的script.sh脚本,并传入选项参数、命令行参数,得到执行结果如下:

    $ sh script.sh  -a -b -c -d -- HAHA 123
    Found -a option
    Found -b option
    -c option not found
    -d option not found
    Param: HAHA
    Param: 123
    

    那如果有些选项参数需要传值呢?我们在继续基于上面的脚本修改:

    #!/bin/bash
    while [ -n "$1" ]
    do
        case "$1" in
            -a) echo "Found -a option" ;;
            -b) param_value="$2"
                echo "Found -b option with value $param_value"
                shift ;;
            --) shift
                break ;;
             *) echo "$1 option not found" ;;
        esac 
        shift
    done
    
    count=1
    for param in $@
    do
        echo "Param: $param"
        let count+=1
    done
    

    脚本修改之后的执行结果:

    $ sh script.sh  -a -b ss -c -d -- HAHA 123
    Found -a option
    Found -b option with value ss
    -c option not found
    -d option not found
    Param: HAHA
    Param: 123
    

    1.13 getopt命令

    有时候我们需要合并选项参数,比如这样的格式:

    $ sh script.sh -ab
    

    getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的格式。getopt的命令格式如下:

    getopt optstring params
    

    optstring是关键所在,它定义了命令行所有需要输入的选项参数字母,如果选项参数需要带值,则在对应的字符后面加冒号(:)


    有点绕?我们用上面在终端运行的脚本作为栗子:

    $ sh script.sh  -a -b ss -c -d -- HAHA 123
    

    一眼就看到script.sh脚本的选项参数有,abcd,其实b还带参数值,那如果用getopt命令来处理后面这一大串选项、参数会是怎样的呢?我们在终端尝试运行下面的命令:

    $ getopt ab:cd -a -b ss -cd HAHA 123
    -a -b ss -c -d -- HAHA 123
    

    在命令选项的工作机制之后,假设我们以后想了解某个命令行怎么用?可以怎么做呢?还是做个demo吧:

    function read_params
    {
        while [[ -n $1 ]]
        do
            case $1 in
                -a) echo "Found option -a" ;;
                -b) echo "Found option -b" 
                    echo "Value is $2"
                    shift ;;
                --) shift
                    break ;;
                 *) echo "Option not found" ;;
            esac
            shift
        done
    
        count=1
        for param in $@
        do
            echo $param
            let count+=1
        done
    }
    
    read_params `getopt ab: $*`
    

    然后在终端就可以:

    $ sh script.sh -ab HAHA 123 456
    

    PS:我们常常用grep命令,比如:

    $ grep -ir shell filename
    $ grep -i -r shell filename
    $ grep -r -i shell filename
    

    相信在了解了getopt命令以及如何shell脚本如何解析命令行选项和命令行参数后,就大概知道上面的grep命令内部是如何解析输入的参数了吧~

    二、结构化命令

    2.1 if-then的使用

    if [ condition ]
    then
        cmds
    elif [ condition ]
    then
        cmds
    else
        cmds
    fi
    
    # 或者
    
    if [ condition ]; then
        cmds
    elif [ condition ]
    then
        cmds
    else
        cmds
    fi
    

    方括号两边各有一个空格,必须加上,否则报错;方括号内可进行数值比较、字符串比较、文件比较。

    2.1.1 数值比较
    比较 描述
    n1 -eq n2 equal
    n1 -ge n2 great or equal
    n1 -gt n2 great than
    n1 -le n2 less or equal
    n1 -lt n2 less than
    n1 -ne n2 not equal
    2.1.2 字符串比较
    比较 描述
    str1 = str2 str1是否等于str2
    str1 != str2 str1是否不等于str2
    str1 < str2 str1小于str2
    str1 > str2 str1大于str2
    -n str1 str1的长度是否不为0
    -z str1 str1的长度是否为0

    关于字符串大于、小于的比较,我们都知道shell里头有重定向符 > ,因此在做大小比较时需要添加转义,如:

    if [ $str1 \> $str2 ]
    

    是不是很麻烦?此时我们可以用if-then的高级特性,双方括号[[ expression ]],那双方括号高级在哪里呢?除了不需要再做转义之外,还可以在双方括号里头写匹配模式,如:

    if [[ $USER == r*ky ]]
    

    PS:忘记单方括号的使用吧,直接用双方括号!

    2.1.3 文件比较

    文件的比较在shell里头用得非常非常的多,也非常的有用!!

    比较 描述
    -d file 检查file是否存在且是一个目录
    -e file 检查file是否存在
    -f file 检查file是否存在且是文件
    -r file 检查file是否存在且可读
    -w file 检查file是否存在且可写
    -x file 检查file是否存在且可执行
    file1 -nt file2 equal
    file1 -ot file2 equal
    2.1.4 复合条件
    if [ condition1 ] && [ condition2 ]
    if [ condition1 ] || [ condition2 ]
    

    2.2 case的使用

    case variable in
    pattern1 | pattern2) cmds1;;
    pattern3) cmds2;;
    *) default cmds;;
    esac
    

    2.3 for循环的使用

    for var in list
    do
        cmds
    done
    

    2.4 while的使用

    while conditions
    do
        cmds
    done
    
    eg:
    var1=10
    while [ $var1 -gt 0 ]
    do
        echo $var1
        var1=$[ $var1-1 ]
    done
    

    2.5 until的使用

    until conditions
    do
        cmds
    done
    

    2.6 break命令

    while [ $var1 -lt 10 ] 
    do 
        if [ $var1 -eq 5 ] 
        then 
            break 
        fi 
            echo "Iteration: $var1" 
            var1=$[ $var1 + 1 ] 
    done
    

    2.7 continue命令

    for var1 in 1 2 3 4 5 6 7 8 9 10 
    do 
        if [ $var1 -eq 5 ] 
        then 
            continue 
        fi 
            echo "Iteration number: $var1" 
    done 
    

    三、好用命令行工具集

    $ tr            
    $ sed
    $ awk
    $ cut
    $ find 
    $ grep
    

    相关文章

      网友评论

          本文标题:Shell编程极简入门

          本文链接:https://www.haomeiwen.com/subject/evsmsltx.html