笔记·Shell基础知识

脚本调试
    #bash -n /path/to/some_script        #只检测,不真正执行;
    #bash -x /path/to/some_script        #调试并执行  


变量声明和赋值
    name=value
    declare -x name=value
    export name=value
    TITLE+=:wang        #变量追加    


只读变量
    declare -r name
    readonly name  

 
位置变量
    $1, $2, ... 对应第1个、第2个等参数,shift [n]换位置
    $0 命令本身,包括路径
    $* 传递给脚本的所有参数,全部参数合为一个字符串
    $@ 传递给脚本的所有参数,每个参数为独立字符串
    $# 传递给脚本的参数的个数
    注意:$@ $* 只在被双引号包起来的时候才会有差异
        #set --        #清空所有位置变量


变量引用:${var_name},$var_name
    #cat /proc/1235/environ 查看进程的环境变量:


脚本安全和set:
    -u 在扩展一个没有设置的变量时,显示错误信息, 等同set -o nounset
    -e 如果一个命令返回一个非0退出状态值(失败)就退出, 等同set -o errexit


格式输出:
    printf "指定的格式" "文本1" ”文本2“……
        %s         字符串
        %d,%i     十进制整数
        %f         浮点格式
        %#s 中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,- 表示左对齐
        %03d 表示3位宽度,不足前面用0补全,超出位数原样输出
        %.2f 中的2表示小数点后显示的小数位数
        范例:
            #printf "%s\n" 1 2 3 4
            #printf "%s %s\n" 1 2 3 4
            #printf " (%s) " 1 2 3 4;echo ""
            #printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50


算数运算:
    (1) let var=算术表达式
    (2) ((var=算术表达式)) 和上面等价
    (3) var=$[算术表达式]
    (4) var=$((算术表达式))
    (5) var=$(expr arg1 arg2 arg3 ...)
    (6) declare -i var = 数值
    (7) echo '算术表达式' | bc
    
    #echo $[$RANDOM%50]        #生成随机数
    #let i=10*2
    #((j=i+10))
    #let i+=20 #相当于let i=i+20
    #echo "scale=3;20/3"|bc
    #expr 2 \* 3      

     
逻辑运算:
    与运算
    或运算
    非运算
    异或运算


条件测试:
    test EXPRESSION
    [ EXPRESSION ] #和test 等价,建议使用 [ ]
    [[ EXPRESSION ]] 相关于增强版的 [ ], 支持[]的用法,且支持扩展正则表达式和通配符


变量测试:
    #注意 [ ] 需要空格,否则会报下面错误:-bash: [-v: command not found
    [ -v NAME ]        #判断 NAME 变量是否定义
    [ -R NAME ]        #判断 NAME 变量是否定义并且是名称引用,bash 4.4新特性


数值测试:
    -eq 是否等于
    -ne 是否不等于
    -gt 是否大于
    -ge 是否大于等于
    -lt 是否小于
    -le 是否小于等于
    算术表达式比较:== != <= >= < >


字符串测试:
    test和[ ]用法:
        -z STRING     字符串是否为空,没定义或空为真,不空为假,
        -n STRING     字符串是否不空,不空为真,空为假
        STRING         同上
        STRING1 = STRING2     是否等于,注意 = 前后有空格
        STRING1 != STRING2     是否不等于
        >     ascii码是否大于ascii码
        <     是否小于

    [[ expression ]]用法:
        ==     左侧字符串是否和右侧的PATTERN相同,注意:此表达式用于[[ ]]中,PATTERN为通配符
        =~     左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配,注意: 此表达式用于[[ ]]中;扩展的正则表达式
        #结论:[[ == ]] == 右侧的 * 做为通配符,不要加“”,只想做为*, 需要加“” 或转义
        #[[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]        #判断成绩是否理想
        #[[ "$FILE" == *.log ]]                    #判断文件后缀
        #[[ "$FILE" == *.log ]]
        #[[ "$N" =~ ^[0-9]+$ ]]                    #判断是否为非负整数
        #[[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]    #判断ip是否合法


