Linux随笔10-Ubuntu网络配置、非交互式远程主机登录以及shell中的数组应用(冒泡排序数组中的元素)

1. Ubuntu系统网络配置总结

Ubuntu系统的网络相关配置,以Ubuntu20.04为例,配置主机名、网卡名称、以及网卡接口IP地址等三块内容。

1.1. 配置主机名

修改Ubuntu的主机名称,可以通过两种方式:修改配置文件/etc/hostname和命令行hostnamectl。分别介绍如下:

  • 修改配置文件’/etc/hostname’

    查看配置文件的内容,具体如下所示:

    root@ubuntu20u04:~# cat /etc/hostname
    ubuntu20u04
    root@ubuntu20u04:~# 
    

    要修改主机名,可以将新主机名写入到这个文件中,替换旧主机名,然后重启系统即可。具体如下所示:

    root@ubuntu20u04:~# vim /etc/hostname
    root@ubuntu20u04:~# cat /etc/hostname
    Ubuntu20U04
    root@ubuntu20u04:~#
    root@ubuntu20u04:~# reboot
    Connection to ubuntu20u04 closed by remote host.
    Connection to ubuntu20u04 closed.
    

    等待重启过程完成,重新登录系统,产看主机名信息,具体如下所示:

    [root@localhost ~]# ssh ubuntu20u04
    Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-73-generic x86_64)
    Last login: Sun May 23 17:04:20 2021 from 192.168.122.1
    root@Ubuntu20U04:~# 
    

    上述就将主机名从ubuntu20u04修改为了Ubuntu20U04。

  • 通过hostnamectl命令

    通过命令行执行的效果如下所示:

    root@Ubuntu20U04:~# hostnamectl set-hostname ubuntu20u04.localhost
    root@Ubuntu20U04:~# exit
    logout
    Connection to ubuntu20u04 closed.
    [root@localhost ~]# ssh ubuntu20u04
    Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-73-generic x86_64)
    Last login: Sun May 23 17:29:32 2021 from 192.168.122.1
    root@ubuntu20u04:~# hostname
    ubuntu20u04.localhost
    root@ubuntu20u04:~# 
    

    上述即通过hostnamectl set-hostname命令设置系统主机名的过程,无需重启,退出重新登录即可让新主机名生效。

1.2. 配置网卡名称

查看系统默认的网卡名称以及信息,具体如下所示:

root@ubuntu20u04:~# ip link show 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:cc:fe:2d brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
    link/ether 02:42:a9:e7:5c:47 brd ff:ff:ff:ff:ff:ff
root@ubuntu20u04:~#

此时系统的默认网卡名称为enp1s0,要将这个网卡名称修改为eth0,就需要修改配置文件’/etc/default/grub’,然后重新生成grub配置文件并更新grub。具体过程如下所示:

  1. 修改/etc/default/grub配置文件的内容
    root@ubuntu20u04:~# vim /etc/default/grub
    # If you change this file, run 'update-grub' afterwards to update
    # /boot/grub/grub.cfg.
    # For full documentation of the options in this file, see:
    #   info -f grub -n 'Simple configuration'
    
    GRUB_DEFAULT=0
    GRUB_TIMEOUT_STYLE=hidden
    GRUB_TIMEOUT=0
    GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
    GRUB_CMDLINE_LINUX_DEFAULT=""
    GRUB_CMDLINE_LINUX=""
    

    将上述配置文件中的GRUB_CMDLINE_LINUX=""修改为GRUB_CMDLINE_LINUX="net.ifnames=0",具体如下所示:

    root@ubuntu20u04:~# vim /etc/default/grub
    # If you change this file, run 'update-grub' afterwards to update
    # /boot/grub/grub.cfg.
    # For full documentation of the options in this file, see:
    #   info -f grub -n 'Simple configuration'
    
    GRUB_DEFAULT=0
    GRUB_TIMEOUT_STYLE=hidden
    GRUB_TIMEOUT=0
    GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
    GRUB_CMDLINE_LINUX_DEFAULT=""
    GRUB_CMDLINE_LINUX="net.ifnames=0"
    

    上述就完成了配置文件修改操作。

  2. 重新生成grub配置文件

    如文件中的提示,修改了上述文件之后,应该执行update-grub这个命令。具体如下所示:

    root@ubuntu20u04:~# update-grub
    Sourcing file `/etc/default/grub'
    Sourcing file `/etc/default/grub.d/init-select.cfg'
    Generating grub configuration file ... 
    Found linux image: /boot/vmlinuz-5.4.0-73-generic
    Found initrd image: /boot/initrd.img-5.4.0-73-generic
    Found linux image: /boot/vmlinuz-5.4.0-72-generic
    Found initrd image: /boot/initrd.img-5.4.0-72-generic
    Found linux image: /boot/vmlinuz-5.4.0-71-generic
    Found initrd image: /boot/initrd.img-5.4.0-71-generic
    Found linux image: /boot/vmlinuz-5.4.0-70-generic
    Found initrd image: /boot/initrd.img-5.4.0-70-generic
    done
    root@ubuntu20u04:~# egrep 'net\.ifnames' /boot/grub/grub.cfg     >         linux   /vmlinuz-5.4.0-73-generic root=/dev/mapper/vg0-lv--0--root ro net.ifnames=0 
                    linux   /vmlinuz-5.4.0-73-generic root=/dev/mapper/vg0-lv--0--root ro net.ifnames=0 
                    linux   /vmlinuz-5.4.0-73-generic root=/dev/mapper/vg0-lv--0--root ro recovery nomodeset dis_ucode_ldr net.ifnames=0    >                 linux   /vmlinuz-5.4.0-72-generic root=/dev/mapper/vg0-lv--0--root ro net.ifnames=0 
                    linux   /vmlinuz-5.4.0-72-generic root=/dev/mapper/vg0-lv--0--root ro recovery nomodeset dis_ucode_ldr net.ifnames=0    >                 linux   /vmlinuz-5.4.0-71-generic root=/dev/mapper/vg0-lv--0--root ro net.ifnames=0 
                    linux   /vmlinuz-5.4.0-71-generic root=/dev/mapper/vg0-lv--0--root ro recovery nomodeset dis_ucode_ldr net.ifnames=0    >                 linux   /vmlinuz-5.4.0-70-generic root=/dev/mapper/vg0-lv--0--root ro net.ifnames=0 
                    linux   /vmlinuz-5.4.0-70-generic root=/dev/mapper/vg0-lv--0--root ro recovery nomodeset dis_ucode_ldr net.ifnames=0    > root@ubuntu20u04:~#
    

    上述输出中可以看出,已经修改完成了。

  3. 重启系统并查看网卡信息

    重启系统

    root@ubuntu20u04:~# reboot
    root@ubuntu20u04:~# Connection to ubuntu20u04 closed by remote host.
    Connection to ubuntu20u04 closed.
    

    由于此时系统的网卡配置文件的名称仍然是enp1s0,而不是eth0,所以此时eth0这个网卡并没有IP地址,所以无法从网络进行登录,在Virtual-Manager控制台中登录之后查看网卡信息如下:
    在这里插入图片描述
    从上图可见,网卡的名称确实改了,但是此时并没有IP地址。
    在控制台中执行如下命令设置一个临时IP地址,方便通过网络连接。具体如下所示:

    root@ubuntu20u04:~# ip addr add 192.168.122.30/24 dev eth0
    root@ubuntu20u04:~# ip link set eth0 up
    

    至此,就在控制台中给其添加了临时IP地址,通过网络连接即可。
    尝试修改原来的配置文件,将其改名,具体如下所示:

    root@ubuntu20u04:~# vim /etc/netplan/00-installer-config.yaml
    root@ubuntu20u04:~# cat /etc/netplan/00-installer-config.yaml
    # This is the network config written by 'subiquity'
    network:
      ethernets:
        eth0:
          dhcp4: false
          addresses:
          - 192.168.122.30/24
          - 10.0.0.7/16
          gateway4: 192.168.122.1
          nameservers:
            addresses: [127.0.0.53, 192.168.1.1, 192.168.18.1]
          routes:
          - to: 192.168.122.1
            via: 0.0.0.0
      version: 2
    root@ubuntu20u04:~# netplan apply
    root@ubuntu20u04:~# ip addr show
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
    2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
        link/ether 52:54:00:cc:fe:2d brd ff:ff:ff:ff:ff:ff
        inet 192.168.122.30/24 scope global eth0
           valid_lft forever preferred_lft forever
        inet 10.0.0.7/16 brd 10.0.255.255 scope global eth0
           valid_lft forever preferred_lft forever
        inet6 fe80::5054:ff:fecc:fe2d/64 scope link
           valid_lft forever preferred_lft forever    
    3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
        link/ether 02:42:01:ea:6f:be brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
           valid_lft forever preferred_lft forever
    root@ubuntu20u04:~#
    

    从上述结果中可见,将配置文件中的就网卡名修改为新的网卡名,然后执行netplan apply应用配置文件即可。
    从上述过程中可见,第3步,应该在重启之前完成,而不应该在重启之后完成,否则无法通过网路进行连接。重新生成配置文件,并更新配置文件之后,就应该立即将旧网卡配置文件的网卡名称修改为新的网卡名称,然后再重启系统即可实现新网卡名称的网络连接了。

