2018-8-28-bash数组与字符串处理

在上次的课程中咱们讲述了函数和case语句,case语句的语法格式为:python

case $VARIABLE in
PAT1)
    分支1
    ;;
PAT2)
    分支2
    ;;
*)
    分支n
    ;;
esac

case是一个多分支的判断语句,与多个值进行比较时,case是比if语句要好用的多,在这里的PAT可以使用GLOB通配符,以及|为或者之意,而若是作非字符串的等值比较时,则case语句就没法取代。linux

紧接着又讲到了函数,函数只能是被调用才能被执行,而给定函数名则便可进行调用,可接受参数,使用$1$2等位置变量进行声明,函数的组成目的是可以实现结构化编程,同时还能完成代码重用,函数的使用可引入了新的上下文,可以使用局部变量,它的生命周期是当函数的生命周期结束,则会提早自动销毁,那么其函数的定义格式为:编程

function f_name {
    函数体
}

f_name() {
    函数体
}

局部变量:local VARIABLE

1、数组

接下来咱们开始讲数组,咱们的程序是由指令和数据组成,对于bash来说,指令就是编程当中的语法关键字,加上各类能调用的系统命令及定义的函数等均可以理解为指令,而数据对于bash来说,存放在变量和文件,当咱们要存储多个类型相同的数据时,就可使用一个叫数组的东西用来存储。数组

因此说数组和变量同样,只不过不一样的是存储多个元素连续的内存空间,而变量是存储单个元素的内存空间。bash

数组也有数组名,且整个数组只有一个名字,它也是指向连续内存空间的开始处的起始位置,数组当中存储多个元素,若是想引用某个特定元素的话,就要使用另一种机制找到该元素,那么这时候就要使用数组中的下标,也称为索引机制。dom

最开始,也就是第一个元素,咱们用0来进行表示,然后咱们就使用1,2,3,4分别来表示。ide

数组:
    程序=指令+数据
        指令:
        数据:变量、文件;

    变量:存储单个元素的内存空间;
    数组:存储多个元素的连续的内存空间;
        数组名:整个数组只有一个名字;
        数组索引:编号从0开始;
            数组名[索引]

以上数组名[索引]这种方式就能够引用数组中的元素,对于bash而言,其真正使用的格式为:${ARRAY_NAME[INDEX]},这里须要注意的是,花括号可不可以省略,由于去掉了花括号,$ARRAY_NAME就会被认为这是个变量,而并不会理解为这是一个数组的索引。因此对于数组以及数组的元素而言,则必须加上{}函数

对于bash-4.0之后的版本,对于数组的索引也能够支持自定义的索引格式,也称之为稀疏格式,不只是0,1,2,...定义的数字格式。而这类数组咱们就称之为关联数组工具

注意:bash-4及以后的版本,支持自定义索引格式,而不只是0,1,2,... 所定义的数字格式。
    此类数组称之为"关联数组";

1.1 声明、赋值及引用数组的方式

对于数组来说,是须要事先进行声明的,若是不声明,极可能会看成单个变量来使用。post

声明数组:
    declare -a NAME: 声明索引数组;
    declare -A NAME: 声明关联数组;

以上就是数组声明的两种方式,那么数组就是在内存空间当中拥有多个连续的空间且分别可以存储多个连续的数据,每一个数据都是独立的单元,只是没有独立的名称,须要用数组名称来来进行引用,但为了引用每个元素,咱们使用下标来进行,可理解为数组的标识,而下标可有两种方式,第一种是以数值的形式,咱们称之为索引数组,第二种使用的是字符串来进行的下标,咱们称之为关联数组。

而使用数组时,如何向数组中的元素进行赋值方法有不少种。

数组中元素的赋值方式:
    (1) 一次只赋值一个元素:
        ARRAY_NAME[INDEX]=VALUE

    (2) 一次赋值所有元素:
        ARRAY_NAME=("VAL1", "VAL2", "VAL3" ...)

    (3) 只赋值特定元素:
        ARRAY_NAME=([0]="VAL1", [3]="VAL5")

        注意:bash支持稀疏格式的数组;

    (4) read -a ARRAY_NAME