文件测试:
    存在性测试:
        -a FILE:同 -e
        -e FILE: 文件存在性测试,存在为真,否则为假        #[ ! -e /etc/issue ]
        -b FILE:是否存在且为块设备文件
        -c FILE:是否存在且为字符设备文件
        -d FILE:是否存在且为目录文件
        -f FILE:是否存在且为普通文件
        -h FILE 或 -L FILE:存在且为符号链接文件
        -p FILE:是否存在且为命名管道文件
        -S FILE:是否存在且为套接字文件

    文件权限测试:
        -r FILE:是否存在且可读
        -w FILE: 是否存在且可写
        -x FILE: 是否存在且可执行
        -u FILE:是否存在且拥有suid权限
        -g FILE:是否存在且拥有sgid权限
        -k FILE:是否存在且拥有sticky权限

    文件属性测试
        -s FILE #是否存在且非空
        -t fd #fd 文件描述符是否在某终端已经打开
        -N FILE #文件自从上一次被读取之后是否被修改过
        -O FILE #当前有效用户是否为文件属主
        -G FILE #当前有效用户是否为文件属组
        FILE1 -ef FILE2 #FILE1是否是FILE2的硬链接
        FILE1 -nt FILE2 #FILE1是否新于FILE2(mtime)
        FILE1 -ot FILE2 #FILE1是否旧于FILE2


