Shell脚本编程_shell简单脚本

2022-11-09 14:39:45 浏览数 (2)

大家好,又见面了,我是你们的朋友全栈君。

文章目录

  • 1. shell 概述
    • 1.1 shell 和 shell 脚本
    • 1.2 Shell 脚本应用场景
    • 1.3 Linux 中的 shell 类型
    • 1.4 脚本实例
  • 2. shell 脚本编程基础
    • 2.1 脚本调试
    • 2.2 脚本执行方法
    • 2.3 管道符号 |
    • 2.4 交互式硬件设备
    • 2.5 重定向操作
    • 2.6 变量
      • (1) 变量的赋值与引用
        • ① 变量赋值
        • ② 变量引用
      • (2) 变量类型
        • ① 自定义变量
        • ② 环境变量
        • ③ 只读变量
        • ④ 位置变量
        • ⑤ 预定义变量
      • (3) 变量的查看与删除
        • ① 查看已定义的所有变量
        • ② 删除变量
      • (4) 变量的赋值运算
    • 2.7 算数运算
      • (1) 实现算数运算的方式
      • (2) 增强型赋值
      • (3) 逻辑运算
      • (4) 随机数生成器变量 — $RANDOM
      • (5) “空” 设备 — /dev/null
      • (6) “零” 设备 — /dev/zero
      • (7) 随机数生成器设备 — /dev/random /dev/urandom
    • 2.8 echo -n/-e 的用法
    • 2.9 条件测试命令
    • 2.10 流程控制
      • (1) if 条件语句
      • (2) case 多分支语句
      • (3) for 循环
      • (4) while 循环
      • (5) until 循环
      • (6) 循环控制语句 continue
      • (7) 循环控制语句 break
      • (8) 循环控制 shift 命令
      • (9) while read 特殊用法
      • (10) select 循环与菜单
    • 2.11 Shell 函数
      • (1) 函数介绍
      • (2) 函数管理
        • ① 定义函数
        • ② 查看函数
        • ③ 删除函数
      • (3) 函数调用
        • ① 交互式环境调用函数
        • ② 在脚本中定义及使用函数
        • ③ 使用函数文件
      • (4) 函数返回值
      • (5) 环境函数
      • (6) 函数参数
      • (7) 函数变量
      • (8) 函数递归
    • 2.12 一些脚本相关工具
      • (1) 信号捕捉 trap
      • (2) 创建临时文件 mktemp
      • (3) 安装复制文件 install
      • (4) 交互式转化批处理工具 expect
    • 2.13 数组
      • (1) 数组介绍
      • (2) 声明数组
      • (3) 数组赋值
        • ① 一次只赋值一个元素
        • ② 一次赋值全部元素
        • ③ 只赋值特定元素
        • ④ 交互式数组值对赋值
      • (4) 显示所有数组
      • (5) 引用数组
        • ① 引用数组元素
        • ② 引用数组所有元素
        • ③ 数组的长度,即数组中元素的个数
      • (6) 删除数组
        • ① 删除数组中的某个元素
        • ② 删除整个数组
      • (7) 数组数据处理
        • ① 数组切片
        • ② 向数组中追加元素
      • (8) 关联数组
      • (9) 数组脚本示例

1. shell 概述

1.1 shell 和 shell 脚本

  shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务,在用户和内核之间充当翻译官的角色,是一个命令解释器。Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 shell。

  shell 脚本(shell script),是一种为 shell 编写的脚本程序。业界所说的 shell 通常都是指 shell 脚本,shell 和 shell script 是两个不同的概念。

1.2 Shell 脚本应用场景

  • 重复性操作
  • 交互性任务
  • 批量事务处理
  • 服务运行状态监控
  • 定时任务执行 …

1.3 Linux 中的 shell 类型

以 CentOS 7 为例

代码语言:javascript复制
[root@c7-1 ~]#cat /etc/shells
/bin/sh			#/bin/sh 是 bash 命令的软链接(已经被 /bin/bash 所替换)
/bin/bash		#基准于 GNU 的框架下发展出的 shell
/usr/bin/sh		#己经被 bash 所替换
/usr/bin/bash	#centos 和 redhat 系统默认使用 bash shell
/bin/tcsh		#csh 的增强版,与 csh 完全兼容,提供更多的功能
/bin/csh		#已经被 /bin/bash 所替换(整合 c shell,提供更多的功能)
/sbin/nologin	#奇怪的 shel1,这个 shell 可以让用户无法登录主机

1.4 脚本实例

代码语言:javascript复制
[root@c7-1 ~]#curl -s http://47.117.130.238/hello.sh
# ------------------------------------------
# Filename: hello.sh
# Version: 1.0
# Date: 2021/06/06
# Author: gongboyi
# Email: 1520509800@qq.com
# Website: https://www.cnblogs.com/shenyuanhaojie/
# Description: This is the first script
# Copyright: 2021 zhou
# License: GPL
# ------------------------------------------
echo "hello world!"

[root@c7-1 ~]#curl -s http://47.117.130.238/hello.sh|bash
hello world!

2. shell 脚本编程基础

2.1 脚本调试

只检测脚本中的语法错误,无法检查出命令错误,不真正执行脚本

代码语言:javascript复制
bash -n /path/to/script.sh

调试并执行

代码语言:javascript复制
bash -x /path/to/script.sh

脚本错误常见的有三种:

代码语言:javascript复制
语法错误:会导致后续的命令不继续执行,可以用 bash -n 检查错误,提示的出错行数不一定是准确的。
命令错误:后续的命令还会继续执行,用 bash -n 无法检查出来 ,可以使用 bash -x 进行观察。
逻辑错误:只能使用 bash -x 进行观察。

2.2 脚本执行方法

代码语言:javascript复制
相对路径执行( ./script.sh )在脚本当前目录,脚本需要执行权限
绝对路径执行( /PATH/to/script.sh )无需在脚本目录,脚本需要执行权限
bash 执行( bash /PATH/to/script.sh )bash 后可跟绝对路径和相对路径,脚本无需执行权限
source 执行( source /PATH/to/script.sh )source 后可跟绝对路径和相对路径,脚本无需执行权限
bash < script.sh 或 cat script.sh | bash

示例:

代码语言:javascript复制
[root@aliyunhost01html]#./hello.sh 
hello world!
[root@aliyunhost01html]#/var/www/html/1.sh 
hello world
[root@aliyunhost01html]#bash /var/www/html/1.sh
hello world
[root@aliyunhost01html]#source /var/www/html/1.sh
hello world
[root@aliyunhost01html]#bash < /var/www/html/1.sh
hello world
[root@aliyunhost01html]#cat /var/www/html/1.sh | bash
hello world

2.3 管道符号 |

将左侧命令的输出结果作为右侧命令的处理对象。 示例:

代码语言:javascript复制
[root@c7-1 ~]#grep "/sbin/nologin" /etc/passwd | awk -F: '{print $1,$7}'
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
mail /sbin/nologin
......

2.4 交互式硬件设备

代码语言:javascript复制
标准输入:从该设备接收用户输入的数据
标准输出:通过该设备向用户输出数据
标准错误:通过该设备报告执行出错信息

类型

设备文件

文件描述编号

默认设备

标准输入

/dev/stdin

0

键盘

标准输出

/dev/stdout

1

显示器

标准错误输出

/dev/stderr

2

显示器

代码语言:javascript复制
[root@c7-1 ~]#ll /dev/std*
lrwxrwxrwx 1 root root 15 9月   2 10:02 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 9月   2 10:02 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 9月   2 10:02 /dev/stdout -> /proc/self/fd/1

2.5 重定向操作

类型

操作符

用途

重定向输入

<

从指定的文件读取数据,而不是从键盘输入

重定向输出

>

将输出结果保存到指定的文件(覆盖原有内容)

>>

将输出结果追加到指定的文件尾部

标准错误输出

2>

将错误信息保存到指定的文件(覆盖原有内容)

2>>

将错误信息追加到指定的文件中

混合输出

&>

将标准输出、标准错误的输出保存到同一个文件中

示例:

代码语言:javascript复制
#将 ./test.sh 的输出重定向到 log.txt 文件中,同时将标准错误也重定向到 log.txt 文件中
./test.sh > log.txt 2>&1

# nohup ... & 代表后台运行并且生成 nohup.log 日志文件
# command>/dev/null 代表命令输出结果导入到空设备
# 2>&1 代表将标准错误的内容重定向到标准输出,即将程序运行中的错误信息也打印出来
nohup ./test.sh>/dev/null 2>&1 &

参考:重定向理解

2.6 变量

(1) 变量的赋值与引用

① 变量赋值
代码语言:javascript复制
name='value'

value 可以是以下多种形式:

代码语言:javascript复制
直接字串:name='root'
变量引用:name="$USER"
命令引用:name=`COMMAND` 或者 name=$(COMMAND)

变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量随着脚本结束,也会自动删除。

使用 read 从标准输入读取数值:

代码语言:javascript复制
read -p "提示信息" [变量名]
read -p "提示信息" [变量名] < [文件]

常用选项:

代码语言:javascript复制
-p   #指定要显示的提示信息
-s   #静默输入,一般用于密码
-n N #指定输入的字符长度 N
-d '字符'	#输入结束符
-t N 	    #TIMEOUT 为 N 秒

示例:

代码语言:javascript复制
[root@c7-1 ~]#read -p "请输入密码:" passwd
请输入密码:120604
[root@c7-1 ~]#echo $passwd
120604
[root@c7-1 ~]#vim IP.txt
[root@c7-1 ~]#read -p "请输入IP地址:" IP < IP.txt
[root@c7-1 ~]#echo $IP
47.117.130.238
[root@c7-1 ~]#cat IP.txt 
47.117.130.238
#实现运维工作菜单
echo -en "E[$[RANDOM%7 31];1m"
cat <<EOF 请选择: 1)备份数据库 2)清理日志 3)软件升级 4)软件回滚 5)删库跑路 EOF
echo -en 'E[0m'
read -p  "请输入上面数字1-5: " MENU
[ $MENU -eq 1 ] && ./backup.sh
[ $MENU -eq 2 ] && echo "清理日志"
[ $MENU -eq 3 ] && echo "软件升级"
[ $MENU -eq 4 ] && echo "软件回滚"
[ $MENU -eq 5 ] && echo "删库跑路"
② 变量引用
代码语言:javascript复制
" "    弱引用,允许通过 $ 符号引用其他变量值
' '    强引用,禁止引用其他变量值,$ 视为普通字符
` `    命令引用,提取命令执行后的输出结果

示例:

代码语言:javascript复制
[root@c7-1 ~]#username=zc
[root@c7-1 ~]#echo "you are $username"
you are zc
[root@c7-1 ~]#echo 'you are $username'
you are $username
[root@c7-1 ~]#echo `whoami`
root

(2) 变量类型

  • 自定义变量:由用户自己定义、修改、使用,只在用户自己的 shell 环境中有效,因此又称为本地变量
  • 特殊变量:环境变量、只读变量、位置变量、预定义变量
① 自定义变量

基本格式:

代码语言:javascript复制
变量名=变量值

示例:

代码语言:javascript复制
[root@c7-1 ~]#username=zc
[root@c7-1 ~]#echo $username
zc
[root@c7-1 ~]#set | grep username #set 显示用户自定义的变量
username=zc
local remote_opts="--username= --config-dir= --no-auth-cache";
--no-auth-cache --username=
[root@c7-1 ~]#unset username
[root@c7-1 ~]#set | grep username
_=username
local remote_opts="--username= --config-dir= --no-auth-cache";
--no-auth-cache --username=
② 环境变量

环境变量分为:

  • 全局变量:不仅对 shell 可见,对其子进程也可见
  • 局部变量:只能在定义其的进程中可见

显示所有环境变量:

代码语言:javascript复制
env
printenv
export
declare -x

Bash 内建的环境变量:

代码语言:javascript复制
PATH
SHELL
USER
UID
HOME
PWD
SHLVL	#shell 的嵌套层数
LANG
MAIL
HOSTNAME
HISTSIZE
_ 		#下划线,表示前一命令的最后一个参数

声明全局变量:

代码语言:javascript复制
export [变量名]
declare -x [变量名]

声明的全局变量虽然在环境变化的时候仍然生效,但是当系统重启后不会保存,要想永久生效可以保存到配置文件中。

变量配置文件:

代码语言:javascript复制
系统层面:/etc/profile、/etc/bashrc
用户层面:~/.bash_profile、~/.bashrc、~/.bash_history、~/.bash_logout
#系统层面的配置文件通常在登录时加载,用户层面的配置文件只对单个用户生效

