shell 脚本
shell 语言特点
- 属于弱类型语言:所以定义变量时不需要指定变量类型
- 属于解释型语言:编写好的shell脚本需要指定的shell解释器才能执行;
- 仅一种变量类型:所有数据实际上都是以字符串形式处理,所有变量和数据都可以看作是字符串;
shell 变量相关
1、变量定义规则
- 定义shell变量时,如果没有赋值,则这个变量值为空。
- 变量名由数字、字母和下划线组成,且不允许使用数字开头,一般采用蛇形命名法。
例如:
# 有空格,不合法
AGE = 18
# 存在非数字、字母和下划线字符,不合法
chunk-name=fdfasfas.js
# 数字开头,不合法
8Name=tom
2、三种变量类型
- 普通变量:只对当前 Shell 或脚本有效,不会自动被子进程继承。
- 全局变量:使用 export 申明的变量,又叫作环境变量,能被当前Shell创建的子进程继承。
- 局部变量:在函数内部使用 local 申明的变量,只在函数内部生效。
例如:
# 普通变量,当前 Shell 或脚本有效
age=18
# 全局变量,,能被当前Shell创建的子进程继承。
export age=18
# 局部变量,函数内生效
get_name()
{
local name=tom
}
3、变量赋值规则
- 空格严格:变量赋值时
=
号两边不能存在任何空格,否则报错 - 引号保护:通过赋值符号(
=
)进行赋值时,如果变量值存在空格或其他特殊字符,需要使用引号保护,表示是一个整体。
例如:
NAME=TOM
# 存在空格,需要使用引号让其成为整体
NAME="John Wilson"
# 存在特殊字符,需要转义或用单引号
PRICE='$18'
4、变量展开规则
4.1 引用变量规则
通过 反引号 `` 或 $
符号可以对变量进行引用,如果变量后面紧跟着其他字母或数字,需要使用花括号 { }
可以明确界定变量名。否则shell 可能会将其误认为是变量名的一部分。
例如:
file="test"
# 通过大括号来界定变量名
echo "${file}.txt"
4.2 变量展开规则
- 不加双引号保护时:因为变量展开时,原始值中可能包含换行符等特殊字符,shell会根据 IFS
(空格、换行符、Tab)
分隔变量为多个单词。如果还含有其他一些特殊字符,例如通配符,则展开之后的结果可能还会被进一步处理。 - 加了双引号保护时:展开后的内容原样输出,不再做分词处理。
例如:变量不加引号的情况
file=$(ls -1d /home/ehigh/test/*)
echo $file
# 输出结果
/home/ehigh/test/1 /home/ehigh/test/2 /home/ehigh/test/3
echo $filename
:
- 先将变量展开,替换为具体的变量值,因为ls命令的输出是多行内容,所以原始值中包含换行符。
- shell会根据 IFS(空格、换行符、Tab) 将展开后的变量分隔为为多个单词
- shell 会将分隔后的单词作为参数传给 echo,echo命令再逐个以空格间隔打印这些单词
例如:变量加双引号的情况
file=$(ls -1d /home/ehigh/test/*)
echo "$file"
# 输出结果
/home/ehigh/test/1
/home/ehigh/test/2
/home/ehigh/test/3
echo "$file"
- 先将变量展开,替换为具体的变量值,因为ls命令的输出是多行内容,所以原始值中包含换行符。
- 因为存在双引号,所以shell不会根据IFS来对变量值中的换行符、空格进行分词操作
- shell 会将整个字符串传输给echo,所以echo就实现了原样输出。
5、变量销毁方式
-
自动销毁:脚本执行完毕后,运行该脚本的 Shell 进程也会结束,所有在该进程中定义的变量都会自动释放,不会再保留在系统中。
-
手动销毁:手动使用
unset
删除变量。在交互式 Shell 中手动定义的变量会一直存在,此时就需要手动销毁。
例如:销毁变量
name=tom
# 销毁变量
unset name
shell 内置变量
1、特殊变量( _ )
_
变量:变量名为_
时,表示“不关心这个变量的值”。如果用户有多个输入,就可以可以利用这一约定来丢弃不需要的输入字段。
2、位置变量
位置变量是shell内置的一些变量,不用定义可以直接使用$来进行引用,常见的位置变量有这些:
- $0:表示的是当前脚本的名称
- $n:n大于1,传递給脚本的第n个参数
- $#:一共有多少个参数传递给了脚本
- $@: 传递给脚本的所有参数
- $$:当前shell脚本的PID
- $?:上一个命令的退出状态。如果命令执行成功,则退出状态为 0
例如:获取当前脚本所在的路径
current_path=$(cd $(dirname $0); pwd)
3、环境变量
3.1 查看当前 Shell 环境中的环境变量
环境变量是定义在当前环境(进程)中、并可以被当前进程及其所有子进程访问和继承的特殊变量。
操作系统
└── Shell进程(当前) ← env查看的是这一层
└── 子进程(脚本、程序)← 继承上面环境变量
例如:查看当前shell进程的环境变量
# 直接执行env命令即可
env
3.2 和用户相关的环境变量
- SHELL:当前用户使用的默认shell路径。例如:SHELL=/bin/bash
- USER :当前登录的用户名。例如:USER=root、
- HOME:当前用户的家目录路径。例如:HOME=/root
- EDITOR:打开某个文件时,默认文本编辑器
3.3 和系统配置相关的环境变量
- PATH:指定以相对路径执行某个可执行程序的搜索路径,多个路径以
:
分隔,并且搜索时不会递归查找,只会查找所指定路径下是否存在该可执行文件。 - LANG :指定系统使用的语言环境。格式为:“语言代码_国家或地区.字符集”,LANG变量常用的两种语言环境。中文环境:
zh_CN.UTF-8
,英文环境:en_US.UTF-8
。 - IFS:内部字段分隔符(Internal Field Separator)的缩写,定义Shell在进行分词或字段拆分时使用的字符。当shell在处理命令或变量展开时,会依据
IFS
中的字符来确定如何将一个字符串拆分为多个字段。IFS 默认指定的分隔符是空格、制表符(tab)和换行符的组合。
例如:设置PATH变量
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
例如:设置当前环境为应为英文环境
export LANG=en_US.UTF-8
例如:查看当前IFS的值
printf "%q\n" "$IFS"
# 输出信息
# 空格表示空格字符
# `\t`表示Tab字符
# `\n`表示换行字符
shell 字符相关
1、会被 shell 解释的特殊字符
1.1 通配符类
-
*
:表示匹配任意多个字符 -
?
:表示匹配单个任意字符 -
[]
:表示匹配范围内的单个字符
1.2 重定向符和管道符
-
>
:输出重定向,覆盖原文件。 -
>>
:追加输出重定向,内容追加到文件尾部。 -
<
:输入重定向。 -
|
:管道符,连接两个命令,左边命令的输出作为右边命令的输入。
1.3 命令控制符
-
;
:在一行内执行多个命令时,分隔多个命令,让多个命令顺序执行,如ls; pwd
。 -
&
:命令后台执行,如sleep 60 &
。 -
&&
:前面的命令执行成功后再执行后面的命令。 -
||
:前面的命令执行失败时再执行后面的命令。
1.4 变量与命令控制换类
$
:变量引用或命令替换,如$HOME
或$(ls)
。command
或$()
:命令替换,执行命令后替换为结果。${}
:变量扩展,如${var}
。~
:代表用户家目录路径。#
:用于在Shell中表示注释,后面文字不被执行。
1.5 引号和转义符
-
'
(单引号):使引号内所有内容保持字面量,不解析变量。 -
"
(双引号):解析变量,但不解析大部分特殊字符。 -
\
:转义符,去除特殊字符本身的特殊含义。
2、三种引号的作用
单引号和双引号的作用是在字符串中存在空格或会被shell解释的特殊字符时,确保字符串能被做为一个整体进行处理,避免shell根据IFS来拆分这个字符串。但是单双引号在处理特殊字符时有所不同。
2.1 单引号的特点
单引号中的任何内容都会原样输出,不会被shell进行解释,但是单引号保护的字符串中不能存在单引号,因为单引号中的内容不会被shell解释,所以加了转义字符也没用。
2.2 双引号的特点
双引号中存在一些特殊字符仍然会被shell解释:
- 美元符:shell 会将美元符号( $) 解释为变量引用符号,美元符号后面的字符串会被当作变量进行变量引用。
- 反引号:反引号(``)效果等同于 $ 字符,也会被shell解释为变量的引用符。
- 反斜杠:反斜杠(\)会被shell解释为转义字符,例如:“\n” 会被解释为换行符。
- 双引号:shell在遇到双引号 (") 时认为这是字符串的开始或结束,如果双引号中的字符串包含双引号,则shell无法确定哪一个是字符串的结束,所以这种情况需要对字符串中的双引号进行转义。
例如:双引号中存在双引号需要转义
echo "\"Hello World\""
例如:单引号和双引号嵌套
NAME=TOM
# My Name is 'TOM'
echo "My Name is '${NAME}'"
说明:上面说了单引号中的任何内容都会原样输出,但是这里的单引号在双引号内,双引号内的单引号就是一个普通字符,双引号内的$会被shell解释,所以能展开变量。
2.3 反引号的特点
反引号的作用和$
一样,是变量引用的作用。反引号内的字符当作一个命令执行,将commnd
替换为命令的输出。
3、shell 三种括号的作用
3.1 大括号的作用
3.1.1 单个大括号场景
命令块(匿名函数):用大括号 { }
将多条命令组合在一起时,这些命令会在当前 Shell 中依次执行,整个命令块的退出状态码就是最后一条命令的退出状态码。
格式:{ command1; command2; }
-
要求括号两边必须有空格
-
每个指令都需要以分号
;
结尾,包括最后一个指令。
例如:
花括号扩展:用于生成一组字符串。将若干字符串用逗号分隔放在大括号中,Shell 会将其扩展为多个独立的字符串。
- 格式:
{ str1,str2,str3 }
例如:
echo {a,b,c,d,e}.txt
# a.txt b.txt c.txt d.txt e.txt
范围扩展:用于生成有序的数字或字母序列。通过指定起始和结束值,Shell 会生成从起始值到结束值的序列。
- 格式:
{ start..end }
例如:删除指定后缀的日志文件
sudo rm -f mysql-bin.0018{37..79}
3.1.2 $ 和 { } 组合
-
作用:变量定界符,表示变量的起始位置。 防止shell将变量后面紧跟着其他字母或数字认为是变量的一部分。
-
格式:
${command}
3.2 中括号的作用
3.2.1 单个中括号
- 作用一:是test 命令的别名。用于实现条件测试,返回值是命令的退出状态码
- 作用二:是shell 的通配符。匹配特定的字符集或字符范围
3.2.2 $ 和 [] 组合
- 实现算术扩展,执行基本的算数运算,参数是一个不带赋值符的表达式,但是已经被
$(( ))
所取代
3.3 小括号的作用
3.3.1 单个小括号场景
-
实现命令组合。在当前进程下开启一个子进程来执行括号内的指令,当前进程的环境信息会被子进程继承,但是子进程的变量和更改不会影响当前进程,整个的退出状态码是最后一个命令的退出状态码。
-
格式:
( cd /some/dir && ls )
(如果存在多个命令命令之间如果在同一行通常需要用分号,最后一个命令的分号可选。)
例如:开启一个子进程在后台执行命令组合。
( ping -c1 $ip3 &> /dev/null
[ $? -eq 0 ] && echo "$ip3 is up" || echo "$ip3 is down"
) &
3.3.2 两个小括号嵌套:
- 是shell中的一个内置命令,一般是用来进行整数的逻辑判断的。例如比较两个整数的大小等。
例如:
if (( 3 > 2 )) ;then
echo "3 > 2"
fi
3.3.3 < 和 ( ) 组合使用:
- 当 ( ) 和 < 组合时,叫作进程替换。这种语法会将小括号里面命令的输出存储到一个文件描述符(通常是一个命名管道或临时文件)中,然后其他命令可以像读取普通文件一样读取命令的输出。
- 格式:<(command) — 可以假想这就是一个匿名文件,某些命令可以像读取普通文件一样读取该文件的内容。
例如:
# 相当于cat 直接读取 ‘<(ls)’ 这个匿名文件做为其标准输入
cat <(ls)
shell Heredoc 语法
1、Heredoc 的作用
Heredoc 是 Here Document 的缩写,是shell中的一种特殊的语法结构,可以理解为一种特殊的输入重定向。用来将一个多行的字符串直接重定向到某个命令的标准输入中,而不需要通过文件或管道等中间步骤。
- 语法格式:
command <<DELIMITER
command
:需要能接收标准输入的命令,例如cat等。DELIMITER
:是固定搭配,<< 后必须跟一个定界符。定界符可以随意定义,一般使用EOF表示结束
例如:
cat > /etc/rsync.pas <<EOF
rsync:123456
EOF
<<EOF
:会将指导遇到定界符EOF的多行内容做为某个命令的标准输入。cat > /etc/rsync.pas
:通过Heredoc 获取到的标准输入,然后重定向到指定文件
2、Heredoc 定界符
定界符可以随意定义,一般使用EOF表示结束,但是定界符是否用引号括起来,对文件的处理有不同。
-
定界符无引号:输入的多行字符串中,如果包含一些特殊字符或变量引用,会被shell解释后再重定向到指定命令的标准输入中。
-
定界符有引号:使用双引号或单引号将定界符括起来后,会将内容视为纯文本,不会进行任何拓展。
例如:
# 变量会被展开,然后在做为cat的标准输入
cat > /etc/rsync.pas <<EOF
rsync:$passwd
EOF
# 所有内容都内容视为纯文本,不会进行任何拓展。
cat > /etc/rsync.pas <<`EOF`
rsync:$passwd
EOF
shell 字符串相关
shell 只有字符串这一种类型,所有的变量或变量值都可以看作是字符串。
1、获取字符串长度
如果要获取字符串的长度,可以在变量展开的时候,在变量名前面加上 # 来得到。
- 格式:${#variable}
说明:这种方式获取字符串长度时,需要是一个变量名,在变量展开时得到,而不是对单纯的字符串操作。
例如:
if [ ${#new_mysql_passwd} -lt 8 ] || [ ${#new_mysql_passwd_confirm} -lt 8 ]; then
echo "密码长度至少 8 位,请重新输入。"
FI
2、删除字符串前缀
删除字符串前缀是从字符串的开始部分开始匹配,去掉匹配掉的,剩下的做为一个新字符串。
2.1 最短匹配原则
找到满足匹配条件pattern部分的内容就停止查找,然后截取掉pattern的部分,余下的就做为一个新字符串。
格式:var=${variable#pattern}
例如:
NAME="lct_net_manager.tb_err_future_loc_20%"
# 截取到的值是tb_err_future_loc_20%
tb_name=${NAME#*.}
说明:单个 # 代表是最短匹配,从前向后找,找到满足条件的就终止。这里的匹配条件是找任意字符开头的,直到遇到字符.的部分截取掉,剩下的做为新字符串。
2.2 最长匹配原则
找到满足匹配条件pattern部分的内容后,还会继续找,知道找到最后一个满足条件的,然后截取掉pattern的部分做为一个新字符串。
格式:var=${variable##pattern}
3、删除字符串后缀
从字符串的末尾部分开始匹配,去掉匹配掉的,剩下的做为一个新字符串。
3.1 最短匹配原则
从字符串的末尾向前查找,找到符合匹配条件的内容就停止匹配,然后将该部分内容截掉后,剩下的做为一个新字符串。
格式:var=${variable%pattern}
3.2 最长匹配原则
从字符串的末尾向前查找,找到符合匹配条件的内容不会停止,而是一直找到字符串的起始字符,将离末尾最远部分满足匹配条件的内容截取掉,剩下的做为一个新字符串。
格式:var=${variable%%pattern}
例如:
NAME="lct_net_manager.tb_err_future_loc_20%"
# 删除从 . 开始的所有字符,截取的字符串是lct_net_manager
db_name=${NAME%%.*}
4、匹配字符串内容
通过 shell 中的 [[ ]]
可以检查字符串中是否包含特定的字符。
4.1 通配符形式匹配特定内容
- 格式:
[[ string == *substring* ]]
- 特点:如果使用
==
时,则等号右边的某些元字符会被解释为shell的通配符。
例如:
# 如果字符串中以 .tar.gz 结尾
if [[ "${DAILY_BACKUP_NAME}" == *.tar.gz ]];then
4.2 正则形式匹配特定内容
- 格式:
[[ string =~ *substring* ]]
- 特点:如果使用
=~
时, 等号右边的某些元字符会被解释为正则表达式元字符,并且默认支持的是扩展正则表达式。
例如:
[[ "$PORT" =~ ^[0-9]+$ ]]
shell 算数运算
shell中虽然只有字符串一种数据类型,但是可以使用数字型字符串(仅包含数字字符0-9)来表示和操作数字,然后通过相应的工具和命令来实现进行算数运算;
- 表达式:编程中的表达式是由常量、变量、操作符和函数等组成的代码片段,可以通过计算产生一个值;
- 运算表达式:不包含赋值符号
=
的表达式 - 赋值表达式:包含赋值操作符
=
的表达式
1、使用 let 关键字
let 是bash中的一个关键字,利用let 可以实现赋值表达式的算数计算。
格式:let "赋值表达式"
特点:
- 自动解析变量:变量前可以不加$符号对变量进行引用,只能计算整数。
- 空格不严格:算数表达式需要用引号保护,参数和操作符之间可以存在空格,也可以没有
- 无返回值:直接进行算数计算,将结果赋值给对应的变量。
- 只能计算整数,不能计算小数。
例如:
a=1
b=2
let "c=a+b" 输出3
2、expr 命令使用
expr 是Linux中的一个命令,可以实现对运算表达式的算数计算。
格式:expr "运算表达式"
特点:
- 有返回值:返回值是计算结果
- 空格严格:参数和操作符之间必须要有空格分隔
- 不能解析变量:变量需要使用$符引用
- 只能计算整数,不能计算小数。
例如:
a=1
b=2
echo "$(expr $a + $b)"
3、$(()) 使用说明
$(()) 是shell的一种语法格式,可以实现对运算表达式的算数计算。
格式:$(( 运算表达式 ))
特点:
- 有返回值:返回值是计算结果的值
- 自动解析变量:变量前可以不加$符号对变量进行引用,加了也没错
- 空格不严格:运算符和参数之间可以有空格,也可以没有
例如:
echo $((3 + 2)) # 5
4、bc 命令使用
bc 命令可以实现交互式,也可以实现非交互式操作。shell脚本中一般使用非交互式模式,通过构建一个运算表达式,然后做为bc的输入,bc会输出计算结果。
bc 除了可以进项算出运算外,还可以对数字进行逻辑判断。
非交互式格式:
- 整数运算:
echo "运算表达式" | bc
- 小数运算:
echo "scale=num; 运算表达式" | bc -l
(通过salce来指定要保留的小数位数) - 逻辑运算:
echo "逻辑表达式" | bc
特点:
1、加上-l
选项后可以计算小数,通过scale
可以指定保留的小数位置。
2、具有返回值,如果是算数运算,则返回值就是计算结果,如果是逻辑运算,则返回0或1
3、空格不严格:构建的表达式中,参数和运算符号之间可以存在空格,也可以没有。
例如:
# 整数运算
echo "3 + 3"|bc
# 进行逻辑运算
$(echo "$util_avg > 90" | bc -l) ))
# 小数计算
$(echo "scale=2; ${iowait_sum} + ${iowait}" | bc -l)
shell 逻辑运算
shell 中没有直接的布尔值类型,但是可以根据命令的退出状态码,即0 表示真,非 0 表示假来实现逻辑判断。
1、短路与(&&)
shell 中的 & 是后台符, 用于将命令放入后台执行,使脚本或终端可以继续执行其他任务,而不会等待该命令完成。实现与运算的是 && 符。
短路与(&&)特点:
- 当两个条件都为真时,整个表达式为真。只要有一个条件为假,结果就是假。
- 如果第一个条件为假,则不会再评估第二个条件,因为整个表达式已经确定为假了。
- 如果第一个条件为真,才会继续评估第二个条件。
例如:
# 前面的执行成功,才会执行后面的
[ $(id -u) -ne 0 ] && echo "need root run this script" && exit 1
2、短路或(||)
shell中的 | 是管道符,用于将前一个命令的输出作为输入传递给下一个命令,实现数据的逐步处理。shell中实现逻辑运算的是 || 符。
短路或(||)特点:
- 当至少一个条件为真时,整个表达式为真。只有当所有条件都为假时,结果才是假。
- 如果第一个条件为真,则不会再评估第二个条件,因为整个表达式已经确定为真了。
- 如果第一个条件为假,才会继续评估第二个条件。
例如:端口判断
[ "$https_port" -eq 443 ] || ( [ "$https_port" -gt 1024 ] && [ "$https_port" -lt 65536 ] );
3、取反运算(!)
取反运算就是将条件的真假值取反。如果条件为真,使用 !
后结果为假;如果条件为假,使用 !
后结果为真。
例如:判断目录是否存在
if [ -d /path/dir ];then
echo "exsit"
fi
shell 条件判断
1、test 和 [
test 是早期shell中的一个内置命令,通过指定的判断条件来返回一个退出状态码(返回 0 表示判断为真,返回非 0 表示判断为假)。现在的shell 一般使用的是 [
命令,其更加简洁和容易阅读。在功能上几乎完全和test
相同,可以理解为 [
就是test命令的别名。
命令格式:
test option expression
[ option expression ]
命令特点:
[
命令使用时,还需要加一个]
,]
仅作为语法符号来表示条件表达式的结束位置。- 左右方括号
[
和]
必须用空格与表达式隔开,否则会报错。
1.1 文件判断
-e filename
:检查文件是否存在-f filename
:检查是否为普通文件-d filename
:检查是否为目录-s filename
:检查文件是否非空
例如:
if [ ! -d "${BACKUP_PATH}" ];then
mkdir -p "${BACKUP_PATH}" && echo "$(date +'%F %T') ${BACKUP_PATH} 创建成功" | tee -a ${LogFile};
fi
1.2 整数判断
-eq
:检查两个数值是否相等-ne
:检查两个数值是否不等-gt
:检查第一个数值是否大于第二个数值-lt
:检查第一个数值是否小于第二个数值-ge
:检查第一个数值是否大于等于第二个数值-le
:检查第一个数值是否小于等于第二个数值
例如:
if [[ $(date +%u) -eq 7 ]]; then
# 今天是周日
START_OF_WEEK=$(date +'%Y%m%d')
fi
1.3 字符串判断
=
:检查字符串是否相等,shell中,= 和 ==
都是等价。!=
:检查字符串是否不等。-z string
:检查字符串是否为空,为空则为真。-n string
:检查字符串是否非空,非空则为真。
例如:字符串非空则为真
if [ -n "${LATEST_FULL_BACKUP}" ]; then
LATEST_FULL_BAK_NAME=$(basename "${LATEST_FULL_BACKUP}")
fi
1.4 浮点数判断
浮点数判断不能使用以上两个命令,需要使用bc命令才行,构造表达式时,通过scale来指定小数的位数。
例如:
a=1.1
if echo "$a > 2" | bc -l > /dev/null; then
echo "a >2"
fi
2、(( )) 命令
(( )) 命令是shell中的一个内置命令,可以对整数进行逻辑判断,该命令会返回一个退出状态码(返回 0 表示判断为真,返回非 0 表示判断为假)。通过退出状态码就可以实现对整数的判断。
常用操作符:
- 相等 (
==
) 、不等 (!=
) - 大于 (
>
) 、小于 (<
) - 大于等于 (
>=
) 、小于等于 (<=
)
例如:
a=1
b=2
if ((a >b)) ;then
echo "a > b"
else
echo "a < b"
fi
shell 数组相关
shell 支持普通数组和关联数组这两种类似的数组。
-
普通数组:通过数字索引来管理元素,索引是从0开始的连续整数。
-
关联数组:允许使用字符串作为索引,可以自定义数组的索引。
1、申明数组规则
数组名和shell普通变量名一样,也是由数字、字母和下划线组成,且不能以数字开头,否则会报错。
申明普通数组:
- 标准格式:declare -a 数组名
- 简化格式:数组名=()
申明关联数组:
- 标准格式:declare -A 数组名(申明关联数组不能简化,只能用标准格式)
2、数组元素赋值
2.1 普通数组赋值
定义时赋值:定义数组的同时直接赋值时,会自动根据IFS指定的分隔符(空格、制表符和换行符)作为分隔符来划分每个元素
例如:
# IFS 会按照空格来进行分词,此时该数组有三个元素
PATTERNS=(
"lct_net_manager.tb_err_future_loc_20%"
"position_ehcommon.tb_blood_pressure_history_%"
"only_door.tb_person_location_history"
)
逐个元素赋值:通过指定数组的索引,然后进行赋值。
例如:
arry=()
arry[5]=18
echo ${arry[5]}
命令替换赋值:通过将命令输出作为数组元素,命令输出后也会受IFS的影响,shell会根据IFS定义的分隔符来进行分词处理,每个单词就是数组的一个元素。
例如:
arry=()
arry=($(ls -d /root/*))
for name in ${arry[@]} ;do
echo $name
done
2.2 关联数组数组
因为关联数组的索引是自定义的,并非普通数组那样是自增的整数,所以给关联数组赋值时,需要指定对应的索引,索引一般用引号保护。避免包含特殊字符或空格导致的错误。
格式为:["key"]="value"
例如:
# 必须显式申明
declare -A arry
arry=(["name"]=tom [age]=18 [sex]=man [number]=112)
echo ${arry[name]}
例如:
key_size=$(redis-cli -a redhat -n ${db_num} MEMORY USAGE "$key_name")
# redis的键名做为key 键值作为value
key_arry["$key_name"]=$key_size
3、操作数组元素
数组的常见操作:
- 获取指定元素的值:
${数组名[索引]}
- 获取数组所有元素:${arry_name[@]}
- 获取数组所有索引:${!arry_name[@]}
- 获取数组的元素个数:${#arry_name[@]}
- 设置数组的默认值:${arry[@]:-defaults}
- 编译数组所有元素:通过for循环实现
例如:便利某个数组的所有元素
for i in "${!fruits[@]}"; do
echo "索引 $i 对应的元素是:${fruits[i]}"
done
例如:如果数组为空或未定义,就返回对应值
${arry[@]:-22 80 8000 8001 3306 9100 9001 1883 1884 9802}
shell 语句结构
1、分支结构
1.1 if 结构
说明:if 语句中的条件必须是一个能返回退出状态的命令
格式:
# **单分支**
if 条件; then
Command;
fi
# **双分支**
if 条件; then
Command;
else
Command;
fi
# **多分支**
if 条件; then
Command;
elif 条件; then
Command;
else
Command;
fi
例如:
# [等价于命令test,if会根据这个命令的退出状态来进行分支选择
if [ 0 -ne 0 ]; then
echo "条件为真"
else
echo "条件为假"
fi
# 例如
if systemctl restart mysql ; then
echo "mysql 重启成功"
else
echo "mysql 重启失败"
fi
1.2 case 结构
在 shell 的 case 结构中,不需要像其他编程语言(如 C 或 Java)那样使用 break。原因是 shell 的 case 语句在匹配到一个模式后,会自动退出整个 case 块,不会继续执行后面的分支。
格式:
case 变量 in
模式1)
# 当变量匹配模式1时执行的代码
break
;;
模式2)
# 当变量匹配模式2时执行的代码
break
;;
*)
# 以上模式都不匹配时执行的代码
break
;;
esac
例如:实现一个选择菜单
#!/bin/bash
read -p "请输入操作(start、stop 或 restart): " action
case "$action" in
start)
echo "系统正在启动..."
;;
stop)
echo "系统正在关闭..."
;;
restart|reload)
echo "系统正在重启..."
;;
*)
echo "无效的操作,请输入 start、stop 或 restart。"
;;
esac
2、循环结构
2.1 for 循环
2.1.1 普通结构
格式:
for 变量 in 一个列表(使用IFS定义的分隔符分隔) ;do
command;
done
例如:
#!/bin/bash
for name in tom bob alice ;do
echo $name
done
2.1.2 for 结合 (( ))
这种方式一般用于处理算术表达式和整数运算
格式:
for ((表达式)) ;do
command;
done
例如:实现乘法表
#!/bin/bash
for (( i=1; i<=9; i++ )) ;do
for (( j=1; j<=i; j++ ));do
echo -ne "$i * $j = $(expr $i \* $j) \t"
if [ $i -eq $j ];then
echo
fi
done
done
- 初始化表达式:在循环开始前执行,通常用于初始化循环变量。
- 终止条件表达式:在每次循环迭代之前检查。如果条件为真(即非零值),则继续循环;
- 步进表达式:用于更新循环变量的值。
2.2 while 循环
while循环的条件必须是一个能返回退出状态的命令
2.2.1 while 普通结构
格式:
while 条件 ;do
# command
done
例如:
# true是shell中的一个特殊命令,主要作用是返回一个真值,即无论何时运行true命令,它都会返回一个成功的退出状态
while true ; do
read -r -p "请输入mosquito_1884新密码: " mqtt1884_passwd
read -rp "再次输入mosquito_1884新密码: " mqtt1884_passwd_confirm
if [ -z "${mqtt1884_passwd}" ] || [ -z "${mqtt1884_passwd_confirm}" ];then
echo "输入无效,密码不能为空。"
elif [ ${#mqtt1884_passwd} -lt 8 ] || [ ${#mqtt1884_passwd_confirm} -lt 8 ]; then
echo "密码长度至少 8 位,请重新输入。"
elif [ "${mqtt1884_passwd}" != "${mqtt1884_passwd_confirm}" ]; then
echo "两次输入的密码不一致,请重新输入"
else
break
fi
done
2.2.2 while 结合 read
while 循环结合 read 命令一般用于逐行读取文件或命令的输出,并对每一行进行处理。
格式:
while read [option] Var ;do
# commmand
done
例如:
#!/bin/bash
while read -rp "请输入数字: " number ;do
echo $number
done
#
请输入数字: 1
1
请输入数字: 2
2
请输入数字: 3
3
请输入数字: ^C
2.2.3 while 结合 read 和 |
当某个命令有多行输出时,通过管道符将输出做为read的输入,从而实现逐行处理,然后通过while来定义处理的指令。
但是通过这种方式,管道符后面的while语句块会启动也给子shell来执行,所以如果涉及到对父shell中变量的处理,则子shell不能改变父shell的值。
格式:
command | while read [option] vAR ;do
# command
done
例如:
#!/bin/bash
sum=0
ls -d /home/* | while read dir ;do
sum=$((sum+1))
echo $dir
echo $sum
done
echo $sum # 输出0,因为子shell的修改不会影响父shell
2.2.4 while 结合 read 和 <()
"<()"
叫作进程替换,用于将括号中命令的输出存储到一个文件描述符(通常是一个命名管道或临时文件)中,然后其他命令可以像读取普通文件一样读取命令的输出。常用于那些需要文件名参数的命令
通过 进程替换结合while 使用,就不会像使用管道符一样开启子进程的问题。再通过 read 来逐行读取内容给while中的指令处理。
格式:
while read 变量名 ;do
# command
done < <(command)
例如:
while read -r device _ _ _ _ r_await _ _ _ _ _ w_await _ _ _ _ _ _ _ aqusz util ;do
echo "Device: $device, r_await: $r_await, w_await: $w_await"
# 将不同设备的信息分别存入数据
done < <(iostat -dx 1 1 | grep -Ev '^Linux|^Device|^$')
2.3 select 循环
通过 select 可以在shell中用于生成简单交互式菜单的命令,让用户通过输入选项编号来选择其中之一。select 一般结合 PS3变量使用,通过PS3变量可以指定 select 命令的提示符,让用户知道该输入选项编号。
格式:
select 变量 in 一个列表 ;do
# command
done
例如:实现一个选择菜单
PS3="请选择指定序号:"
select opt in "MySQL" "Redis" "Mqtt1884" "Mqtt1883" ;do
case $opt in
"MySQL")
set_mysql
break # 这里的break用于退出select循环
;;
"Redis")
set_redis
break
;;
"Mqtt1884")
set_mqtt1884
break
;;
"Mqtt1883")
set_mqtt1883
break
;;
*)
echo "无效的选择,请重试"
;;
esac
done
2.3 break 和 continule
- break 命令:终止整个循环。当执行到
break
命令时,循环立即结束,程序控制流跳出循环体,继续执行循环后面的代码。 - continue 命令:跳过当前循环中余下的命令,立即进入下一次循环迭代。当执行到
continue
命令时,当前迭代剩下的命令不会执行,循环直接进入下一次迭代的判断。
shell 函数
shell中的函数本质上就是一个语句块,它将一系列命令封装起来,使得可以通过函数名来重复调用这些命令。
1、定义函数
完整格式:
function fun_name()
{
command;
}
简写格式:
# 省略小括号:
function fun_name
{
command;
command;
}
# 省略function 关键字:
fun_name()
{
command;
command;
}
2、调用函数
说明:shell 脚本是从上到下依次执行的,如果在调用函数之前没有定义,shell 会提示“未找到命令”或类似的错误。所以需要先定义函数才能调用函数。
- 无参调用:直接通过函数名即可调用函数
- 传参调用:函数名后面跟参数,参数一般使用空格分隔。函数内通过
$1
、$2
等来引用这些参数
3、函数返回值
调用函数后,命令状态退出码($?) 就是函数的返回值,默认情况下函数的返回值就是最后一条命令执行状态的退出码,也可以退出return 命令来指定函数的退出状态码。
3.1 return 命令
格式:return [0-255]
作用:
- 函数退出状态码:在函数的末尾可以通过return 来定义函数的退出状态码。
- **退出函数执行:**如果在函数的其他位置使用,则当函数执行到
return
时,会立即退出函数并返回控制权给调用者,函数后续的指令不会被执行。
说明:return 命令一般在函数内部使用。
shell read 命令
通过 read
命令可以从标准输入(通常是键盘)读取一行数据,并将其存储到一个或多个变量中。
说明:当read 指定多个变量的时候,shell默认会根据IFS定义的分隔符来对用户的标准输入字符串进行分词处理,然后赋值给对应位置的变量。
格式:read [option] var1 .. varn
常用选项:
-p
:在读取输入前输出提示信息,例如: -p “请输入密码”-a
:将用户的多个输入,以使用IFS
(内部字段分隔符)中的字符来拆分输入存入数组。例如:read -a words-n
:只读取指定的字符数就结束,不必等到回车键。例如: -n 1 就只会读取用户输入的第一个字符,然后自动结束。-r
:关闭反斜杠转义,使用户输入的反斜杠保持原样
例如:
read -rp "请输入当前 redis 的密码(default: eHIGH2014): " current_redis_passwd