关于()和{}:
    ( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境帮助参看:man bash 搜索(list)
    { list; } 不会启子shell, 在当前shell中运行,会影响当前shell环境帮助参看:man bash 搜索{ list; }


组合测试条件:
    [ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
    [ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
    [ ! EXPRESSION ] 取反
    COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN;如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2
    COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE;如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2
    ! COMMAND #非,取反


read命令:
    -p 指定要显示的提示
    -s 静默输入,一般用于密码
    -n N 指定输入的字符长度N
    -d '字符' 输入结束符
    -t N TIMEOUT为N秒
        #read -p "Please input your name: " NAME
        #read x y z <<< "I love you"

bash shell:        
    profile类:
        全局:对所有用户有效
            /etc/profile
            /etc/profile.d/*.sh
        个人:仅对当前用户有效
            ~/.bash_profile
        功能:定义环境变量,运行命令或脚本

    bashrc类:
        全局:对所有用户有效
            /etc/bashrc
        个人:仅对当前用户有效
            ~/.bashrc 
        功能:定义本地变量,定义命令别名

    交互式登录shell进程:
        /etc/profile-->/etc/profile.d/*-->~/.bash_profile-->~/.bashrc-->/etc/bashrc

    非交互式登录shell进程:
        ~/.bashrc-->/etc/bashrc-->/etc/profile.d/*


流程控制:
    (1)选择执行 if 语句:
        if 判断条件1; then
            条件1为真的分支代码
        elif 判断条件2; then
            条件2为真的分支代码
        elif 判断条件3; then
            条件3为真的分支代码
        ...
        else
            以上条件都为假的分支代码
        fi
    (2)条件判断 case 语句:
        case $INPUT in
        y|yes)
            echo "You input is YES"
            ;;
        n|no)
            echo "You input is NO"
            ;;
        *)
            echo "Input fales,please input yes or no!"
        esac
    (3)循环for
        for 变量名 in 列表
        do
            循环体
        done
        范例1:
            #sum=0;for i in {1..100};do let sum+=i;done ;echo sum=$sum
            #seq -s+ 1 2 100| bc
            #echo {1..100..2}|tr ' ' + | bc
        范例2:
            for i in {1..10};do
                useradd user$i
                PASS=`cat /dev/urandom | tr -dc '[:alnum:]' |head -c12`
                echo $PASS | passwd --stdin user$i &> /dev/null
                echo user$i:$PASS >> /data/user.log
                echo "user$i is created"
            done
    (4)循环while            
        无线循环范例:
            while true; do
                循环体
            done
            while : ; do
                循环体
            done
        范例2:
            WARNING=10
            touch deny_hosts.txt
            while true;do
                ss -nt | sed -nr '1!s#.* ([0-9.]+):[0-9]+ *#\1#p'|sort |uniq -c|sort |
                while read count ip;do
                    if [ $count -gt $WARNING ];then
                        echo $ip is deny
                        grep -q "$ip" deny_hosts.txt || { echo $ip >> deny_hosts.txt;iptables -A INPUT -s $ip -j REJECT; }
                    fi
                done
                sleep 10
            done
    (5)循环until
        until CONDITION; do
            循环体
        done
        范例1:
            #sum=0;i=1;until ((i>100));do let sum+=i;let i++;done;echo $sum
    (6)循环控制continue:continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
        while CONDITION1; do
            CMD1
            ...
            if CONDITION2; then
                continue
            fi
            CMDn
            ...
        done
    (7)循环控制语句 break:break [N]:提前结束第N层整个循环,最内层为第1层
        while CONDITION1; do
            CMD1
            ...
            if CONDITION2; then
                break
            fi
            CMDn
            ...
        done
    (8)循环控制 shift:shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
        参量列表list一旦被移动,最左端的那个参数就从列表中删除。while循环遍历位置参量列表时,常用到shift
        范例1:
            #!/bin/bash
            #step through all the positional parameters
            until [ -z "$1" ]
            do
                echo "$1"
                shift
            done
            echo
            ./shfit.sh a b c d e f g h
    (9)while 特殊用法 while read:遍历文件或文本的每一行
            while read line; do
                循环体
            done < /PATH/FROM/SOMEFILE
    (10)循环与菜单 select
        select variable in list ;do
            循环体命令
        done


函数 function:
    定义函数:
        #语法一:
        func_name (){
        ...函数体...
        } 
        语法二:
        function func_name {
        ...函数体...
        }
        #语法三:
        function func_name () {
        ...函数体...
        }

    查看函数:
        #查看当前已定义的函数名
        declare -F
        #查看当前已定义的函数定义
        declare -f
        #查看指定当前已定义的函数名
        declare -f func_name
        #查看当前已定义的函数名定义
        declare -F func_name

    删除函数:
        unset func_name

    使用函数文件:
        . filename
        source filename

    函数返回值:
        默认取决于函数中执行的最后一条命令的退出状态码
        自定义退出状态码,其格式为:
            return 从函数中返回,用最后状态命令决定返回值
            return 0 无错误返回
            return 1-255 有错误返回

    函数参数:
        传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 ...
        在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量

    函数变量:
        普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
        环境变量:当前shell和子shell有效
        本地变量:函数的生命周期;函数结束时变量被自动销毁
            local NAME=VALUE
        注意:
            如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
            由于普通变量和局部变量会冲突,建议在函数中只使用本地变量

    函数递归:
        函数内部自已调用自已
        必须有结束函数的出口语句,防止死循环
        #!/bin/bash
        fact() {
            if [ $1 -eq 0 -o $1 -eq 1 ]; then
                echo 1
            else
                echo $[$1*$(fact $[$1-1])]
            fi
        }
        fact $1
        
        fork 炸弹:
            :(){ :|:& };:
            bomb() { bomb | bomb & }; bomb


脚本相关工具:
    信号捕捉trap:
        #进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
        trap '触发指令' 信号
        #忽略信号的操作
        trap '' 信号
        #恢复原信号的操作
        trap '-' 信号
        #列出自定义信号操作
        trap -p
        #当脚本退出时,执行finish函数
        trap finish EXIT

    创建临时文件 mktemp:
        mktemp 命令用于创建并显示临时文件,可避免冲突
            mktemp [OPTION]... [TEMPLATE]        #TEMPLATE: filenameXXX,X至少要出现三个
                -d #创建临时目录
                -p DIR或--tmpdir=DIR #指明临时文件所存放目录位置

    安装复制文件 install:
        install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合
            install [OPTION]... [-T] SOURCE DEST 单文件
            install [OPTION]... SOURCE... DIRECTORY
            install [OPTION]... -t DIRECTORY SOURCE...
            install [OPTION]... -d DIRECTORY... #创建空目录
                -m MODE,默认755
                -o OWNER
                -g GROUP
                -d DIRNAME 目录

    交互式转化批处理工具 expect
        expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
            -c:从命令行执行expect脚本,默认expect是交互地执行的
            -d:可以调试信息
        expect中相关命令:
            spawn 启动新的进程
            expect 从进程接收字符串
            send 用于向进程发送字符串
            interact 允许用户交互
            exp_continue 匹配多个字符串在执行动作后加此命令

        范例1:非交互式复制文件
            #!/usr/bin/expect
            spawn scp /etc/redhat-release 10.0.0.7:/data
            expect {
                "yes/no" { send "yes\n";exp_continue }
                "password" { send "magedu\n" }
            }
            expect eof

        范例2:自动登录
            #!/usr/bin/expect
            spawn ssh 10.0.0.7
            expect {
                "yes/no" { send "yes\n";exp_continue }
                "password" { send "magedu\n" }
            }
            interact


数组 array:
    声明数组:
        #普通数组可以不事先声明,直接使用
        declare -a ARRAY_NAME
        #关联数组必须先声明,再使用
        declare -A ARRAY_NAME

    数组赋值:
        ARRAY_NAME[INDEX]=VALUE
            weekdays[0]="Sunday"
            weekdays[4]="Thursday"
        ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
            title=("ceo" "coo" "cto")
            num=({0..10})
            alpha=({a..g})
            file=( *.sh )    
        ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
        read -a ARRAY    

    显示所有数组:
        declare -a

    引用数组:
        ${ARRAY_NAME[INDEX]}        #如果省略[INDEX]表示引用下标为0的元素
        ${ARRAY_NAME[*]}            #引用所有元素
        ${ARRAY_NAME[@]}            #同上
        ${#ARRAY_NAME[*]}            #数组中元素个数
        ${#ARRAY_NAME[@]}            #同上

    删除数组:
        unset ARRAY[INDEX]
        unset ARRAY

    数组数据处理:
        数组切片:
            ${ARRAY[@]:offset:number}
                offset #要跳过的元素个数
                number #要取出的元素个数
            {ARRAY[@]:offset}        #取偏移量之后的所有元素
        向数组中追加元素:
            ARRAY[${#ARRAY[*]}]=value
            ARRAY[${#ARRAY[@]}]=value

    关联数组:
        declare -A ARRAY_NAME
        ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)


字符串处理:
    字符串切片:
        1 基于偏移量取字符串:
            #返回字符串变量var的字符的长度,一个汉字算一个字符
                ${#var}
            #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
                ${var:offset}
            #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
                ${var:offset:number}
            #取字符串的最右侧几个字符,取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
                ${var: -length}
            #从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
                ${var:offset:-length}
            #先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意:-length前空格,并且length必须大于offset
                ${var: -length:-offset}
        2 基于模式取子串
            #其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
                ${var#*word}
            #同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模式,以最后一个word为界删左留右
                ${var##*word}
            #其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word,删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以从右向左的第一个word为界删右留左
                ${var%word*}
            #同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符,即贪婪模式,以从右向左的最后一个word为界删右留左
                ${var%%word*}

    查找替换:
        #查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
            ${var/pattern/substr}
        #查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
            ${var//pattern/substr}
        #查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
            ${var/#pattern/substr}
        #查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
            ${var/%pattern/substr}

    查找并删除:
        #删除var表示的字符串中第一次被pattern匹配到的字符串
            ${var/pattern}
        删除var表示的字符串中所有被pattern匹配到的字符串
            ${var//pattern}
        删除var表示的字符串中所有以pattern为行首匹配到的字符串
            ${var/#pattern}
        删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
            ${var/%pattern}

    字符大小写转换:
        #把var中的所有小写字母转换为大写
            ${var^^}
        #把var中的所有大写字母转换为小写
            ${var,,}


高级变量:
    有类型变量:
        declare [选项] 变量名
        -r 声明或显示只读变量
        -i 将变量定义为整型数
        -a 将变量定义为数组
        -A 将变量定义为关联数组
        -f 显示已定义的所有函数名及其内容
        -F 仅显示已定义的所有函数名
        -x 声明或显示环境变量和函数,相当于export
        -l 声明变量为小写字母 declare -l var=UPPER
        -u 声明变量为大写字母 declare -u var=lower
        -n make NAME a reference to the variable named by its value
        

posted @ 2022-07-25 17:45  Krill_ss  阅读(76)  评论(0编辑  收藏  举报