参考:Linux环境变量配置全攻略

③ 只读变量

只读变量的声明:

代码语言:javascript复制
readonly [变量名]
declare -r [变量名]

查看:

代码语言:javascript复制
readonly [-p]
declare -r

只读变量无法删除或更改,当退出登录或者重启系统会失效。

示例:

代码语言:javascript复制
[root@c7-1 ~]#readonly PI=3.14
[root@c7-1 ~]#echo $PI
3.14
[root@c7-1 ~]#PI=3.14159
-bash: PI: 只读变量
[root@c7-1 ~]#unset PI
-bash: unset: PI: 无法反设定: 只读 variable
[root@c7-1 ~]#logout
Connection closed by foreign host.
Disconnected from remote host(c7-1) at 21:53:33.
Type `help' to learn how to use Xshell prompt.
[c:~]$ 
Reconnecting in 5 seconds. Press any key to exit local shell.
...
Connecting to 192.168.10.20:22...
Connection established.
To escape to local shell, press Ctrl Alt ].
Last login: Fri Sep  3 21:33:20 2021 from 192.168.10.1
[root@c7-1 ~]#echo $PI
[root@c7-1 ~]#
④ 位置变量

在 bash shell 中内置的变量,在脚本代码中调用通过命令行传递给脚本的参数。

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

清空所有位置变量:

代码语言:javascript复制
set --

示例:

代码语言:javascript复制
[root@c7-1 ~]#cat test.sh 
echo $1 $2 $3
echo $0
echo $?
echo $$
echo $!
echo $#
echo $*
echo $@
[root@c7-1 ~]#bash test.sh a b c
a b c
test.sh
0
72999
3
a b c
a b c
#实现添加用户
[root@c7-1 ~]#cat useradd.sh
#!/bin/bash
useradd $1
echo $2 | passwd --stdin $1
[root@c7-1 ~]#bash useradd.sh zc 123456
更改用户 zc 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@c7-1 ~]#cat /etc/passwd|grep zc
zc:x:1001:1001::/home/zc:/bin/bash
#删库跑路之命令 rm 的安全实现
WARNING_COLOR="echo -e E[1;31m"
END="E[0m"
DIR=/tmp/`date  %F_%H-%M-%S`
mkdir $DIR
mv  $*  $DIR
${WARNING_COLOR}Move $* to $DIR $END
⑤ 预定义变量

  预定义变量是在 Shell 一开始时就定义的变量,这一点和默认环境变量有些类似。不同的是,预定义变量不能重新定义,用户只能根据 Shell 的定义来使用这些变量。严格来说,位置参数变量也是预定义变量的一种,只是位置参数变量的作用比较统一,所以我们把位置参数变量单独划分为一类数量。

代码语言:javascript复制
$? 表示前一条命令执行后的返回状态,返回值为О表示执行正确,返回任何非О值均表示执行出现异常
$$ 表示返回当前进程的进程号
$! 返回最后一个后台进程的进程号

用户可以在脚本中使用以下命令自定义退出状态码:

代码语言:javascript复制
exit [n]
#脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
#如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码

示例:

代码语言:javascript复制
#测试网络通信
[root@c7-1 ~]#ping -c1 -W1 47.117.130.238 &> /dev/null
[root@c7-1 ~]#echo $?
0
#测试服务端口
[root@aliyunhost01~]#curl 127.0.0.1:80 &> /dev/null
[root@aliyunhost01~]#echo $?
0

(3) 变量的查看与删除

① 查看已定义的所有变量
代码语言:javascript复制
set
② 删除变量
代码语言:javascript复制
unset [变量名]
#只读变量无法删除

(4) 变量的赋值运算

代码语言:javascript复制
 	加
-	减
*	乘
/	除
% 	取模,即取余数,示例:9%4=1,5%3=2
** 	乘方

示例:

代码语言:javascript复制
[root@c7-1 ~]#expr 2   3
5
[root@c7-1 ~]#num1=2
[root@c7-1 ~]#num2=3
[root@c7-1 ~]#expr $num1   $num2
5
[root@c7-1 ~]#expr $num2 / $num1
1
[root@c7-1 ~]#expr $num1 * $num2
6
[root@c7-1 ~]#expr $num2 % $num1
1
[root@c7-1 ~]#sum=$(($num1**$num2))
[root@c7-1 ~]#echo $sum
8

2.7 算数运算

(1) 实现算数运算的方式

代码语言:javascript复制
let var=算术表达式
((var=算术表达式))
var=$[算术表达式]
var=$((算术表达式))
var=$(expr arg1 arg2 arg3 ...)
declare -i var = 数值
echo '算术表达式' | bc 

(2) 增强型赋值

代码语言:javascript复制
 =	# i =j 相当于 i=i j
-= 	# i-=j 相当于 i=i-j
*=	# i*=j 相当于 i=i*j 
/=  # i/=j 相当于 i=i/j
%=	# i%=j 相当于 i=i%j 
    # i  ,  i 相当于 i=i 1
--  # i--,--i 相当于 i=i-1

说明:i 和 i,i– 和 –i 含义是不同的,我们用具体的示例来解释

代码语言:javascript复制
# i 先将值赋值给了 j ,然后再完成自加
[root@c7-1 ~]#unset i j; i=1; let j=i  ; echo "i=$i,j=$j"
i=2,j=1
# i 完成自加后赋值给 j
[root@c7-1 ~]#unset i j; i=1; let j=  i; echo "i=$i,j=$j"
i=2,j=2

(3) 逻辑运算

代码语言:javascript复制
布尔型变量bool的取值只有false和true
true	1
false	0

& 与

代码语言:javascript复制
 1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0

| 或

代码语言:javascript复制
 1 或 1 = 1
1 或 0 = 1
0 或 1 = 1
0 或 0 = 0 

! 非

代码语言:javascript复制
! 1 = 0
! 0 = 1

^ 异或

代码语言:javascript复制
相同为假,不同为真
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0 

&& 短路与

代码语言:javascript复制
CMD1 短路与 CMD2
第一个CMD1结果为真(1),第二个CMD2必须要参与运算,才能得到最终的结果
第一个CMD1结果为假(0),总的结果必定为0,因此不需要执行CMD2

|| 短路或

代码语言:javascript复制
CMD1 短路或 CMD2
第一个CMD1结果为真(1),总的结果必定为1,因此不需要执行CMD2
第一个CMD1结果为假(0),第二个CMD2必须要参与运算,才能得到最终的结果

(4) 随机数生成器变量 – $RANDOM

bash 有内建的随机数生成器:$RANDOM(0-32767)

代码语言:javascript复制
#生成 0-49 之间随机数
echo $[$RANDOMP]
#更换字体颜色,每次输出的结果显示不同的颜色
echo -e "E[1;$[RANDOM%7 31]m hello e[0m"

(5) “空” 设备 – /dev/null

“空” 设备,也被称为黑洞。任何输入到这个设备的数据都将被直接丢弃(但是操作返回成功 $? 值为 0)。最常用的用法是把不需要的输出重定向到这个文件。例如:

代码语言:javascript复制
#将标准输出和错误输出重定向到 /dev/null,运行这个脚本不会输出任何信息到终端
run.sh 1>/dev/null 2>&1  

(6) “零” 设备 – /dev/zero

“零” 设备,可以无限的提供空字符(0x00,ASCII代码NUL)。常用来生成一个特定大小的文件。例如:

代码语言:javascript复制
#产生一个 1k 大小的文件 output.txt
dd if=/dev/zero of=./output.txt bs=1024 count=1 

(7) 随机数生成器设备 – /dev/random /dev/urandom

随机数设备,提供不间断的随机字节流。二者的区别是 /dev/random 产生随机数据依赖系统中断,当系统中断不足时,/dev/random 设备会 “挂起”,因而产生数据速度较慢,但随机性好;/dev/urandom 不依赖系统中断,数据产生速度快,但随机性较低。

代码语言:javascript复制
#生成随机密码
< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;
openssl rand -base64 32
date  %s | sha256sum | base64 | head -c 32 ; echo
tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1
strings /dev/urandom | grep -o '[[:alnum:]]' | head -n 30 | tr -d 'n'; echo
dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 -w 0 | rev | cut -b 2- | rev
date | md5sum

参考:Linux 中的虚拟设备

2.8 echo -n/-e 的用法

echo -n 不换行输出

代码语言:javascript复制
[root@c7-1 ~]#echo "123456"
123456
[root@c7-1 ~]#echo -n "123456"
123456[root@c7-1 ~]#

echo -e 处理特殊字符

代码语言:javascript复制
若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出
a 发出警告声
b 删除前一个字符
c 最后不加上换行符号
f 换行但光标仍旧停留在原来的位置
n 换行且光标移至行首
r 光标移至行首,但不换行
t 插入tab
v 与f相同
\ 插入字符
nnn 插入nnn(八进制)所代表的ASCII字符

参考: echo -n/-e 用法 echo 命令详解 echo 显示内容带颜色

2.9 条件测试命令

判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程。

代码语言:javascript复制
若真,则状态码变量 $? 返回 0
若假,则状态码变量 $? 返回 1

条件测试格式:

代码语言:javascript复制
test <选项> <测试的内容>
[ 选项 测试的内容 ]
[[ 选项 测试的内容 ]]

常用测试选项:

代码语言:javascript复制
-e	测试目录或文件是否存在,[ -a file ] 等于 [ -e file ]
-d	测试是否为目录
-f	测试是否为文件
-r	测试当前用户是否有权限读取
-w	测试当前用户是否有权限写入
-x	测试当前用户是否有权限执行
-u 	测试文件是否存在且拥有 suid 权限
-g	测试文件是否存在且拥有 sgid 权限
-k	测试文件是否存在且拥有 sticky 权限
-z	如果 STRING 的长度为零则为真
-n	如果 STRING 的长度非零则为真
-b	测试文件是否存在并且是块设备文件
-c	测试文件是否存在并且是字符设备文件
-L	测试文件是否存在并且是链接文件
-p	测试文件是否存在并且是管道文件
-S	测试文件是否存在并且是套接字文件
-s	测试文件是否存在并且文件大小为空

其他文件属性测试选项:

代码语言:javascript复制
-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

字符串比较:

代码语言:javascript复制
[ STRING1 = STRING2 ]	两个字符串相等为真
[ STRING1 != STRING2 ]	两个字符串不等为真                    
[ STRING1 < STRING2 ]	如果 STRING1 按字典顺序排在 STRING2 之前,则为真
[ STRING1 > STRING2 ]	如果 STRING1 按字典顺序排在 STRING2 之后,则为真
[[ STRING1 == STRING2 ]]	左侧字符串是否和右侧的 PATTERN 相同
[[ STRING1 =~ STRING2 ]]	左侧字符串是否能够被右侧的正则表达式的 PATTERN 所匹配

逻辑运算与、或、非(条件组合测试):

代码语言:javascript复制
[ ! EXPR ]	逻辑非,与表达相反的结果为真
[ EXPR1 -a EXPR2 ]	逻辑与,要同时满足多个表达式才为真,等同于 &&
[ EXPR1 -o EXPR2 ]	逻辑或,满足其中任意一个表达式即为真,等同于 ||
#如果 && 和 || 混合使用,&& 要在前,|| 在后

变量测试:

代码语言:javascript复制
-v VAR	变量 VAR 是否设置
-r VAR	变量 VAR 是否设置并引用

数值比较:

代码语言:javascript复制
-eq	是否等于
-ne 是否不等于
-gt 是否大于
-ge 是否大于等于
-lt 是否小于
-le 是否小于等于

2.10 流程控制

(1) if 条件语句

格式:

代码语言:javascript复制
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else
COMMANDS; ] fi

单分支:

代码语言:javascript复制
if	判断条件;then
条件为真的分支代码
fi

双分支:

代码语言:javascript复制
if	判断条件;then
条件为真的分支代码
else
条件为假的分支代码
fi

多分支:

代码语言:javascript复制
if	判断条件1;then
条件1为真的分支代码
elif	判断条件2;then
条件2为真的分支代码
elif	判断条件3;then
条件3为真的分支代码
...
else
以上条件都为假的分支代码
fi

示例:

代码语言:javascript复制
###单分支
[root@c7-1 ~]#cat if1.sh 
#!/bin/bash
if ping -c1 -w2 www.baidu.com &> /dev/null;then
echo "网络连通"
fi
[root@c7-1 ~]#bash if1.sh 
网络连通
###双分支
[root@c7-1 ~]#cat if2.sh
#!/bin/bash
IP=47.117.130.238
if ping -c1 -i 0.2 -w2 $IP &> /dev/null;then
echo "主机在线"
else 
echo "主机断联"
fi
[root@c7-1 ~]#bash if2.sh
主机在线
###多分支
[root@c7-1 ~]#cat if3.sh
#!/bin/bash
read -p "请输入分数:" GRADE
if [ $GRADE -le 100 -a $GRADE -ge 80 ];then
echo "成绩优秀"
elif [ $GRADE -lt 80 -a $GRADE -ge 60 ];then
echo "成绩合格"
else 
echo "不及格"
fi
[root@c7-1 ~]#bash if3.sh
请输入分数:65
成绩合格

(2) case 多分支语句

格式:

代码语言:javascript复制
case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac

分支结构:

代码语言:javascript复制
case 变量值 in
模式一)
命令序列
;;
模式二)
命令序列
;;
模式三)
命令序列
;;
......
*)
默认命令序列
esac                 

case 支持 glob 风格的通配符:

代码语言:javascript复制
*	任意长度任意字符
?	任意单个字符
[]	指定范围内的任意单个字符
|	逻辑或

示例:

代码语言:javascript复制
###判断输入yes或no
[root@c7-1 ~]#cat YESorNO.sh
#!/bin/bash
read -p "请输入YES或NO:" VAR
VAR=`echo $VAR | tr 'A-Z' 'a-z'`
case $VAR in
y|ye|ys|yes)
echo "你输入的是YES"
;;
n|no)
echo "你输入的是NO"
;;
*)
echo "输入错误,请重新输入YES或NO"
esac
[root@c7-1 ~]#bash YESorNO.sh
请输入YES或NO:y
你输入的是YES
###httpd 服务控制
[root@c7-1 ~]#cat httpd.sh 
#!/bin/bash
case $1 in
start)
/usr/bin/systemctl $1 httpd
/usr/bin/ps aux | grep httpd
echo "httpd start"
;;
stop)
/usr/bin/systemctl $1 httpd
/usr/bin/ps aux | grep httpd
echo "httpd stop"
;;
restart)
echo "httpd stop......"
/usr/bin/ps aux | grep httpd
/usr/bin/systemctl $1 httpd
echo "httpd restart......"
/usr/bin/ps aux | grep httpd
;;
status)
/usr/bin/systemctl $1 httpd
;;
*)
echo "请输入 start|stop|restart|status"
esac
[root@c7-1 ~]#./httpd.sh status | grep active
Active: active (running) since 一 2021-09-06 11:20:30 CST; 35s ago

(3) for 循环

格式:

代码语言:javascript复制
#格式一
for 变量名 in 取值列表;
do
循环体
done
#格式二
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done

示例:

代码语言:javascript复制
#!/bin/bash
for i in `seq 10`
do 
echo $i
done
————————————————————
#!/bin/bash
#输出 10 次 hello world
a=10
for i in `seq $a`
do
echo "hello world"
done
————————————————————
#!/bin/bash
#输出 0-10,间隔 2
for i in { 
0..10..2}
do
echo $i
done
————————————————————
#!/bin/bash
for ((i=1;i<=5;i  ))
do 
echo $i
done
————————————————————
#!/bin/bash
#批量创建用户并修改密码
for i in { 
1..5}
do
useradd user$i
echo "123456" | passwd --stdin user$i
done
————————————————————
#!/bin/bash
#根据用户列表文件批量创建用户并修改密码
ULIST=$(cat /opt/user.txt)
for UNAME in $ULIST
do
useradd $UNAME
echo "123456"|passwd --stdin $UNAME &> /dev/null
done
————————————————————
#!/bin/bash
#根据用户列表文件删除用户
ULIST=$(cat /opt/user.txt)
for UNAME in $ULIST
do
userdel -r $UNAME &> /dev/null
done
————————————————————
#!/bin/bash
#测试文件中的 IP 是否连通
IPLIST=$(cat /opt/ipaddrs.txt)
for IP in $IPLIST
do
ping -c 3 -i 0.2 -W 3 $IP &> /dev/null
if [ $? -eq 0 ];then
echo "Host $IP is up"
else
echo "Host $IP is down"
fi
done
————————————————————
#!/bin/bash
#测试 IP 网段
for i in { 
1..255}
do
ping -c 2 -i 0.1 -W 1 192.168.10.$i &>/dev/dull
if [ $? -eq 0 ]
then
echo "192.168.10.$i is up"
else
echo "192.168.10.$i is down"
fi
done
————————————————————
#!/bin/bash
#密码登录与确认
init=123456
for i in { 
1..3}
do
read -p "请输入免密:" pass
if [ $pass == $init ];then
echo "密码正确"
exit
fi
done
echo "密码错误"
————————————————————
#!/bin/bash
#九九乘法表一
for i in { 
1..9};do
for j in `seq $i`;do
echo -e "${j}x${i}=$[i*j]tc"
done
echo
done
————————————————————
#!/bin/bash
#九九乘法表二
for ((i=1;i<10;i  ));do
for((j=1;j<=i;j  ));do
echo -e "${j}x${i}=$[i*j]tc"
done
echo
done
————————————————————
#!/bin/bash
#生成菱形
read -p "请输入菱形行数:" RM
for ((i=1;i<=$RM;i  ))
do
for ((j=$RM;j>$i;j--))
do
echo -n " "
done
for ((n=1;n<=$i;n  ))
do
echo -n "* "
done
echo ""
done
for ((k=$RM-1;k>=1;k--))
do
for ((m=$RM;m>$k;m--))
do
echo -n " "
done
for ((l=1;l<=$k;l  ))
do
echo -n "* "
done
echo ""
done

(4) while 循环

格式:

代码语言:javascript复制
while CONDITION; 
do
循环体
done

说明: CONDITION:循环控制条件,进入循环之前,先做一次判断,每一次循环之后会再次做判断。条件为 “true” 则执行一次循环,直到条件测试状态为 “false” 终止循环,因此:CONDTION 一般应该有循环控制变量,而此变量的值会在循环体不断地被修正。 进入条件:CONDITION 为 true 退出条件:CONDITION 为 false

示例:

代码语言:javascript复制
#!/bin/bash
i=1
while [ $i -le 5 -a $i -ge 0 ]
do
echo $i 
let i  
done
echo -e "33[31m最后的i值为:$i33[0m"
echo -e "33[31m循环结束33[0m"
————————————————————
#!/bin/bash
#httpd 服务控制
while pgrep "httpd" &> /dev/null
do
echo -e "33[32mhttpd 服务正在运行33[0m"
sleep 2
done
echo -e "33[31mhttpd 服务异常33[0m" 
echo -e "33[32m安装并启动 httpd 服务......33[0m"
yum -y install httpd &>/dev/null
systemctl start httpd &>/dev/null && systemctl enable httpd &> /dev/null
systemctl status httpd
————————————————————
#!/bin/bash
#猜数字,猜对了通过 break 退出循环
NUM=8
while true
do
read -p "请输入数字:" SHUZI
if [ $SHUZI -eq $NUM ];then
echo "猜对了"
break
elif [ $SHUZI -gt $NUM ];then
echo "猜大了"
elif [ $SHUZI -lt $NUM ];then
echo "猜小了"
fi
done
————————————————————
#无限循环,break 退出
while true; 
do
循环体
done
while true; 
do
循环体
break
done

while 循环实现磁盘超载邮件告警:

代码语言:javascript复制
#配置发邮件的邮箱
[root@centos8 ~]#cat .mailrc
set from=1520509800@qq.com
set smtp=smtp.qq.com
set smtp-auth-user=1520509800@qq.com
set smtp-auth-password=esvnhbnqocirbicf
set smtp-auth=login
set ssl-verify=ignore
#告警脚本
[root@centos8 ~]#cat while_diskcheck.sh
WARNING=80
while :;do
USE=`df | sed -rn '/^/dev/sd/s#.* ([0-9] )%.*#1#p' |sort -nr|head -n1`
if [ $USE -gt $WARNING ];then
echo Disk will be full from `hostname -I` | mail -s "disk warning" 1520509800@qq.com
fi
sleep 10
done

(5) until 循环

重复测试某个条件,只要条件不成立则反复执行。

格式:

代码语言:javascript复制
until CONDITION;
do
循环体
done

说明:

代码语言:javascript复制
进入条件:CONDITION 为 false
退出条件:CONDITION 为 true

无限循环:

代码语言:javascript复制
until false;
do
循环体
done

示例:

代码语言:javascript复制
#!/bin/bash
i=1
sum=0
until [ $i -eq 10 ]
do
sum=$[$i $sum]
let i  
done
echo $sum 

(6) 循环控制语句 continue

continue [N]:提前结束第 N 层的本轮循环,而直接进入下一轮判断,最内层为第 1 层。

格式:

代码语言:javascript复制
while CONDITION1; 
do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done

示例:

代码语言:javascript复制
#!/bin/bash
for ((i=0;i<10;i  ));
do
for((j=0;j<10;j  ));
do
[ $j -eq 5 ] && continue 2
echo $j
done
echo ---------------------------
done

参考:continue-break

(7) 循环控制语句 break

break [N]:提前结束第 N 层整个循环,最内层为第 1 层。

格式:

代码语言:javascript复制
while CONDITION1; 
do
CMD1
...
if CONDITION2;then
break
fi
CMDn
...
done

示例:

代码语言:javascript复制
#!/bin/bash
#可测试 break 和 break 2 输出结果有什么不同
#break 退出单个循环,break 2 退出 2 层循环
for ((i=0;i<10;i  ));
do
for((j=0;j<10;j  ));
do
[ $j -eq 5 ] && break 2
echo $j
done
echo ---------------------------
done

参考:continue-break

(8) 循环控制 shift 命令

shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。 参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift。

示例:

代码语言:javascript复制
#!/bin/bash
#step through all the positional parameters
until [ -z "$1" ]
do
echo "$1"
shift
done
echo
./shift.sh a b c d e f g h
————————————————————
#!/bin/bash
if [ $# -eq 0 ];then
echo "Usage: `basename $0` user1 user2 ..."
exit
fi
while [ "$1" ];do
if id $1 &> /dev/null;then
echo $1 is exist
else
useradd $1
echo "$1 is created"
fi
shift
done
echo "All user is created"

(9) while read 特殊用法

while 循环的特殊用法,遍历文件或文本的每一行。

格式:

代码语言:javascript复制
while read line;
do
循环体
done < /PATH/FROM/FILE

示例:

代码语言:javascript复制
#!/bin/bash
WARNING=80
MAIL=root@gongboyi.com
df|sed -nr "/^/dev/sd/s#^([^ ] ) .* ([0-9] )%.*#1 2#p"|while read DEVICE USE;
do
if [ $USE -gt $WARNING ] ;then
echo "$DEVICE will be full,use:$USE" | mail -s "DISK WARNING" $MAIL
fi
done

(10) select 循环与菜单

格式:

代码语言:javascript复制
select variable in list;
do
循环体命令
done

示例:

代码语言:javascript复制
#!/bin/bash
#----------
sum=0
PS3="请点菜(1-6): "
select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 火锅 点菜结束;do
case $REPLY in
1)
echo $MENU 价格是 100
let sum =100
;;
2)
echo $MENU 价格是 88
let sum =88
;;
3)
echo $MENU价格是 66
let sum =66
;;
4)
echo $MENU 价格是 166
let sum =166
;;
5)
echo $MENU 价格是 200
let sum =200
;;
6)
echo "点菜结束,退出"
break
;;
*)
echo "点菜错误,重新选择"
;;
esac
done
echo "总价格是: $sum"

2.11 Shell 函数

(1) 函数介绍

  函数 function 是由若干条 shell 命令组成的语句块,实现代码重用和模块化编程。它与 shell 程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分。   shell 程序在子 shell 中运行,而 shell 函数在当前 shell 中运行。因此在当前 shell 中,函数可对 shell 中变量进行修改。   函数由两部分组成:函数名和函数体。

(2) 函数管理

① 定义函数

语法一:

代码语言:javascript复制
func_name () { 

...函数体...
}

语法二:

代码语言:javascript复制
function func_name { 

...函数体...
}

语法三:

代码语言:javascript复制
function func_name () { 

...函数体...
}
② 查看函数
代码语言:javascript复制
#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#指定查看当前已定义的函数名
declare -F func_name
#指定查看当前已定义的函数名定义
declare -f func_name
③ 删除函数
代码语言:javascript复制
unset func_name

(3) 函数调用

函数的调用方式:

代码语言:javascript复制
可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中

函数的生命周期:

代码语言:javascript复制
被调用时创建,返回时终止

调用的概念:

代码语言:javascript复制
函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数
代码。
① 交互式环境调用函数
代码语言:javascript复制
[root@c7-1 ~]#dir () { 

> ls -l
> }
[root@c7-1 ~]#dir
总用量 4
-rw-r--r-- 1 root root 187 9月   8 17:47 1.sh
② 在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至 shell 首次发现它后才能使用,调用函数仅使用其函数名即可。

代码语言:javascript复制
#!/bin/bash
function fun () { 

echo "hello world!"
}
#通过函数名调用函数
fun
————————————————————
#!/bin/bash
function test { 

read -p "请输入数字:" num
return $[$num*2]
}
test
echo $?
————————————————————
#!/bin/bash
sum () { 

read -p "请输入第一个参数:" NUM1
read -p "请输入第二个参数:" NUM2
echo -e "33[32m你输入的两个数为:$NUM1 和 $NUM233[0m"
SUM=$(($NUM1 $NUM2))
echo -e "33[31m两个数之和为:$SUM33[0m"
}
sum
—————————————————————
#!/bin/bash
#函数调用函数
# local 一般用于局部变量声明
fa () { 

if [ $1 -eq 1 ];then
echo 1
else
local tp=$[ $1 - 1 ]
local res=$(fa $tp)
echo $[ $1 * $res ]
fi
}
read -p "请输入:" num
res=$(fa $num)
echo $res
————————————————————
#!/bin/bash
#修改本地 repo 源
#请事先挂载光盘镜像,VMware设置里选择已连接
function repobackup { 

cd /etc/yum.repos.d
mkdir repo.bak
mv *.repo repo.bak
mount /dev/sr0 /mnt > /dev/null
}
makelocalrepo () { 

echo -e ' [local] name=local baseurl=file:///mnt enabled=1 gpgcheck=0' > local.repo
}
uselocalrepo () { 

yum clean all > /dev/null
yum makecache > /dev/null
yum -y install httpd > /dev/null
systemctl start httpd
}
#-----main-----#
repobackup
makelocalrepo
uselocalrepo
③ 使用函数文件
  • 可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入 shell,再进行调用函数
  • 文件名可任意选取,但最好与相关任务有某种联系,例如:functions
  • 一旦函数文件载入 shell,就可以在命令行或脚本中调用函数。可以使用 delcare -f 或 set 命令查看所有定义的函数,其输出列表包括已经载入 shell 的所有函数
  • 若要改动函数,首先用 unset 命令从 shell 中删除函数,改动完毕后,再重新载入此文件

实现函数文件的过程:

代码语言:javascript复制
创建函数文件,只存放函数的定义
在 shell 脚本或交互式 shell 中调用函数文件,格式如下:
. filename 或 source filename

示例:

代码语言:javascript复制
[root@centos8 ~]#cat functions
#!/bin/bash
#functions
hello () { 

echo Run hello Function
}
hello2 () { 

echo Run hello2 Function
}
[root@centos8 ~]#. functions
[root@centos8 ~]#hello
Run hello Function
[root@centos8 ~]#hello2
Run hello2 Function
[root@centos8 ~]#declare -f hello hello2
hello ()
{ 

echo Run hello Function
}
hello2 ()
{ 

echo Run hello2 Function
}

(4) 函数返回值

函数的执行结果返回值:

代码语言:javascript复制
使用 echo 等命令进行输出
函数体中调用命令的输出结果

函数的退出状态码:

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

(5) 环境函数

类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数。 定义环境函数:

代码语言:javascript复制
export -f function_name
declare -xf function_name

查看环境函数:

代码语言:javascript复制
export -f
declare -xf

(6) 函数参数

函数可以接受参数:

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

(7) 函数变量

变量作用域:

代码语言:javascript复制
普通变量:只在当前 shell 进程有效,为执行脚本会启动专用子 shell 进程。因此,本地变量的作用范围是当前 shell 脚本程序文件,包括脚本中的函数
环境变量:当前 shell 和子 shell 有效
本地变量:函数的生命周期结束时变量被自动销毁

注意:

代码语言:javascript复制
如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量

在函数中定义本地变量的方法:

代码语言:javascript复制
local NAME=VALUE

(8) 函数递归

函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环。 递归示例:

代码语言:javascript复制
阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有 0 的阶乘为 1,自然数 n 的阶乘写作 n!
n!=1×2×3×...×n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n
n!=n(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!

示例:

代码语言:javascript复制
#!/bin/bash
#
fact() { 

if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1

  fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源。 参考: fork 炸弹 函数实现:

代码语言:javascript复制
:(){ 
 :|:& };:
bomb() { 
 bomb | bomb & }; bomb

脚本实现:

代码语言:javascript复制
cat bomb.sh
#!/bin/bash
./$0|./$0&

2.12 一些脚本相关工具

(1) 信号捕捉 trap

代码语言:javascript复制
trap '触发指令' 信号
进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '' 信号
忽略信号的操作
trap '-' 信号
恢复原信号的操作
trap -p
列出自定义信号操作
trap finish EXIT
当脚本退出时,执行 finish 函数

示例:

代码语言:javascript复制
#!/bin/bash
trap 'echo "Press ctrl c"' int quit
trap -p
for((i=0;i<=10;i  ))
do
sleep 1
echo $i
done
trap '' int
trap -p
for((i=11;i<=20;i  ))
do
sleep 1
echo $i
done
trap '-' int
trap -p
for((i=21;i<=30;i  ))
do
sleep 1
echo $i
done

(2) 创建临时文件 mktemp

mktemp 命令用于创建并显示临时文件,可避免冲突。 格式:

代码语言:javascript复制
mktemp [OPTION]... [TEMPLATE]

常见选项:

代码语言:javascript复制
-d 创建临时目录
-p DIR或--tmpdir=DIR 指明临时文件所存放目录位置

示例:

代码语言:javascript复制
mktemp /tmp/testXXX
tmpdir=`mktemp –d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX

(3) 安装复制文件 install

install 命令格式:

代码语言:javascript复制
install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录

选项:

代码语言:javascript复制
-m MODE,默认755
-o OWNER
-g GROUP

示例:

代码语言:javascript复制
install -m 700 -o wang -g admins srcfile desfile
install -m 770 -d /testdir/installdir 

(4) 交互式转化批处理工具 expect

  expect 主要应用于自动化交互式操作的场景,借助 expect 处理交互的命令,可以将交互过程如:ssh 登录,ftp 登录等写在一个脚本上,使之自动完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率。

expect 语法:

代码语言:javascript复制
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]

常见选项:

代码语言:javascript复制
-c:从命令行执行expect脚本,默认expect是交互执行的
-d:可以输出调试信息

expect 中相关命令:

代码语言:javascript复制
spawn	启动新的进程
expect	从进程接收字符串
send	用于向进程发送字符串
interact		允许用户交互
exp_continue	匹配多个字符串在执行动作后加此命令

详细使用参考:交互式转化批处理工具 expect

2.13 数组

(1) 数组介绍

变量:存储单个元素的内存空间。 数组:存储多个元素的连续的内存空间,相当于多个变量的集合。 数组名和索引:

代码语言:javascript复制
索引的编号从0开始,属于数值索引
索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0 版本之后开始支持
bash 的数组支持稀疏格式(索引不连续)