示例:

# animals[0]=pig
# animals[1]=bear
# echo ${animals[0]}
pig

这就是数组元素赋值,无需事先声明,这种一次只是赋值一个元素,若是echo回显没有使用下标的话,默认显示为0元素的数值。

引用数组中的元素:${ARRAY_NAME[INDEX]}
    注意:引用时,只给数组名,表示引用下标为0的元素;

这是第一种赋值方式,那么第二种一次赋值多个元素的方式为:

# weekdays=("Monday" "Tuesday" "Wednesday" "Tourday")
# echo ${weekdays[1]}
Tuesday

第三种是是赋值特定的,就是能够在挑选元素赋值,就是稀疏格式的数组,所谓稀疏格式就是:每一个元素的那个值都不必定会存在。

# sword=([0]="Yitian" [3]="Tudong" [5]="Gelu")
# echo ${sword[1]}

# echo ${sword[3]}
Tudong

最后就是使用read -a来给数组进行赋值,自动赋值给多个元素。

# read -a jianghu
Yuebuqun Dongfangbubai Linghuchong Qianchaotaijian
# echo ${jianghu[1]}
Dongfangbubai

若是咱们想使用关联数组的话,示例以下:

# world[us]="america"
# echo "${world[us]}"
america
# world[uk]="United Kingdom"
# echo "${world[uk]}"
United Kingdom

咱们本身直接给出字符串下标看成索引,来实现数组中的元素也是能够的,而这种就叫作关联数组。

那么引用数组中的元素咱们也展现过了,为${ARRAY_NAME[INDEX]},须要注意的是,引用只给出数组名的话,会表示引用的为下标为0的元素。

引用数组中的元素:${ARRAY_NAME[INDEX]}
    注意:引用时,只给数组名,表示引用下标为0的元素;

另外咱们也能够引用脚本时的参数同样,来引用数组中的全部元素及个数,说白了就是引用数组的长度。