1.3. 配置网卡IP地址

关于更多的Ubuntu20.04的网卡IP地址设置,参见上篇博客ubuntu20.04网卡IP地址设置的2.2这个章节。

2. 非交互式远程主机登录

通常在生产环境中,为了实现非交互式远程主机登录以及命令执行,都会给ssh配置基于密钥的认证。除了密钥验证实现非交互式远程主机登录之外,还可以使用如下两种方式进行非交互式远程主机登录。

2.1. 使用expect实现

该命令的也需要安装之后才能使用,在Ubuntu20.04中安装的命令为apt install expect,在CentOS7.6中安装的命令为yum instsall -y expect
expect是由tcl语言开发的,用于自动处理一些交互过程。这个过程可以写为expect脚本,具体如下所示:

expect脚本的开头第一行为#!/usr/bin/expect -f,其中-f选项的含义表示从脚本文件中读取expect脚本。

#!/usr/bin/expect -f


#--------------------------------------------------
# FileName    : remote_login.expect
# Author      : 六弦企鹅
# Contact     : 123456789
# Version     : v0.1
# Modify      : 2021-05-23
# Usage       : expect -f remote_login.expect
# Description : 通过expect脚本实现非交互远程主机登录
#--------------------------------------------------


set remote_host 192.168.122.2
set remote_user root
set remote_pass 123456
set timeout 10


spawn ssh ${remote_user}@${remote_host}

expect {
    "yes/no" {send "yes/n"; exp_continue}
    "password" {send "${remote_pass}\n"}
}

interact

上述脚本的执行结果如下所示:

root@ubuntu20u04:~/scripts# expect -f remote_login.expect 
spawn ssh root@192.168.122.2
root@192.168.122.2's password: 
Last login: Sun May 23 17:13:49 2021 from 192.168.122.30
[root@c7u6m1 ~]# 

上述就实现了非交互式远程主机登录。

2.2. 使用sshpass实现

使用sshpass实现远程主机登录,需要在系统上安装sshpass,在ubuntu20.04上安装的命令为apt install sshpass,在CentOS7.6上安装的命令为yum install -y sshpass。安装完成之后,可以使用该命令进行远程非交互式系统登录,并在远程主机上执行命令。该命令可以有3种方式提供密码,分别如下:

  • -e选项,此时需要将密码设置为环境变量SSHPASS的值,比如export SSHPASS=123456; sshpass -e ssh root@c7u6m1 hostname
  • -p password选项,此时直接在命令行中提供密码,比如sshpass -p 123456 ssh root@c7u6m1 hostname,这种方式会导致密码残留在命令历史中
  • -f pass_file选项,此时将密码记录在文件,将密码记录在密码文件的第一行即可。这种方式不会在命令行中记录密码信息。比如sshpass -f pass_file ssh root@c7u6m1 hostname

下面通过一个简单的脚本实现在远程主机上非交互执行命令,具体如下所示:

两台主机之间并未配置密钥验证,所以直接执行的时候需要输入密码。

root@ubuntu20u04:~/scripts/# ssh root@192.168.122.2 hostname
root@192.168.122.2's password:
c7u6m1

脚本实现如下所示:

#!/bin/bash


#--------------------------------------------------
# FileName    : remote_login.sh
# Author      : 六弦企鹅
# Contact     : 123456789
# Version     : v0.1
# Modify      : 2021-05-23
# Usage       : bash remote_login.sh
# Description : 利用sshpass实现非交互式登录远程主机并执行命令
#--------------------------------------------------

remote_user=root
remote_host=192.168.122.2
remote_pass=123456
remote_comd=hostname


sshpass -p ${remote_pass} ssh ${remote_user}@${remote_host} ${remote_comd}
sshpass -f ${pass_file} ssh ${remote_user}@${remote_host} ${remote_comd}
export SSHPASS=${remote_pass}; sshpass -e ssh ${remote_user}@${remote_host} ${remote_comd}

上述脚本的执行过程如下所示:

root@ubuntu20u04:~/scripts# bash remote_login.sh
c7u6m1
c7u6m1
c7u6m1
root@ubuntu20u04:~/scripts#