(2) 声明数组

代码语言:javascript复制
#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
#两者不可相互转换

(3) 数组赋值

① 一次只赋值一个元素
代码语言:javascript复制
ARRAY_NAME[INDEX]=VALUE

示例:

代码语言:javascript复制
weekdays[0]="Sunday"
weekdays[4]="Thursday"
② 一次赋值全部元素
代码语言:javascript复制
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

示例:

代码语言:javascript复制
title=("ceo" "coo" "cto")
num=({ 
0..10})
alpha=({ 
a..g})
file=( *.sh )
③ 只赋值特定元素
代码语言:javascript复制
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
④ 交互式数组值对赋值
代码语言:javascript复制
read -a ARRAY

(4) 显示所有数组

代码语言:javascript复制
declare -a

示例:

代码语言:javascript复制
[root@aliyunhost01~]#declare -a
declare -a BASH_ARGC='()'
declare -a BASH_ARGV='()'
declare -a BASH_LINENO='()'
declare -a BASH_SOURCE='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -a DIRSTACK='()'
declare -a FUNCNAME='()'
declare -a GROUPS='()'
declare -a PIPESTATUS='([0]="0")'

(5) 引用数组

① 引用数组元素
代码语言:javascript复制
${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素

示例:

代码语言:javascript复制
[root@centos8 ~]#declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@centos8 ~]#echo ${title[1]}
coo
[root@centos8 ~]#echo ${title}
ceo
[root@centos8 ~]#echo ${title[2]}
cto
[root@centos8 ~]#echo ${title[3]}
② 引用数组所有元素
代码语言:javascript复制
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

示例:

代码语言:javascript复制
[root@centos8 ~]#echo ${title[@]}
ceo coo cto
[root@centos8 ~]#echo ${title[*]}
ceo coo cto
③ 数组的长度,即数组中元素的个数
代码语言:javascript复制
${ 
#ARRAY_NAME[*]}
${ 
#ARRAY_NAME[@]}

示例:

代码语言:javascript复制
[root@centos8 ~]#echo ${#title[*]}
3

(6) 删除数组

① 删除数组中的某个元素
代码语言:javascript复制
unset ARRAY[INDEX]

示例:

代码语言:javascript复制
[root@centos8 ~]#echo ${title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo ${title[*]}
ceo cto
② 删除整个数组
代码语言:javascript复制
unset ARRAY

示例:

代码语言:javascript复制
[root@centos8 ~]#unset title
[root@centos8 ~]#echo ${title[*]}
[root@centos8 ~]#

(7) 数组数据处理

① 数组切片
代码语言:javascript复制
${ARRAY[@]:offset:number}
offset #要跳过的元素个数
number #要取出的元素个数
#取偏移量之后的所有元素
{ 
ARRAY[@]:offset}

示例:

代码语言:javascript复制
[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos8 ~]#echo ${num[*]:6}
6 7 8 9 10
② 向数组中追加元素
代码语言:javascript复制
ARRAY[${ 
#ARRAY[*]}]=value
ARRAY[${ 
#ARRAY[@]}]=value

示例:

代码语言:javascript复制
[root@centos8 ~]#num[${#num[@]}]=11
[root@centos8 ~]#echo ${#num[@]}
12
[root@centos8 ~]#echo ${num[@]}
0 1 2 3 4 5 6 7 8 9 10 11

(8) 关联数组

代码语言:javascript复制
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
注意:关联数组必须先声明再调用

示例:

代码语言:javascript复制
[root@centos8 ~]#name[ceo]=mage
[root@centos8 ~]#name[cto]=wang
[root@centos8 ~]#name[coo]=zhang
[root@centos8 ~]#echo ${name[ceo]}
zhang
[root@centos8 ~]#echo ${name[cto]}
zhang
[root@centos8 ~]#echo ${name[coo]}
zhang
[root@centos8 ~]#echo ${name}
zhang
[root@centos8 ~]#declare -A name
-bash: declare: name: cannot convert indexed to associative array
[root@centos8 ~]#unset name
[root@centos8 ~]#declare -A name
[root@centos8 ~]#name[ceo]=mage
[root@centos8 ~]#name[cto]=wang
[root@centos8 ~]#name[coo]=zhang
[root@centos8 ~]#echo ${name[coo]}
zhang
[root@centos8 ~]#echo ${name[ceo]}
mage
[root@centos8 ~]#echo ${name[cto]}
wang
[root@centos8 ~]#echo ${name[*]}
mage wang zhang

关联数组参考: Shell 关联数组 Shell 中的关联数组,获取数组索引

(9) 数组脚本示例

范例:生成 10 个随机数保存于数组中,并找出其最大值和最小值

代码语言:javascript复制
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i  ));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[0]} &&  max=${nums[0]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min

范例:定义一个数组,数组中的元素对应的值是 /var/log 目录下所有以 .log 结尾的文件。统计出其下标为偶数的文件中的行数之和

代码语言:javascript复制
#!/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"

范例:shell 脚本实现冒泡排序

代码语言:javascript复制
#!/bin/bash
#a test about sort
echo "please input a number list"
read -a arrs
for((i=0;i<${#arrs[@]};i  )){ 

for((j=0;j<${#arrs[@]}-1;j  )){ 

if [[ ${arrs[j]} -gt ${arrs[j 1]} ]];then
tmp=${arrs[j]}
arrs[j]=${arrs[j 1]}
arrs[j 1]=$tmp
fi
}
}
echo "after sort"
echo ${arrs[@]}

数组参考: Shell 数组 Shell 数组操作 Shell 脚本数组用法小结 Shell 数组(详细)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

0 人点赞