数组的长度(数组中元素的个数)
    ${#ARRAY_NAME[*]}
    ${#ARRAY_NAME[@]}

示例:生成10个随机数,并找出其中的最大值和最小值。

#!/bin/bash
#

declare -a rand
declare -i max=0

for i in {0..9}; do
    rand[$i]=$RANDOM
    echo "${rand[$i]}"
    [ ${rand[$i]} -gt $max ] && max=${rand[$i]}
done

echo "Max:$max"

示例:定义一个数组,数组中元素是/var/log/目录下全部以.log结尾的文件;统计其下标为偶数的文件中的行数之和。

#!/bin/bash
#

declare -a files
files=(/var/log/*.log)

declare -i lines=0

for i in $(seq 0 $[${#files[*]}-1]); do
    if [ $[$i%2] -eq 0 ]; then
        let lines+=$(wc -l ${files[$i]} | cut -d ' ' -f1)
    fi
done

echo "Lines: $lines."

引用数组中的元素个数咱们刚才讲过了,但若是要引用数组中的全部元素。

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

那么咱们接下来开始讲述一下数组元素切片,那个切片的目的就是能够返回有限的几个元素。
示例:

# files=(/etc/[Pp]*)
# echo "${files[*]}"
/etc/PackageKit /etc/pam.d /etc/passwd /etc/passwd- /etc/pbm2ppa.conf /etc/pinforc /etc/pkcs11 /etc/pki /etc/plymouth /etc/pm /etc/pnm2ppa.conf /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/pulse /etc/purple /etc/python

咱们能够保留某些个元素,而切片是用偏移量offset,意味这偏移过去几个元素并取几个元素number

数组元素切片:${ARRAY_NAME[@]:offset:number}
    offset:要路过的元素个数;
    number:要取出的元素个数;省略number时,表示偏移量以后的全部元素;

示例:

# echo ${files[@]:3:4}
/etc/passwd- /etc/pbm2ppa.conf /etc/pinforc /etc/pkcs11
# echo ${files[@]:3}
/etc/passwd- /etc/pbm2ppa.conf /etc/pinforc /etc/pkcs11 /etc/pki /etc/plymouth /etc/pm /etc/pnm2ppa.conf /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/pulse /etc/purple /etc/python

对于数组而言,咱们还能够向数组中追加元素。给明元素下标,可是若是不知道数组有多少元素的话,就要引用数组的长度。

向非稀疏格式数组中追加元素:
    ARRAY_NAME[${#ARRAY_NAME[*]}]=
# animals[${#animals[*]}]=admin
# echo "${animals[@]}"
pig dog admin

那么删除数组中的某个元素的话咱们使用unset指令,与以前的撤销变量差很少,只不过不一样的是撤销变量的话须要使用$,并非撤销元素中的值而已,而在数组中就直接输入那个数组中的那个元素就能够。

删除数组中的某元素:
    unset ARRAY[INDEX]
# unset animals[0]
# echo "${animals[@]}"
dog  admin

刚才讲到过关联数组是什么,关联数组是以用字符为下标的形式来进行,需先声明其关联数组然后可进行赋值,那么它的声明方式及格式为:

关联数组:
    declare -A ARRAY_NAME
        ARRAY_NAME=([index_name1]="value1" [index_name2]="value" ...)

2、bash的内置字符串处理工具

接下来咱们就说一下经常使用的字符串处理工具,首先第一个就是字符串切片工具,与数组切片差很少,偏移过去取出那几个字符。

字符串切片:
    ${var:offset:number}
        取字符串的子串;
        取字符串的最右侧的几个字符:${var: -length}
            注意:冒号后必须有一个空白字符;
# name=Jerry
# echo ${name: 1}
erry
# echo ${name: 2}
rry
# echo ${name:2:2}
rr
# echo ${name: -4}
erry

还有一种就是基于模式去字符串的子串。

基于模式取子串:
    ${var#*word}:其中word是指定的分隔符;功能:自左而右,查找var变量所存储的字符串中,第一次出现的word分隔符,删除字符串开头至此分隔符之间的全部字符;
    ${var##word}:其中word是指定的分隔符;功能:自左而右,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除字符串开头至此分隔符之间的全部字符;
# mypath="/etc/init.d/functions"
# echo "${mypath#*/}"
etc/init.d/functions

# echo "${mypath##*/}"
functions

从以上两个实例看得出,第一个以/为分隔符,表示自左而右,查找第一次的分隔符/,把整个开头的分隔符到第一次出现的/将其删除。

而第二个实例就是自左而右找出最后一个/分隔符,将第一次开头的分隔符到最后一个开头的分隔符之间的范围将其删除,可做为取路径基名。

除了自左而右,咱们还能够自右而左。

${var%word}:其中word是指定的分隔符;功能:自右而左,查找var变量所存储的字符串中,第一次出现的word分隔符,删除此分隔符至字符串尾部之间全部的字符;
${var%%word}:其中word是指定的分隔符;功能:自右而左,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除此分隔符至字符串尾部之间的全部字符;
mypath="/etc/init.d/functions"
${mypath%/*}:/etc/init.d

url=http://www.china.org:80
    ${url##:*}
    ${url%%:*}

那么接下来介绍的是如何进行查找及替换。

查找替换:
    ${var/PATTERN/SUBSTI}:查找var所表示的字符串中,第一次被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;
    ${var//PATTERN/SUBSTI}:查找var所表示的字符串中,全部被PATTERN所匹配到的字符串,并将其所有其替换为SUBSTI所表示的字符串;

    ${var/#PATTERN/SUBSTI}:查找var所表示的字符中,行首被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;
    ${var/%PATTERN/SUBSTI}:查找var所表示的字符中,行尾被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串;

    注意:PATTERN中使用的glob风格和通配符;
# userinfo="root:x:0:0:root admin:/root:/bin/chroot"
# echo "${userinfo/root/ROOT}"
ROOT:x:0:0:root admin:/root:/bin/chroot
# echo "${userinfo//root/ROOT}"
ROOT:x:0:0:ROOT admin:/ROOT:/bin/chROOT
# echo "${userinfo/#r??t/ROOT}"
ROOT:x:0:0:root admin:/root:/bin/chroot
# echo "${userinfo/%r??t/ROOT}"
root:x:0:0:root admin:/root:/bin/chROOT

除了查找及替换外,还有一个就是查找及删除。

${var/PATTERN}:以PATTERN为模式查找var字符串中第一次匹配,并删除之;
${var//PATTERN}:
${var/#PATTERN}:
${var/%PATTERN}:

那么下一个介绍的就是字符大小写转换。

字符大小写转换:
    ${var^^}:把var中的全部小写字符转换为大写;
    ${var,,}:把var中的全部大写字符转换为小写;

最后一个介绍的是变量赋值。

变量赋值:
    ${var:-VALUE}:若是var变量为空,或未设置,那么返回VALUE;不然,则返回var变量的值;
    ${var:=VALUE}:若是var变量为空,或未设置,那么返回VALUE,并将VALUE赋值给var变量;不然,则返回var变量的值;
    ${var:+VALUE}:若是var变量为空,则返回VALUE;
    ${var:?ERROR_INFO}:若是var为空,或未设置,那么返回ERROR_INFO为错误提示;不然,返回var的值;

第一个就是若是这个变量自己有值,就返回本身自己的值,而若是这个变量没值,就返回后面所定义的值。

# echo ${hi:-world}
world
# hi="america"
# echo ${hi:-world}
america

那么第二个就是若是这个变量的值为空的话,就将这个值赋予给这个变量,否则的话就返回这个变量的定义的值。

# echo $hello

# echo "${hello:=world}"
world
# echo $hello
world

第三个就是若是var不空,则返回VALUE

# unset hello
# echo $hello

# echo "${hello:+worlds}"

# hello=world
# echo "${hello:+worlds}"
worlds

以上就是bash内置的字符串处理工具,咱们如今示例如下脚本案例,来完成以下功能。

练习:写一个脚本,完成以下功能:
    (1) 提示用户输入一个可执行命令的名称;
    (2) 获取命令所依赖的全部库文件列表;
    (3) 复制命令至某个目标目录(例如/mnt/sysroot)下的对应路径中;
        bash, /bin/bash ==> /mnt/sysroot/bin/bash
    (4) 复制命令依赖到的全部库文件至目标目录下的所对应的路径下;
        /lib64/ld-linux-x6464.so.2 ==> /mnt/sysroot/lib64/ld-linux-x8664.so.2

    进一步:
        每次复制完成一个命令后,不要退出,而是提示用户继续输入要复制的其它命令,并重复完成如上所描述的功能;直到用户输入"quit"退出脚本;
写一个脚本:
    ping命令去查看192.168.1.1-192.168.67.1范围内的全部主机是否在线;在线显示为up,不在线显示为down,分别统计在线主机,及不在线的主机数;

    分别使用for, while, until循环实现;
#!/bin/bash
#
declare -i uphosts=0
declare -i downhosts=0

for i in {1..67}; do
    if ping -W1 -c1 192.168.$i.1 &> /dev/null;then
        echo "192.168.$i.1 is up."
        let uphosts+=1
    else
        echo "192.168.$i.1 is down."
        let downhosts+=1
    fi
done

echo "uphosts is $uphosts."
echo "downhosts is $downhosts."
#!/bin/bash
#
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1

hostping() {
    if ping -W1 -c1 $1 &> /dev/null;then
        echo "$1 is up."
        return 0
    else
        echo "$1 is down."
        return 1
    fi
}

while [ $i -le 67 ]; do
    hostping 192.168.$i.1
    [ $? -eq 0 ] && let uphosts+=1 || let downhosts+=1
    let i++
done

echo "uphosts is $uphosts."
echo "downhosts is $downhosts."
相关文章
相关标签/搜索