上述脚本中第二条命令中用到的密码文件的内容如下所示:

root@ubuntu20u04:~/scripts# cat pass_file
123456

上述脚本中使用了3种密码提供方式执行了相同的操作。以及对应的执行效果。

在上述脚本中,去掉sshpass中的ssh命令后面的remote_comd部分,就可以实现非交互远程登陆。

在命令行中的执行效果如下所示:

root@ubuntu20u04:~/scripts# sshpass -p 123456 ssh root@192.168.122.2
Last login: Sun May 23 14:57:18 2021 from 192.168.122.1
[root@c7u6m1 ~]# 

3. 数组在shell脚本中的应用示例

大多数Linux发行版的默认shell解释器都是bash,而bash也确实不负众望,提供了很好的命令行交互方式和体验,以及丰富的脚本特性。除了基本的环境变量、条件判断以及循环迭代等逻辑结构以及函数之外,这里提到的数组也是bash提供的一个很好的特性,但是其在日常使用中用的并不是很频繁。下面从数组的基本使用方式、数组变量两方面进行简要介绍。

3.1. 数组基本介绍

数组可以理解为一个容器变量,其中可以放置多个值,且各个值可以是不同类型(这一点不同于C/C++语言,要求数组中的元素必须是相同类型)。

3.1.1. 数组基本使用

bash解释器中提供的数组分为两种类型:

  • 索引数组(indexed array)
    索引数组是通过数字下标进行引用,通过小括号包含、空格分隔的各个值以赋值的方式创建;或者通过declare -a arr_name的方式进行预定义,然后再以赋值的方式进行创建,此时创建就不必是小括号括起来的空格分隔的各个值了,可以直接通过下标引用的方式进行赋值。具体如下所示:

    数组赋值采用小括号包含各个元素的形式,各个元素之间用空格分隔。具体如下所示:

    [root@localhost ~]# a=(1 'm' 'hello world')
    

    上述就通过赋值的方式构建了一个数组,其中包含3个元素,1个数字,1个字符,1个字符串。
    除了上述的形式之外,还可以通过declare -a arr_name定义数组名字,然后再通过对单个索引赋值的方式创建数组。此时可以不连续赋值的方式创建数组。

    [root@localhost ~]# declare -a arr 
    [root@localhost ~]# arr[0]=1
    [root@localhost ~]# arr[1]=2
    [root@localhost ~]# arr[2]=3
    [root@localhost ~]# echo ${arr[*]}
    1 2 3 
    [root@localhost ~]# 
    

    上述为连续赋值的方式创建数组,下面采用不连续赋值的方式创建数组:

    [root@localhost ~]# arr[6]=7
    [root@localhost ~]# arr[5]=6
    [root@localhost ~]# declare -a | egrep arr 
    declare -a arr=([0]="1" [1]="2" [2]="3" [5]="6" [6]="7")
    [root@localhost ~]# 
    

    从上述的输出中可以看出,预先定义数组名字之后,可以通过不连续赋值的方式创建数组。
    另外,通过命令declare -a命令可以列出当前bash环境中所有的数组。

  • 关联数组(associated array)
    而关联数组则是通过declare -A arr_name的方式创建,然后通过命名索引(而不是从0开始的正整数索引)的方式进行赋值和引用。具体如下所示:

    先定义关联数组变量名,然后通过命名索引引用关联数组中的元素。

    [root@localhost ~]# declare -A named_arr
    [root@localhost ~]# named_arr[name]='xiaowang'
    [root@localhost ~]# named_arr[age]=34
    [root@localhost ~]# echo ${named_arr[*]}
    xiaowang 34
    [root@localhost ~]# 
    [root@localhost ~]# declare -A | egrep named_arr
    declare -A named_arr=([name]="xiaowang" [age]="34" )
    [root@localhost ~]# 
    [root@localhost ~]# echo ${named_arr[name]}
    xiaowang
    [root@localhost ~]# echo ${named_arr[age]}
    34
    [root@localhost ~]# 
    

    上述即为关联数组的定义和引用方式。

接下来是简单的使用。

  • 部分或者全部引用数组中的元素

    下面对上面创建的数组中的元素进行引用,具体如下所示:

    [root@localhost ~]# echo ${a[*]}
    1 m hello world
    [root@localhost ~]# echo ${a[1]}
    m
    [root@localhost ~]# echo ${a[2]}
    hello world
    [root@localhost ~]#
    

    上述分别引用了数组中的全部元素,以及第2个和第3个元素。数组下标从0开始,所以下标1表示第二个元素。
    如果直接引用数组名,则会打印数组中的第一个元素,具体如下所示:

    [root@localhost ~]# echo $a
    1
    [root@localhost ~]# echo ${a[0]}
    1
    [root@localhost ~]#
    
  • 更改数组

    除了上述的值的引用之外,还可以通过指定的下标,给数组中的某个索引赋予新值。具体如下所示:

    [root@localhost ~]# a[0]=20
    [root@localhost ~]# echo ${a[*]}
    20 m hello world
    [root@localhost ~]# echo $a
    20
    [root@localhost ~]# 
    

    除了上述修改某个元素的方式之外,还可以利用+=操作将两个数组连接起来,具体如下所示:

    [root@localhost ~]# a+=(89, 2018)
    [root@localhost ~]# echo ${a[*]}
    20 m hello world 89, 2018
    [root@localhost ~]# a[3]=89
    [root@localhost ~]# echo ${a[*]}
    20 m hello world 89 2018
    [root@localhost ~]#
    

    上述就完成了对数组中某个下标所指向值的修改以及数组连接扩展。上述的${a[*]}的形式是将数组中的各个元素作为一个单一值来对待,如果要将各个元素分别对待,那么可以使用${a[@]}的形式。这个区别只有在for循环中应用的时候,才能看出差别。

  • 获取数组长度

    通过echo ${#arr_name[*]}或者echo ${#arr_name[@]}的形式获取数组的长度。具体如下所示:

    [root@localhost ~]# echo ${#named_arr[*]}
    2 
    [root@localhost ~]# echo ${#named_arr}
    0 
    [root@localhost ~]# echo ${#named_arr[@]}
    2 
    [root@localhost ~]# declare -A | egrep named
    declare -A named_arr=([name]="xiaowang" [age]="34" )
    [root@localhost ~]# 
    

    上述为求取关联数组的长度,即其中包含了几个元素。对于索引数组,效果是一样的,具体如下所示:

    [root@localhost ~]# echo ${#arr[*]}
    5
    [root@localhost ~]# echo ${#arr[@]}
    5
    [root@localhost ~]# echo ${#arr}
    1
    [root@localhost ~]# declare -a | egrep arr
    declare -a arr=([0]="1" [1]="2" [2]="3" [5]="6" [6]="7")
    [root@localhost ~]# 
    
  • 查看数组的索引下标

    要查看数组的索引下标分配情况,可以执行如下操作,形式类似于求取数组的长度,只不过把求取长度中的#号修改为!号。对于索引数组和关联数组都有效。下属中的*号可以替换为@符号。

    [root@localhost ~]# echo ${!arr[*]}
    0 1 2 5 6
    [root@localhost ~]# echo ${!named_arr[*]}
    name age
    [root@localhost ~]#
    
  • 删掉数组中的元素

    要删掉数组中的元素,可以使用unset命令,具体如下所示:

    [root@localhost ~]# unset arr[5]
    [root@localhost ~]# echo ${!arr[@]}
    0 1 2 6
    [root@localhost ~]# declare -a | egrep arr
    declare -a arr=([0]="1" [1]="2" [2]="3" [6]="7")
    [root@localhost ~]# unset named_arr[age]
    [root@localhost ~]# echo ${!named_arr[@]}
    name
    [root@localhost ~]# echo ${named_arr[@]}
    xiaowang
    [root@localhost ~]# declare -A | egrep named
    declare -A named_arr=([name]="xiaowang" )
    [root@localhost ~]#
    

上述及部分就完成了数组的创建、数组中元素引用以及修改的操作。接下来介绍一下数组中可以用的变量有哪些。
关于上述内容,更详细的解释,参见man bash的帮助手册,在其中搜索Arrays即可。

3.1.2. 关于展开(EXPANSION)

在上面的数组基本介绍中,其实已经使用到了花括号展开,比如引用数组中的所有元素${arr[*]},求取数组长度${#arr[@]},求取数组下标${!arr[@]}等几种形式。
而bash中总共支持7种类型的展开,分别为花括号展开(brace expansion)、波浪号展开(tilde expansion)、参数和变量展开(parameter and variable expansion)、命令替换(command substitution)、算数展开(arithmetic expansion)、单词分割(word splitting)以及路径展开(path expansion)共7类展开。
7类展开之间的先后顺序为花开括号展开->波浪展开, 参数和变量展开, 算数展开, 命令替换(按照从左到右的顺序)->单词分割->路径展开。
除此之外,在一些系统上还支持进程替换(process substitution),这个是与波浪号展开、参数和变量展开、算数展开和命令替换同时执行的。
关于花括号展开的更详细介绍,参见man bash的帮助手册,在其中搜索EXPANSION即可。

  1. 花括号展开(Brace Expansion)

    花括号展开用于生成任意的字符串,其形式为用花括号将一些列字符串或者表达式用英文半角逗号分隔,并按照从左到右的顺序进行展开。比如a{d,c,b}e展>开为ade, ace, abe
    {x..y[..incr]}形式的顺序表达式中,x,y可以是整数也可以是单一字符,而incr则表示可选的增量值,必须是整数。当x,y是整数的时候,表达式则按照增量incr将x和y之间的所有数列出来,可以在x,y的整数前面加上前缀0,以使其达到相同的宽度。注意,x,y必须是相同的类型;默认的incr值为1,-1也是可以的。
    花括号展开在其他展开之前执行,对于其他展开来说的特殊字符,都会保留在最终结果中。bash不会对展开上下文或者花括号中的文本执行语法解释。
    常用的示例如下:

    • mkdir /usr/local/src/bash/{old,new,dist,bugs}
    • chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}
      花括号展开与传统的sh解释器在一定程度上存在不兼容的问题,所以建议在bash中使用花括号展开。
  2. 波浪号展开(Tilde Expansion)

    如果一个词以未被引号括起来的~开头,且第一个斜线之前的所有前缀都没有被引号括起来,就被认为是波浪号前缀。此时波浪号后面,斜线前面的这个单词>,就被认为是登录用户名,此时这个波浪号前缀就被认为是这个用户的家目录,其就会被替代为该用户的HOME变量,比如~root代表root用户的家目录。另外,~+代表当前目录,效果等效于PWD这个环境变量,~-代表此前的旧的工作路径,效果等效于OLDPWD。具体如下所示:

    [root@localhost ~]# cd /tmp/
    [root@localhost tmp]# echo ~+
    /tmp
    [root@localhost tmp]# echo ~-
    /root
    [root@localhost tmp]# echo ~root
    /root
    [root@localhost tmp]#
    
  3. 参数和变量展开(Parameter & Variable Expansion)

    参数和变量展开的基本使用形式为${parameter},此时这个表达式代表parameter的值,当parameter是超过1位的位置参数时,或者当parameter后面跟着非该变量或者参数构成部分的字符时,都需要将parameter用花括号括起来。
    如果parameter前面第一个字符是感叹号!,那么此时parameter并不是变量名称引用(nameref),此时代表间接变量(it introduces a level of variable indirection)。下面是一些使用范式的示例:

    • ${parameter:-word}:使用parameter的默认值。如果parameter被设置为空或者未设置(null),此时这个表达式的整体结果就为word。否则的话,这个表达式的结果就为parameter变量或者参数的内容。

      具体示例如下所示:

      [root@localhost tmp]# str='hello world'
      [root@localhost tmp]# echo ${str:-china}
      hello world
      [root@localhost tmp]# str=
      [root@localhost tmp]# echo ${str:-china}
      china
      [root@localhost tmp]# unset str 
      [root@localhost tmp]# echo ${str:-china}
      china
      [root@localhost tmp]#
      
    • ${parameter:=word}:分配默认值。如果parameter未设置或者被设置为空,那么就用word的值赋值给parameter,此时表达式的值为word;否则,保留parameter的值不变,表达式的结果为parameter的内容。

      具体如下所示:

      [root@localhost tmp]# echo $str 
        
      [root@localhost tmp]# echo ${str:=chine}
      chine
      [root@localhost tmp]# echo $str
      chine
      [root@localhost tmp]# echo ${str:=america}
      chine
      [root@localhost tmp]# echo $str
      chine
      [root@localhost tmp]#
      [root@localhost tmp]# str=
      [root@localhost tmp]# echo ${str:=america}
      america
      [root@localhost tmp]# echo $str
      america
      [root@localhost tmp]# unset str
      [root@localhost tmp]# echo ${str:=america}
      america
      [root@localhost tmp]# echo $str
      america
      [root@localhost tmp]# 
      
    • ${parameter:?word}:如果parameter未设置或者被设置为空,则显示错误,并且将word的内容写入到标准错误输出中。否则,这个表达式的值就是parameter的内容。

      具体如下所示:

      [root@localhost tmp]# unset str 
      [root@localhost tmp]# echo ${str:?error_info}
      -bash: str: error_info
      [root@localhost tmp]# str=
      [root@localhost tmp]# echo ${str:?error_info}
      -bash: str: error_info
      [root@localhost tmp]# str='hello world'
      [root@localhost tmp]# echo ${str:?error_info}
      hello world
      [root@localhost tmp]# str=
      [root@localhost tmp]# echo ${str:?error_info}
      -bash: str: error_info
      [root@localhost tmp]# echo $str 
        
      [root@localhost tmp]#
      
    • ${parameter:+word}:使用可选值word的内容。如果parameter为空或者未设置,那么不做替换操作;否则使用word的内容替换parameter的内容。

      具体如下所示:

      [root@localhost tmp]# unset str 
      [root@localhost tmp]# echo ${str:+hello} 
        
      [root@localhost tmp]# str=
      [root@localhost tmp]# echo ${str:+hello} 
        
      [root@localhost tmp]# str='america'
      [root@localhost tmp]# echo ${str:+hello}
      hello
      [root@localhost tmp]#
      
    • ${parameter:offset} | ${parameter:offset:length}:子字符串展开。把parameter的内容从offset指定的偏移位置展开length指定的长度的片段。>如果parameter为’@’,此时被索引的数组下标相当于’@‘或者’*’,或者关联数组的名字索引。如果忽略length这个参数值,那么将会展开到末尾。
      如果offset的值为负数,那么此时偏移值计算的起始位置就是从parameter内容的末尾开始,如果length值是负数,表示从parameter的值末尾开始的偏移个数,而不是字符个数。注意负数偏移值与其前面的’:‘之间必须至少有1个空格,以区分’:-'这个特殊符号。

      具体示例如下所示:

      [root@localhost tmp]# string=01234567890abcdefgh
      [root@localhost tmp]# echo ${string:7}
      7890abcdefgh
      [root@localhost tmp]# echo ${string:7:0} 
        
      [root@localhost tmp]# echo ${string:7:2}
      78
      [root@localhost tmp]# echo ${string:7:-2}        # 注意:此处的length值为-2,但是此时其并不表示字符长度,而是表示从该字符串的末尾,即倒数>第二个字符开始,向前偏移7个字符。另外,此时由于-2前面的冒号并不是跟在变量string的后面,所以此处可以不用在:和-之间加空格。
      7890abcdef
      [root@localhost tmp]# echo ${string: -7}         # 注意:此处的offset值为-7,需要在:与-之间加上至少1个空格,以区分':-'这种展开。
      bcdefgh
      [root@localhost tmp]# echo ${string: -7:0}       # 同理  
        
      [root@localhost tmp]#  echo ${string: -7:2}      # 同理
      bc
      [root@localhost tmp]# echo ${string: -7:-2}      # 同理
      bcdef
      [root@localhost tmp]# set -- 01234567890abcdefgh # 相当于指定了函数参数,即给当前bash进程指定了函数参数,此时可以通过位置变量进行引用,也>可以通过`$@`或者`$*`来引用全部的参数。 
      [root@localhost tmp]# echo ${1:7}
      7890abcdefgh
      [root@localhost tmp]# echo ${1:7:0} 
        
      [root@localhost tmp]# echo ${1:7:2}
      78
      [root@localhost tmp]# echo ${1:7:-2}
      7890abcdef
      [root@localhost tmp]# echo ${1: -7} 
      bcdefgh
      [root@localhost tmp]# echo ${1: -7:0} 
        
      [root@localhost tmp]# echo ${1: -7:2}
      bc
      [root@localhost tmp]# echo ${1: -7:-2}
      bcdef
      [root@localhost tmp]# array[0]=01234567890abcdefgh
      [root@localhost tmp]# echo ${array[0]:7}
      7890abcdefgh
      [root@localhost tmp]# echo ${array[0]:7:0} 
        
      [root@localhost tmp]# echo ${array[0]:7:2}
      78
      [root@localhost tmp]# echo ${array[0]:7:-2}
      7890abcdef
      [root@localhost tmp]# echo ${array[0]: -7} 
      bcdefgh
      [root@localhost tmp]# echo ${array[0]: -7:0} 
        
      [root@localhost tmp]# echo ${array[0]: -7:2}
      bc
      [root@localhost tmp]# echo ${array[0]: -7:-2}
      bcdef
      [root@localhost tmp]#
      

      如果parameter是@,此时结果是从offset开始的长度为length的字符个数,如果offset是负数,就表示从最后一个字符开始向前偏移,此时-1表示最后一个字符,如果length为负数,则展开操作会报错。

      具体示例如下所示:

      [root@localhost tmp]# set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f g h 
      [root@localhost tmp]# echo ${@:7}
      7 8 9 0 a b c d e f g h 
      [root@localhost tmp]# echo ${@:7:0} 
        
      [root@localhost tmp]# echo ${@:7:2}
      7 8 
      [root@localhost tmp]# echo ${@:7:-2}
      -bash: -2: substring expression < 0 
      [root@localhost tmp]# echo ${@: -7:2}
      b c 
      [root@localhost tmp]# echo ${@:0}
      -bash 1 2 3 4 5 6 7 8 9 0 a b c d e f g h 
      [root@localhost tmp]# echo ${@:0:2}
      -bash 1
      [root@localhost tmp]# echo ${@: -7:0} 
        
      [root@localhost tmp]# 
      

      如果parameter是索引数组的’@‘或者’*’,那么此时的结果就是${parameter[offset]}开始的length长度的字符。如果length的值为负数,则展开操作会报>错。具体示例如下所示:

      [root@localhost tmp]# array=(0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h)
      [root@localhost tmp]# echo ${array[@]:7}
      7 8 9 0 a b c d e f g h 
      [root@localhost tmp]# echo ${array[@]:7:2}
      7 8 
      [root@localhost tmp]# echo ${array[@]: -7:2}
      b c 
      [root@localhost tmp]# echo ${array[@]: -7:-2}
      -bash: -2: substring expression < 0 
      [root@localhost tmp]# echo ${array[@]:0}
      0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h 
      [root@localhost tmp]# echo ${array[@]:0:2}
      0 1 
      [root@localhost tmp]# echo ${array[@]: -7:0}
        
      [root@localhost tmp]# 
      
    • ${!prefix} | ${!prefix@}:匹配前缀名字*。展开以prefix开头的变量,以IFS指定的第一个字符作为分隔符,当在双引号中使用@符号的时候,表示将>各个变量作为单独的存在而展开。

      具体如下所示:

      [root@localhost tmp]# pre_a=4
      [root@localhost tmp]# pre_b=5
      [root@localhost tmp]# echo ${!pre_@}
      pre_a pre_b
      [root@localhost tmp]# echo ${!pre_b}
      5 
      [root@localhost tmp]# echo ${!pre_*}
      pre_a pre_b
      [root@localhost tmp]# echo ${!pre_a}
      4 
      [root@localhost tmp]#
      
    • ${!name[@]} | ${!name[*]}:列出数组的下标(索引下标或者名称下标)。如果name是数组变量,则列出数组的下标;如果name不是数组变量,且已经被设置不为空,那么这个表达式返回0;如果name未设置,则返回null。

      具体如下所示:

      [root@localhost tmp]# echo ${!num_arr[*]}
      0 1 2 3 4 5 6 7 8 9 
      [root@localhost tmp]# echo ${!pre_a[*]}
      0 
      [root@localhost tmp]# echo ${!pre_a[@]}
      0 
      [root@localhost tmp]# echo ${!pre_c[*]}
        
      [root@localhost tmp]# declare -a | egrep num_arr
      declare -a num_arr=([0]="53" [1]="35" [2]="94" [3]="14" [4]="19" [5]="31" [6]="4" [7]="95" [8]="67" [9]="0")
      [root@localhost tmp]# 
      
    • ${#parameter}:返回parameter的长度。如果parameter是’‘或者’@’,那么这个表达式返回的是位置参数的个数,否则返回parameter的值的长度。如果parameter是以’@‘或者’'为下标的数组名字,那么这个表达式返回数组中的元素个数。

      具体如下所示:

      [root@localhost tmp]# echo $pre_a
      4 
      [root@localhost tmp]# echo ${#pre_a}
      1 
      [root@localhost tmp]# declare -a | egrep num_arr
      declare -a num_arr=([0]="53" [1]="35" [2]="94" [3]="14" [4]="19" [5]="31" [6]="4" [7]="95" [8]="67" [9]="0")
      [root@localhost tmp]# echo ${#num_arr[*]}
      10
      [root@localhost tmp]# echo ${num_arr}
      53
      [root@localhost tmp]# echo ${#num_arr}
      2 
      [root@localhost tmp]# pre_c=100
      [root@localhost tmp]# echo ${#pre_c}
      3 
      [root@localhost tmp]#
      
    • ${parameter#word} | ${parameter##word}:从parameter前缀中移除被word匹配到的内容${parameter#word}这个表达式的效果是,将parameter的头部开始的部分中被word匹配的最短内容删除。${parameter##word}这个表达式的效果是,将parameter的头部开始的部分中被word匹配的最长的内容删除。如果parameter是’@‘或’’,那么这个模式匹配以及移除操作会轮流作用在每一个位置参数上。如果parameter是以’@‘或’'为下标的数组变量,那么这个模式匹配和移除操作会轮流应用到数组的每个元素上。

      具体如下所示:

      [root@localhost tmp]# str='america, american'
      [root@localhost tmp]# echo ${str#amer}
      ica, american
      [root@localhost tmp]# echo ${str##amer}
      ica, american
      [root@localhost tmp]# echo ${str##*amer}
      ican
      [root@localhost tmp]# echo ${str#*amer}
      ica, american
      [root@localhost tmp]#
      
    • ${parameter%word} | ${parameter%%word}:从parameter的后缀中移除被word匹配到的内容${parameter%word}这个表达式的效果是,将parameter>的尾部开始的部分中被word匹配的最短内容删除。${parameter%%word}这个表达式的效果是,将parameter的尾部开始的部分中被word匹配的最长的内容删除。如果parameter是’@‘或’’,那么这个模式匹配和删除操作会被轮流应用到每个位置参数上。如果parameter是以’@‘或’'为下标的数组,那么这个模式匹配和删除操作就>会被轮流应用到数组的每个元素上。

      具体如下所示:

      [root@localhost tmp]# echo $str
      america, american
      [root@localhost tmp]# echo ${str%ri*}
      america, ame 
      [root@localhost tmp]# echo ${str%%ri*}
      ame 
      [root@localhost tmp]#
      
    • ${parameter/pattern/string}:模式替换,将parameter中被pattern匹配到的内容替换为string。这个表达式的效果是,被pattern匹配到的最长的内容替换为string,如果pattern以’/‘开头,那么pattern的所有匹配都会被替换为string;如果pattern以’#‘开头,那么被匹配到的内容必须位于parameter的开头;如果pattern以%开头,那么pattern匹配的内容必须位于parameter的结尾。如果string为空,那么被pattern的内容将会被删除,此时pattern后面的’/‘斜线可以省略。
      如果指定了nocasematch这个shell选项,那么在执行pattern的模式匹配的时候会忽略字母大小写。如果parameter是’@‘或’’,那么这个替换操作会被应用到每一个位置参数上。如果parameter是以’@‘或’'为下标的数组,那么这个替换操作也会轮流应用到数组的每个元素上。

      具体如下所示:

      [root@localhost tmp]# echo $str
      america, american
      [root@localhost tmp]# echo ${str/*amer/aust}
      austican
      [root@localhost tmp]# echo ${str/amer/aust}
      austica, american
      [root@localhost tmp]#
      
    • ${parameter^pattern} | ${parameter^^pattern} | ${parameter,pattern} | ${parameter,pattern}:大小写修改。修改parameter内容中字母的大小写,pattern在路径名展开中作为一个匹配模式,注意:pattern只能匹配单个字符,即pattern要么为单个字符,要么省略。parameter中每个被pattern匹配的字符都会被进行大小写转换。^表示将parameter开头的被pattern匹配到的首个字符转换为大写;,表示将parameter开头的被pattern匹配到的首个字符从大写转换为小写。^^,,表示将每一个被匹配到的字符进行大小写转换;^,表示只转换被匹配到的首字符,如果省略pattern,则相当于指定了?,即匹配任意单个字符。如果parameter为’@‘或’’,那么大小写修改操作会被应用到每个位置参数上;如果parameter是以’@‘或’'为下标的数组,那么这个大小写修改操作会被应用到数组的每个元素上。

      具体示例如下:

      [root@localhost tmp]# echo $str1
      america, AMErican
      [root@localhost tmp]# echo ${str1^a}
      America, AMErican
      [root@localhost tmp]# echo ${str1^m}
      america, AMErican
      [root@localhost tmp]# echo ${str1,a}
      america, AMErican
      [root@localhost tmp]# str2='America, american'
      [root@localhost tmp]# echo ${str2,A}
      america, american
      [root@localhost tmp]# echo ${str2^a}
      America, american
      [root@localhost tmp]# echo ${str1^^r}
      ameRica, AMERican
      [root@localhost tmp]# echo ${str1,,A}
      america, aMErican
      [root@localhost tmp]#
      
    • ${parameter@operator}:参数转换。具体行为取决于operator,而operator为一下单字符:

      • Q:将parameter的内容加上引号
        [root@localhost tmp]# echo $str
        america, american
        [root@localhost tmp]# echo ${str@Q}
        'america, american'
        
      • E:The expansion is a string that is the value of parameter with backslash escape sequences expanded as with the $’…’ quoting mechansim.
      • P:The expansion is a string that is the result of expanding the value of parameter as if it were a prompt string (see PROMPTING below)
      • A:The expansion is a string in the form of an assignment statement or declare command that, if evaluated, will recreate parameter with its attributes and value.
      • a:The expansion is a string consisting of flag values representing parameter’s attributes.
  4. 命令替换(Command Substitution)

    这个在shell脚本中很常用,有两种形式:$(command)\command`。bash在子shell中执行命令替换。命令替换 ( c a t f i l e ) ‘ 可 以 替 换 为 ‘ (cat file)`可以替换为` (catfile)(< file),>当使用$(cmd)`这个形式的时候,小括号中的所有内容都被认为是命令的组成部分,不会有任何被特殊对待。命令替换可以嵌套,但是对于内层的反引号形式的命令>替换,需要将反引号进行转义。

  5. 算数展开(Arithmetic Expansion)

    算数展开的形式为$((expression)),可以将数学表达式写在这个小括号中。具体如下所示:

    [root@localhost tmp]# echo $((5/2))
    2 
    [root@localhost tmp]#
    
  6. 进程替换(Process Substitution)

    进程替换允许将一个进程的输入和输出作为文件名来引用,其形式为<(list)或者>(list),其中进程list是以异步的方式执行,它的输入和输出以文件名的
    形式存在,而文件名则作为表达式的结果以参数的形式传递给当前命令。如果使用了>(list)这个形式,写入到的文件将会给进程list提供输入参数;如果使用了<(list)这个形式,作为参数的文件将会从进程list的输出中读取内容写入到文件中。进程替换只在支持FIFO的命名管道的系统中才被支持,或者是/dev/fd这个命名打开文件的方式才可以。在支持进程替换的系统上,进程替换操作与参数和变量替换、命令替换、算术展开同步执行。

  7. 单词分割(Word Splitting)

    shell会扫描参数和变量展开、命令替换,而对于双引号括起来的算术展开则并不会执行单词分割。shell会将IFS变量中定义的每个字符作为单词分割符,并使>用这些分割符作为域分隔符分割其他展开中的结果。如果没有明确指定IFS的值,那么IFS就会采用默认值:即空格符、制表符和换行符。在前一个展开结果开头和结>尾的这些空格符、制表符和换行符都会被忽略,而使用不在开头或者结尾的这些分割符来进行单词分割。如果给IFS显式指定了默认字符意外其他字符作为分割符,也
    会忽略单词开头和结尾的空格符、制表符和换行符,就像采用默认分割符时的效果一样。如果IFS为null,那么就不会分割单词。显式指定null参数可以通过""或者’’,即IFS=""或者IFS='',此时IFS=null

  8. 路径名称展开(Pathname Expansion)

    单词分割之后,除非显式指定-f选项,否则bash会扫每个单词搜索’*’、’?’、’[’。如果搜索到这3个字符中的1个,那么这个单词就被认为是一个模式,即pattern。并且会将pattern匹配的文件名排序存成一个列表。如果没有匹配到任何文件名,并且未使能nullglob,那么单词就保持原样。如果使能了nullglob,并且为搜索到匹配的文件名,那么这个单词将会被移除。如果设置了failglob这个shell选项,且没有文件名被匹配出来,此时会打印错误消息,并且不再执行该命令。如果使能了nocaseglob,在进行文件名匹配的时候就会字母的大小写。当在路径名展开中使用匹配模式的时候,位于文件名开头的’.‘或’/.‘都必须显式进行匹配;如果指定了dotglob,则无需显式匹配。当匹配路径名的时候,路径分隔符’/‘必须被显式匹配。其他情况下的’.‘并不会被特殊对待。关于上述提到的nocaseglob, nullglob, failglob, dotglob的详细信息,参见man bash的SHELL BUILTIN COMMANDS下面的shopt部分。GLOBIGNORE环境变量会限制模式匹配的文件名集合。如果指定了GLOBIGNORE环境变量,每一个被其中的模式匹配到的文件名都会被从模式匹配结果列表中被移除。如果此时设置了nocaseglob选项,那么在GLOBIGNORE中的模式进行匹配的时候会忽略字符大小写。当设置了GLOBIGNORE环境变量且不为null的时候,’.‘以及’…‘这两个隐藏路径则总是被忽略。而将GLOBIGNORE设置未非空值与设置了dotglob选项的效果相同,所以,此时所有以’.'开头的路径都会被匹配出来。当GLOBIGNORE未设置的时候,dotglob选项会被禁用。模式匹配遵从extglob这个shell选项的设置。
    具有特殊意义的模式匹配字符如下:

    • *:匹配任意字符,包括null字符,当使能了globstar这个shell选项的时候,’‘号会被应用到路径名展开上下文环境中。两个连在一起的’’,即’‘会匹配所有的文件,以及0个或者多个目录以及子目录。如果是’/’,那么此时只能匹配目录以及子目录。
    • ?:匹配任意单个字符
    • [...]:匹配中括号中所列范围内的任意单个字符,如果中括号中是横线分隔的两个字符,此时表示范围匹配,比如[a-z], [0-9]。此时在这两个字符之>间的任意字符都会被匹配出来。如果[!string]或者[^string],那么此时表示不匹配中括号中所列的字符内容。关于字符的排列顺序,取决于环境变量LC_COLLATE或者LC_ALL的内容。比如[a-d]的效果就是匹配’abcd’这四个字符中的任意一个。另在,在中括号中还可以指定字符类,此时需要在中括号的字符类两边加上英文>半角冒号,形式为[:char_class:],支持的字符类char_class的值分别为:alnum alpha ascii blank cntrl digit graph lower print punct space upper word xdigit,此为POSIX标准的字符类集合。比如要匹配任意的大小写字母,可以写为[:alpha:],如果要匹配任意的大小写字母以及数字,可以写为[:alnum:]。除了上述的形式之外,还支持等号字符类,即[=c=],表示匹配所有与c处于相同collation weight的任意字符。另外[.symbol.] matches the collating symbol symbol. > 如果使用shopt内建命令使能了extglob这个选项,还可以支持几个额外的扩展模式匹配操作符。在下面的描述中,pattern-list是包含’|'分隔的多个模式的>列表。具体如下所示:
    • ?(pattern-list):通过给定模式匹配0个或者1个内容
    • *(pattern-list):通过给定模式匹配0个或者多个内容
    • +(pattern-list):通过给定模式匹配1个或者多个内容
    • @(pattern-list):通过给定模式匹配1个内容
    • !(pattern-list):匹配给定模式之外的内容,即排除给定模式匹配到的内容

3.2. 数组使用示例

下面的示例会以包含10个随机数的数组为基础,可以在命令行中通过一行命令创建出该随机数数组,具体如下所示:

通过for循环,结合bash的环境变量RANDOM进行随机数创建。在for循环中通过花括号展开,创建10个随机数;而通过echo ${RANDOM}%100 | bc命令可以保证创建
的随机数为100以内的数值。此时for循环的输出结果为按行输出,所以会打印10行,此处需要将其改为按列输出,打印为1行,所以需要paste -s这个命令以及选项
来完成。具体过程如下所示:

[root@localhost ~]# for n in {1..10}; do echo ${RANDOM}%100 | bc; done | paste -s
44    81  1   45  10  34  94  37  7   37  
[root@localhost ~]# num_arr=(`for n in {1..10}; do echo ${RANDOM}%100 | bc; done | paste -s`)
[root@localhost ~]# echo $num_arr
53
[root@localhost ~]# echo ${num_arr[*]}
53 35 94 14 19 31 4 95 67 0

上述就将包含10个100以内随机数的数组num_arr创建完成了。

接下来,就可以使用上述方式创建数组并实现对应功能的脚本了。

3.2.1. 求取数组中随机数的最大值和最小值

利用上述的命令行形式生成随机数数组,然后在脚本中处理生成的随机数数组,求取其中的最大值以及最小值。

#!/bin/bash


#--------------------------------------------------
# FileName    : max_min_random.sh
# Author      : 六弦企鹅
# Contact     : 123456789
# Version     : v0.1
# Modify      : 2021-05-22
# Usage       : bash max_min_random.sh
# Description : 求取随机数组中的最大值和最小值
#--------------------------------------------------

random_arr=(`for i in {1..10}; do echo $RANDOM%100 | bc; done | paste -s`)

min=${random_arr[0]}
max=${random_arr[0]}


for i in `seq 1 $(echo ${#random_arr[@]}-1 | bc)`
do
    if [ ${min} -gt ${random_arr[$i]} ]; then
        min=${random_arr[$i]}
    else
        continue
    fi
done

for j in `seq 1 $(echo ${#random_arr[*]}-1 | bc)`
do
    if [ $max -lt ${random_arr[$j]} ]; then
        max=${random_arr[$j]}
    else
        continue
    fi
done

echo 'The minium number in ' ${random_arr[*]} 'is ' $min
echo 'The maxium number in ' ${random_arr[@]} 'is ' $max

上述脚本的执行过程如下所示:

root@ubuntu20u04:~/scripts# bash max_min_random.sh
The minium number in  32 81 84 74 10 91 59 36 37 69 is  10
The maxium number in  32 81 84 74 10 91 59 36 37 69 is  91
root@ubuntu20u04:~/scripts# bash max_min_random.sh
The minium number in  1 60 88 40 5 86 90 97 95 7 is  1
The maxium number in  1 60 88 40 5 86 90 97 95 7 is  97
root@ubuntu20u04:~/scripts#

上述即为脚本的执行结果,在不同的随机数数组中,均可以求取其中的最大值与最小值。

上述脚本中,应用了bash的索引数组、花括号展开、命令替换、for循环控制结构以及if条件判断结构。

3.2.2. 采用冒泡算法对数组中随机数进行升序或者降序排列

冒泡排序(Bubble Sort)是一种通过迭代比较相邻两个元素的大小,并按照指定的升序或者降序对元素进行排序的算法。该算法的示意图如下:
在这里插入图片描述
上图即为冒泡排序的数据处理过程。用shell脚本实现如下:

下述脚本中分别实现了升序排列的冒泡算法实现以及降序排列的冒泡算法实现,具体脚本内容如下所示:

#!/bin/bash


#--------------------------------------------------
# FileName    : bubble_sort_random.sh
# Author      : 六弦企鹅
# Contact     : 123456789
# Version     : v0.1
# Modify      : 2021-05-23
# Usage       : bash bubble_sort_random.sh
# Description : 冒泡排序算法对随机数数组中的元素进行排序
#--------------------------------------------------


random_arr=(`for i in {1..10}; do echo ${RANDOM}%100 | bc; done | paste -s`)
echo 'original random_array is ' ${random_arr[*]}


# increase
max_idx=$(echo ${#random_arr[*]}-1 | bc)
for i in `seq 0 $max_idx`
do
    for j in `seq 0 $max_idx`
    do 
        if [ $j -eq $max_idx ]; then
            break
        fi 
        if [ ${random_arr[$j]} -gt ${random_arr[$((j+1))]} ]; then
            temp=${random_arr[$j]}
            random_arr[$j]=${random_arr[$((j+1))]}
            random_arr[$((j+1))]=${temp}
        else
            continue
        fi
    done
done
echo 'increased-sorted random_array is ' ${random_arr[*]}


# decrease
max_idx=$(echo ${#random_arr[*]}-1 | bc)
for m in `seq 0 $max_idx`
do
    for n in `seq 0 $max_idx`
    do
        if [ $n -eq $max_idx ]; then
            break
        fi
        if [ ${random_arr[$n]} -lt ${random_arr[$((n+1))]} ]; then
            temp=${random_arr[$n]}
            random_arr[$n]=${random_arr[$((n+1))]}
            random_arr[$((n+1))]=$temp
        else
            continue
        fi
    done
done
echo 'decreased-sorted random_array is ' ${random_arr[*]}

上述脚本的执行过程以及结果如下所示:

> root@ubuntu20u04:~/scripts# bash bubble_sort_random.sh
original random_array is  68 62 61 50 26 67 95 14 86 37
increased-sorted random_array is  14 26 37 50 61 62 67 68 86 95
decreased-sorted random_array is  95 86 68 67 62 61 50 37 26 14
root@ubuntu20u04:~/scripts# bash bubble_sort_random.sh
original random_array is  89 87 46 71 82 57 72 41 5 63
increased-sorted random_array is  5 41 46 57 63 71 72 82 87 89
decreased-sorted random_array is  89 87 82 72 71 63 57 46 41 5
root@ubuntu20u04:~/scripts#

上述即为shell脚本中实现的对随机数数组中的元素进行冒泡排序的完整脚本以及执行结果。

上述脚本中使用了bash提供的数组功能、命令替换、嵌套的for循环结构以及if条件判断结构。上述代码其实还可以更精简些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值