shell 脚本语言的基本用法

shell 脚本的用途

  • 将简单的命令组合完成复杂的工作,自动化执行命令,提高工作效率
  • 减少手工命令的输入,一定程度上避免人为错误
  • 将软件或应用的安装及配置实现标准化
  • 用于实现日常性的,重复性的,非交互式的运维工作,如:文件打包压缩备份,监控系统运行状态并实现告警等

shell 脚本基本结构

shell是基于过程式、解释执行的语言。

shell脚本是包含一些命令或声明,并符合一定格式的文本文件。

一个shell脚本文件中主要包含以下内容

  • 各种系统命令的组合
  • 数据存储:变量、数组
  • 表达式:a + b
  • 控制语句:if

格式要求:首行shebang机制

1
2
3
4
5
#!/bin/bash 
#!/usr/bin/python
#!/usr/bin/perl
#!/usr/bin/ruby
#!/usr/bin/lua

shell脚本创建过程

  1. 用编辑器创建新文件,首行必须是 shell 声明(shebang),编辑完成后保存退出
  2. 添加可执行权限
  3. 运行脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 新建文件
[root@rocky86 ~]# vim test.sh
#!/bin/bash
echo "hello world"

# 加可执行权限
[root@rocky86 ~]# chmod +x test.sh

# 执行,相对路径
[root@rocky86 ~]# ./test.sh
hello world

# 执行,绝对路径
[root@rocky86 ~]# /root/test.sh
hello world

shell 脚本注释规范

一般要注明作者,创建(修改)时间,文件名,版本号,该程序的功能及作用,目地,版权信息,作者联系方式等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky86 ~]# vim test.sh
#!/bin/bash
#
# ****************************************************
# Author: jose
# QQ: 123456
# Date: 2022-08-16
# FileName: test.sh
# URL: http://www.magedu.com
# Description: test
# Copyright(C): 2022 All right
# ***************************************************

echo "hello world"

配置vim 自动生成注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@rocky86 ~]# vim .vimrc
set nu
set ts=4
autocmd BufNewFile *.sh exec ":call SetTitle()"
func SetTitle()
if expand("%:e") == 'sh'
call setline(1,"#!/bin/bash")
call setline(2,"# ")
call setline(3,"# ****************************************************")
call setline(4,"# Author: jose")
call setline(5,"# QQ: 123456")
call setline(6,"# Date: ".strftime("%Y-%m-%d"))
call setline(7,"# FileName: ".expand("%"))
call setline(8,"# URL: http://www.magedu.com")
call setline(9,"# Description: test")
call setline(10,"# Copyright(C): ".strftime("%Y")." All right")
call setline(11,"# ***************************************************")
call setline(12,"")
call setline(13,"")
call setline(14,"")
endif
endfunc

第一个 shell 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# vim hello.sh
#!/bin/bash
#
# ****************************************************
# Author: jose
# QQ: 123456
# Date: 2022-08-16
# FileName: test.sh
# URL: http://www.magedu.com
# Description: test
# Copyright(C): 2022 All right
# ***************************************************

# 经典写法
echo "hello, world"
# 流行写法
echo 'Hello, world!'

执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@rocky86 ~]# bash hello.sh 
hello, world
Hello, world!

# 管道
[root@rocky86 ~]# cat hello.sh | bash
hello, world
Hello, world!

# 重定向
[root@rocky86 ~]# bash < hello.sh
hello, world
Hello, world!

# 重定向
[root@rocky86 ~]# bash <<< $(cat hello.sh)
hello, world
Hello, world!

[root@rocky86 ~]# chmod +x hello.sh

# 绝对路径
[root@rocky86 ~]# /root/hello.sh
hello, world
Hello, world!

# 相对路径
[root@rocky86 ~]# ./hello.sh
hello, world
Hello, world!

# 当前进程中执行
[root@rocky86 ~]# . hello.sh
hello, world
Hello, world!

# 子进程中执行
[root@rocky86 ~]# . /hello.sh
hello, world
Hello, world!

本地执行远程脚本

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# curl http://10.0.0.157/test.sh -s | bash
hello, world
Hello, world!

[root@rocky86 ~]# curl http://10.0.0.157/test.sh 2>/dev/null | bash
hello, world
Hello, world!

[root@rocky86 ~]# wget -qO - http://10.0.0.157/test.sh | bash
hello, world
Hello, world!

在远程主机运行本地脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# cat test2.sh 
#!/bin/bash

hostname -I

# 本地执行
[root@rocky86 ~]# bash test2.sh
10.0.0.150

# 远程主机上执行
[root@rocky86 ~]# ssh root@10.0.0.157 /bin/bash < test2.sh
root@10.0.0.157's password:
10.0.0.157

范例:备份脚本

1
2
3
4
5
#!/bin/bash
echo -e "\033[1;32mStarting backup...\033[0m"
sleep 2
cp -av /etc/ /data/etc`date +%F`/
echo -e "\033[1;32mBackup is finished\033[0m"

shell 脚本调试

写一个脚本,获取主机系统信息,包括CPU型号,内存大小,硬盘大小,操作系统版本这四个指标

第一个版本

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# vim sysinfo-v1.sh
#!/bin/bash
lscpu | sed -nr 's/^Model name: +(.*)/\1/p'
cat /proc/meminfo | head -n 1 | tr -s " " | cut -d" " -f2,3
lsblk /dev/sda | grep "^sda" | tr -s " " | cut -d" " -f4
cat /etc/os-release | sed -nr "s/^VERSION=\"(.*)\"/\1/p"

[root@rocky86 ~]# bash sysinfo-v1.sh
11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
1828236 kB
200G
8.6 (Green Obsidian)

第二个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

echo -e "CPU \c"
lscpu | sed -nr 's/^Model name: +(.*)/\1/p'

echo -e "Mem \c"
cat /proc/meminfo | head -n 1 | tr -s " " | cut -d" " -f2,3

echo -e "DISK \c"
lsblk /dev/sda | grep "^sda" | tr -s " " | cut -d" " -f4

echo -e "OS \c"
cat /etc/os-release | sed -nr "s/^VERSION=\"(.*)\"/\1/p"

[root@rocky86 ~]# bash sysinfo-v2.sh
CPU 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
Mem 1828236 kB
DISK 200G
OS 8.6 (Green Obsidian)

第三个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

echo -e "========================= sysinfo begin ================\n"

echo -e "CPU \c"
echo -e "\E[1;32m `lscpu | sed -nr 's/^Model name: +(.*)/\1/p'` \E[0m"

echo -e "Mem \c"
echo -e "\E[1;32m `cat /proc/meminfo | head -n 1 | tr -s " " | cut -d" " -f2,3` \E[0m"

echo -e "DISK \c"
echo -e "\E[1;32m `lsblk /dev/sda | grep "^sda" | tr -s " " | cut -d" " -f4` \E[0m"

echo -e "OS \c"
echo -e "\E[1;32m `cat /etc/os-release | sed -nr 's/^VERSION=\"(.*)\"/\1/p'` \E[0m"

echo -e "\n========================= sysinfo end =================="

[root@rocky86 ~]# bash sysinfo-v3.sh
========================= sysinfo begin ================
CPU 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
Mem 1828236 kB
DISK 200G
OS 8.6 (Green Obsidian)
========================= sysinfo end ==================

执行前先做语法检查

此选项只能检测脚本中的语法错误,不能检测命令错误,也不会执行脚本

1
bash -n test.sh
1
2
3
4
5
6
7
8
9
[root@rocky86 ~]# vim test3.sh
#!/bin/bash
echo "hello world"
xxxxx
lscpu | sed -n p'

[root@rocky86 ~]# bash -n test3.sh
test3.sh: line 17: unexpected EOF while looking for matching `''
test3.sh: line 18: syntax error: unexpected end of file

调试并执行

逐行输出命令,并输出执行结果

1
bash -x test.sh
1
2
3
4
5
6
7
[root@rocky86 ~]# bash -x test3.sh 
+ echo 'hello world'
hello world
+ xxxxx
test3.sh: line 15: xxxxx: command not found
test3.sh: line 17: unexpected EOF while looking for matching `''
test3.sh: line 18: syntax error: unexpected end of file

总结:脚本错误常见的有三种

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

一个空格引发的血案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@rocky86 ~]# vim test4.sh
#!/bin/bash

cat > test.conf<<EOF
$(hostname)
$(hostname -I)
EOF
echo "==== over ===="

[root@rocky86 ~]# bash test4.sh
test4.sh: line 18: warning: here-document at line 14 delimited by end-of-file (wanted `EOF')

[root@rocky86 ~]# cat -nA test4.sh
1 #!/bin/bash$
2 $
3 cat > test.conf<<EOF$
4 $(hostname)$
5 $(hostname -I)$
6 EOF $ # ===================================== 这里多了一个空格
7 echo "==== over ===="$

请问:这是哪种错误?

1
2
3
4
5
# 获取一天后的 Y-m-d
sleep $[24*3600]; date +%F

# 正确的写法
date +%F --date="+ day"

变量

变量

变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据

数据要存在内存空间中,而内存空间又是通过内存地址来访问的,但内存地址一般是16进制的编号;

为了方便使用,就有了变量名,当访问一个变量时,实际是访问其对应的内存地址的那块内存空间;

1

如上图所示

我们要获取 123 ,有两种方式,一种是通过变量名 x,另一种是通过内存地址(指针) 0x7ff995d4412,很明显是通过变量名更方便

变量类型

变量类型:

  • 内置变量,如:PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE
  • 用户自定义变量

不同的变量存放的数据不同,决定了以下

  • 数据存储方式
  • 参与的运算
  • 表示的数据范围

变量数据类型:

  • 字符
  • 数值:整型、浮点型,bash 不支持浮点数
  • 布尔
  • 指针
  • 结构体

编程语言分类

2

静态和动态语言

  • 静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
  • 动态编译语言:不用事先声明,可随时改变类型,如:bash,Python

静态类型

1
2
3
4
5
6
7
8
[root@rocky86 ~]# vim test.cpp
# include <stdio.h>
# include<unistd.h>
int main() {
int var1=123;
bool var2=false;
return 0;
}

动态类型

1
2
3
4
5
6
7
8
[root@rocky86 ~]# vim test.sh
#!/bin/bash

var1=123
var2="abc"

echo $var1
echo $var2

强类型和弱类型语言

  • 强类型语言:不同类型数据操作,必须经过强制转换才同一类型才能运算,如: java , c# , python
  • 弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事 先定义可直接调用,如:bash ,php,javascript

强类型

1
2
3
4
5
6
7
8
9
10
[root@rocky86 ~]# vim test.py
#!/bin/python

var1='magedu';

var1+=10; # 这一行会报错,string 和 int 无法用连字符操作

var1+=str(20); # int 要先转换成 string 类型

print(var1);

弱类型

1
2
3
4
5
6
[root@rocky86 ~]# vim test5.sh
#!/bin/bash

var1=123
var1+="abc"
echo $var1

Shell中变量命名法则

命名要求

  • 区分大小写
  • 不能使程序中的保留字和内置变量:如:if, for
  • 只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ - ”,和主机名相反

命名习惯

  • 见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM
  • 变量名大写
  • 局部变量小写
  • 函数名小写
  • 大驼峰StudentFirstName,由多个单词组成,且每个单词的首字母是大写,其它小写
  • 小驼峰studentFirstName ,由多个单词组成,第一个单词的首字母小写,后续每个单词的首字母是大写,其它小写
  • 下划线: student_name

变量定义和引用

变量的生效范围等标准划分变量类型

  • 普通变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell 进程均无效
  • 环境变量:生效范围为当前shell进程及其子进程
  • 本地变量:生效范围为当前shell进程中某代码片断,通常指函数

变量赋值:

1
name='value'

value 可以是以下多种形式

1
2
3
4
name='root'                  # 直接字串   
name="$USER" # 变量引用
name=`COMMAND` # 命令引用
name=$(COMMAND) # 命令引用

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

变量引用:

1
2
$name
${name}

弱引用和强引用

  • “$name” 弱引用,其中的变量引用会被替换为变量值
  • ‘$name’ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

范例:变量的各种赋值方式和引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@rocky8 ~]# TITLE='cto'

[root@rocky8 ~]# echo $TITLE
cto

[root@rocky8 ~]# echo I am $TITLE
I am cto

[root@rocky8 ~]# echo "I am $TITLE"
I am cto

[root@rocky8 ~]# echo 'I am $TITLE'
I am $TITLE

[root@rocky8 ~]# NAME=$USER
[root@rocky8 ~]# echo $NAME
root

# 命令执行结果赋值
[root@rocky8 ~]# FILE=`ls /run`
[root@rocky8 ~]# echo $FILE
agetty.reload atd.pid auditd.pid console crond.pid cron.reboot cryptsetup dbus dmeventd-client dmeventd-server faillock fsck httpd initctl initramfs irqbalance lock log lvm mariadb motd.d mount NetworkManager openvpn-client openvpn-server plymouth rsyslogd.pid screen sepermit setrans sshd.pid sudo systemd tmpfiles.d tuned udev user utmp vmware

[root@rocky8 ~]# FILE=/etc/*
[root@rocky8 ~]# echo $FILE
/etc/9sklafdnakljsdf /etc/adjtime /etc/aliases /etc/aliases.db /etc/alternatives /etc/anacrontab /etc/asound.conf /etc/at.deny /etc/audit /etc/authselect /etc/bash_completion.d /etc/bashrc

保留格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@rocky8 ~]# seq 5
1
2
3
4
5

[root@rocky8 ~]# NUM=`seq 5`
[root@rocky8 ~]# echo $NUM
1 2 3 4 5

[root@rocky8 ~]# echo "$NUM"
1
2
3
4
5

[root@rocky8 ~]# NAMES="wang
> zhang
> zhao
> li"

[root@rocky8 ~]# echo $NAMES
wang zhang zhao li

[root@rocky8 ~]# echo "$NAMES"
wang
zhang
zhao
li

范例:变量引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@centos8 data]# NAME=mage
[root@centos8 data]# AGE=20
[root@centos8 data]# echo $NAME
mage

[root@centos8 data]# echo $AGE
20

[root@centos8 data]# echo $NAME $AGE
mage 20

[root@centos8 data]# echo $NAME$AGE
mage20

[root@centos8 data]# echo $NAME_$AGE
20

[root@centos8 data]# echo ${NAME}_$AGE
mage_20

范例:变量的间接赋值和引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos8 ~]# TITLE=cto
[root@centos8 ~]# NAME=wang
[root@centos8 ~]# TITLE=$NAME
[root@centos8 ~]# echo $NAME
wang

[root@centos8 ~]# echo $TITLE
wang

[root@centos8 ~]# NAME=mage
[root@centos8 ~]# echo $NAME
mage

[root@centos8 ~]# echo $TITLE
wang

范例:变量追加值

1
2
3
4
[root@centos8 ~]# TITLE=CTO
[root@centos8 ~]# TITLE+=:wang
[root@centos8 ~]# echo $TITLE
CTO:wang

范例:利用变量实现动态命令

1
2
3
[root@centos8 ~]# CMD=hostname
[root@centos8 ~]# $CMD
centos8.magedu.com

范例:将sysinfo.sh 中的颜色定义成变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 ~]# vim sysinfo-v4.sh
#!/bin/bash
COLOR=34

echo -e "========================= sysinfo begin ================\n"

echo -e "CPU \c"
echo -e "\E[1;${COLOR}m `lscpu | sed -nr 's/^Model name: +(.*)/\1/p'` \E[0m"

echo -e "Mem \c"
echo -e "\E[1;${COLOR}m `cat /proc/meminfo | head -n 1 | tr -s " " | cut -d" " -f2,3` \E[0m"

echo -e "DISK \c"
echo -e "\E[1;${COLOR}m `lsblk /dev/sda | grep "^sda" | tr -s " " | cut -d" " -f4` \E[0m"

echo -e "OS \c"
echo -e "\E[1;${COLOR}m `cat /etc/os-release | sed -nr 's/^VERSION=\"(.*)\"/\1/p'` \E[0m"

echo -e "\n========================= sysinfo end =================="

当前进程和子进程

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# vim test10.sh
#!/bin/bash

VAR1=123
VAR2=456

echo "test"
echo $BASHPID
sleep 20

[root@rocky86 ~]# chmod +x test10.sh

当前进程中执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky86 ~]# echo $BASHPID
2570

# test10.sh中输出的 BASHPID 也是 2570
[root@rocky86 ~]# . test10.sh
test
2570

# 执行完成后,当前进程中能使用 test10.sh 中定义的变量
[root@rocky86 ~]# echo $VAR1 $VAR2
123 456

# 进程树中查看
[root@rocky86 ~]# pstree -p | grep 2570
|-sshd(1009)-+-sshd(2537)---sshd(2562)---bash(2570)---sleep(9403)

子进程中执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky86 ~]# echo $BASHPID
2570

# 进程ID不一样
[root@rocky86 ~]# ./test10.sh
test
9501

# 子进程中的变量己经销毁了
[root@rocky86 ~]# echo $VAR1 $VAR2
[root@rocky86 ~]#

# 进程树中查看
[root@rocky86 ~]# pstree -p | grep 9501
|-sshd(1009)-+-sshd(2537)---sshd(2562)---bash(2570)---test10.sh(9501)---sleep(9502)

显示已定义的所有变量:

1
set

删除变量:

1
unset <name>

范例:

1
2
3
4
5
6
7
[root@centos8 ~]# NAME=mage
[root@centos8 ~]# TITLE=ceo
[root@centos8 ~]# echo $NAME $TITLE
mage ceo

[root@centos8 ~]# unset NAME TITLE
[root@centos8 ~]# echo $NAME $TITLE

范例:显示系统信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# vim sysinfo-v5.sh
#!/bin/bash

RED="\E[1;31m"
GREEN="echo -e \E[1;32m"
END="\E[0m"
. /etc/os-release
$GREEN----------------------- sysinfo begin --------------------$END
echo -e "HOSTNAME: $RED`hostname`$END"
echo -e "IPADDR: $RED` ifconfig ens160|grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' |head -n1`$END"
echo -e "IPADDR: $RED` hostname -I`$END"
echo -e "OSVERSION: $RED$PRETTY_NAME$END"
echo -e "KERNEL: $RED`uname -r`$END"
echo -e "CPU: $RED`lscpu|grep '^Model name'|tr -s ' '|cut -d : -f2`$END"
echo -e "MEMORY: $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
echo -e "DISK: $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"
$GREEN----------------------- sysinfo end ----------------------------------$END

环境变量

环境变量:

  • 可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,将会新的值传递给孙子进程
  • 一般只在系统配置文件中使用,在脚本中较少使用

变量声明和赋值:

1
2
3
4
5
6
7
# 声明并赋值
export name=VALUE
declare -x name=VALUE

# 或者分两步实现
name=VALUE
export name

变量引用:

1
2
$name
${name}

显示所有环境变量:

1
2
3
4
env
printenv
export
declare -x

查看指定进程的环境变量

1
cat /proc/$PID/environ

删除变量:

1
unset name

bash内建的环境变量

1
2
3
4
5
6
7
8
9
10
11
12
PATH
SHELL
USER
UID
HOME
PWD
SHLVL # shell的嵌套层数,即深度
LANG
MAIL
HOSTNAME
HISTSIZE
_ # 下划线,表示前一命令的最后一个参数

范例: 查看进程的环境变量

1
2
3
4
5
6
7
8
9
[root@rocky8 ~]# cat /proc/1688/environ 
LANG=CPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/binNOTIFY_SOCKET=/run/systemd/notifyINVOCATION_ID=03144162b25c48a4b39edcf1597a1530JOURNAL_STREAM=9:27235

[root@rocky8 ~]# cat /proc/1688/environ | tr '\0' '\n'
LANG=C
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
NOTIFY_SOCKET=/run/systemd/notify
INVOCATION_ID=03144162b25c48a4b39edcf1597a1530
JOURNAL_STREAM=9:27235

范例

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 0817]# var1=123;export var2=456
[root@rocky86 0817]# vim test6.sh
#!/bin/bash

echo $var1
echo $var2
echo $PATH
echo $PS1

[root@rocky86 0817]# bash test6.sh
456
/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

只读变量

只读变量:只能声明定义,但后续不能修改和删除,即常量

声明只读变量:

1
2
readonly name
declare -r name

查看只读变量:

1
2
readonly [-p]
declare -r

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@centos8 ~]# readonly PI=3.14159
[root@centos8 ~]# echo $PI
3.14159

[root@centos8 ~]# PI=3.14
-bash: PI: readonly variable

[root@centos8 ~]# unset PI-
bash: unset: PI: cannot unset: readonly variable

[root@centos8 ~]# echo $PI
3.14159

[root@centos8 ~]# exit
logout
Connection closed by foreign host.
Disconnected from remote host(10.0.0.8) at 14:27:04.
Type `help' to learn how to use Xshell prompt.

[c:\~]$
Reconnecting in 1 seconds. Press any key to exit local shell.
.
Connecting to 10.0.0.8:22...
Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.
WARNING! The remote SSH server rejected X11 forwarding request.
Last login: Wed Apr 1 13:51:28 2020 from 10.0.0.1

[root@centos8 ~]# echo $PI
[root@centos8 ~]#

位置变量

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

1
2
3
4
5
6
7
$1,$2,...                           # 对应第1个、第2个等参数,shift [n]换位置
$0 # 命令本身,包括路径
$* # 传递给脚本的所有参数,全部参数合为一个字符串
$@ # 传递给脚本的所有参数,每个参数为独立字符串
$# # 传递给脚本的参数的个数

# 注意:$@ $* 只在被双引号包起来的时候才会有差异

清空所有位置变量

1
set --

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos8 ~]# cat /data/scripts/arg.sh 
#!/bin/bash

echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${10}"
echo "11st arg is ${11}"
echo "The number of arg is $# "
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"

[root@centos8 ~]# bash /data/scripts/arg.sh {a..z}
1st arg is a
2st arg is b
3st arg is c
10st arg is j
11st arg is k
The number of arg is 26
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
The scriptname is arg.sh

范例:删库跑路之命令rm的安全实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos8 ~]# cat /data/scripts/rm.sh 
#!/bin/bash

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

[root@centos8 ~]# chmod a+x /data/scripts/rm.sh
[root@centos8 ~]# alias rm='/data/scripts/rm.sh'
[root@centos8 ~]# touch {1..10}.txt
[root@centos8 ~]# rm *.txt
Move 10.txt 1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt to /tmp/2020-04-01_15-15-28

范例:$*和$@的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@centos8 scripts]# cat f1.sh
#!/bin/bash
echo "f1.sh:all args are $@"
echo "f1.sh:all args are $*"
./file.sh "$*"

[root@centos8 scripts]# cat f2.sh
#!/bin/bash
echo "f2.sh:all args are $@"
echo "f2.sh:all args are $*"
./file.sh "$@"

[root@centos8 scripts]# cat file.sh
#!/bin/bash
echo "file.sh:1st arg is $1"

[root@centos8 scripts]# ./f1.sh a b c
f1.sh:all args are a b c
f1.sh:all args are a b c
file.sh:1st arg is a b c

[root@centos8 scripts]# ./f2.sh a b c
f2.sh:all args are a b c
f2.sh:all args are a b c
file.sh:1st arg is a

范例: 利用软链接实现同一个脚本不同功能

1
2
3
4
5
6
7
8
9
10
11
12
[root@centos8 ~]# cat test.sh 
#!/bin/bash
# ********************************************************************
echo $0

[root@centos8 ~]# ln -s test.sh a.sh
[root@centos8 ~]# ln -s test.sh b.sh
[root@centos8 ~]# ./a.sh
./a.sh

[root@centos8 ~]# ./b.sh
./b.sh

退出状态码变量

当我们浏览网页时,有时会看到下图所显示的数字,表示网页的错误信息,我们称为状态码,在shell脚本中也有相似的技术表示程序执行的 相应状态。

3

进程执行后,将使用变量 $? 保存状态码的相关数字,不同的值反应成功或失败,$?取值范例 0-255

1
$?                  # 0代表成功,1-255代表失败

范例:

1
2
ping -c1 -W1 hostdown &> /dev/null 
echo $?

范例:

1
2
3
[root@centos8 ~]# curl -fs http://www.wangxiaochun.com >/dev/null
[root@centos8 ~]# echo $?
0

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

1
exit [n]

注意:

  • 脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
  • 如果exit后面无数字,终止退出状态取决于exit命令前面命令执行结果
  • 如果没有exit命令, 即未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一 条命令的状态码

展开命令行

展开命令执行顺序

1
2
3
4
5
6
7
8
9
把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明 ~
命令替换$() 和 ``
再次把命令行分成命令词
展开文件通配符*、?、[abc]等等
准备I/0重导向 <、>
运行命令

防止扩展

1
\                   # 反斜线 会使随后的字符按原意解释

范例:

1
2
3
4
5
[root@centos8 ~]# echo Your cost: \$5.00 
Your cost: $5.00

[root@rocky8 ~]# echo "The book's price is \$10"
The book's price is $10

加引号来防止扩展

1
2
''                      # 单引号 防止所有扩展    
"" # 双引号 也可防止扩展,但是以下情况例外 $

变量扩展

1
2
3
``                       # 反引号,命令替换   
\ # 反斜线,禁止单个字符扩展
! # 叹号,历史命令替换

范例: ``和$() 区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos8 ~]# echo `echo \`
> ^C

[root@centos8 ~]# echo `echo \\`
\

[root@centos8 ~]# echo `echo \\\`
> ^C

[root@centos8 ~]# echo `echo \\\\`
\

[root@centos8 ~]# echo $(echo \)
> ^C

[root@centos8 ~]# echo $(echo \\)
\

[root@centos8 ~]# echo $(echo \\\)
> ^C

[root@centos8 ~]# echo $(echo \\\\)
\\

脚本安全和 set

set 命令:可以用来定制 shell 环境

$- 变量

选项 含义 备份
h hashall 开启hash表缓存外部命令路径
i interactive-comments 表示当前是交互是shell
m monitor 开启监控模式,可通过 Job control来控制进程的停止、继续,后台或者前台执行等
B braceexpand 是否支持大括号扩展
H history 是否可用 ! 快速展开history命令

范例:查看

1
2
3
4
5
6
7
8
9
10
11
 # 查看命令行下的配置
[root@rocky86 0817]# echo $-
himBHs

# 查看脚本中的配置
[root@rocky86 0817]# cat set.sh
#!/bin/bash
echo $-

[root@rocky86 0817]# bash set.sh
hB

范例:关闭hash缓存功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@rocky86 0817]# echo $-
himBHs

[root@rocky86 0817]# hash
hits command
6 /usr/bin/bash
2 /usr/bin/cat
6 /usr/bin/vim


[root@rocky86 0817]# set +h
[root@rocky86 0817]# echo $-
imBHs

[root@rocky86 0817]# hash
-bash: hash: hashing disabled

范例:关闭大括号展开

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 0817]# echo $-
imBHs

[root@rocky86 0817]# echo {1..5}
1 2 3 4 5

[root@rocky86 0817]# set +B
[root@rocky86 0817]# echo $-
imHs

[root@rocky86 0817]# echo {1..5}
{1..5}

set 命令实现脚本安全

u 开启此项,在使用一个没有声明的变量时,会报错,并且终止程序,同 set -o nounset
e 开启此项,命令返回非0就退出,不再继续执行后面的代码,同 set -o errexit
o set -o 显示所有; set -o option 开启 option 项; set +o option 关闭 option 项
x set -x
  • -u 在扩展一个没有设置的变量时,显示错误信息, 等同set -o nounset
  • -e 如果一个命令返回一个非0退出状态值(失败)就退出, 等同set -o errexit
  • -o option 显示,打开或者关闭选项 显示选项:set -o 打开选项:set -o 选项 关闭选项:set +o 选项
  • -x 当执行命令时,打印命令及其参数,类似 bash -x

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@centos8 ~]# set -o
allexport off
braceexpand on
emacs on
errexit off
errtrace off
functrace off
hashall on
histexpand on
history on
ignoreeof off
interactive-comments on
keyword off
monitor on
noclobber off
noexec off
noglob off
nolog off
notify off
nounset off
onecmd off
physical off
pipefail off
posix off
privileged off
verbose off
vi off
xtrace off

范例:限制使用没声明的变量

1
2
3
4
5
6
7
8
9
[root@rocky86 0817]# cat set2.sh 
#!/bin/bash

set -u
DIR=/test
rm -rf ${DIr}/* # 这个变量其实不存在,如果没有 set -u 选项,则会删除根目录

[root@rocky86 0817]# bash set2.sh
set2.sh: line 17: DIr: unbound variable

范例:遇到错误终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky86 0817]# vim set3.sh 
#!/bin/bash

set -e
echo 123
cd /test2/ # 这个目录不存在,如果没有 set -e,则也会删根

rm -rf *
echo 456

# 遇到错误行就终止
[root@rocky86 0817]# bash set3.sh
123
set3.sh: line 17: cd: /test2/: No such file or directory

格式化输出 printf

相当于增强版的 echo, 实现丰富的格式化输出

格式

1
printf format args...

4

常用格式替换符

替换符 功能
%s 字符串
%d,%i 十进制整数
%f 浮点格式
%c ASCII字符,即显示对应参数的第一个字符
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转 义
%o 八进制值
%u 不带正负号的十进制值
%x 十六进制值(a-f)
%X 十六进制值(A-F)
%% 表示%本身

说明:

1
2
3
%[N]s                   #  N表示输出宽度,不够补空格 -N 表示左对齐
%[0][N]d # 0表示宽度不够时在左边补0,N表示输出宽度
%[.N]f # .N 表示保留的小数位

常用转义字符

转义符 功能
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ 表示\本身

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
[root@centos8 ~]# printf "%s" 1 2 3 4
1234[root@centos8 ~]#
[root@centos8 ~]# printf "%s\n" 1 2 3 4
1
2
3
4

[root@centos8 ~]# printf "%f\n" 1 2 3 4
1.000000
2.000000
3.000000
4.000000

# .2f 表示保留两位小数
[root@centos8 ~]# printf "%.2f\n" 1 2 3 4
1.00
2.00
3.00
4.00

[root@centos8 ~]# printf "(%s)" 1 2 3 4;echo
(1)(2)(3)(4)

[root@centos8 ~]# printf " (%s) " 1 2 3 4;echo
(1) (2) (3) (4)

[root@centos8 ~]# printf "(%s)\n" 1 2 3 4
(1)
(2)
(3)
(4)

[root@centos8 ~]# printf "%s %s\n" 1 2 3 4
1 2
3 4

[root@centos8 ~]# printf "%s %s %s\n" 1 2 3 4
1 2 3
4

# %-10s 表示宽度10个字符,左对齐
[root@centos8 ~]# printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50
姓名 性别 年龄 体重
小明 男 20 70
小红 女 18 50

# 将十进制的17转换成16进制数
[root@centos8 ~]# printf "%X" 17
11

# 将十六进制C转换成十进制
[root@centos8 ~]# printf "%d\n" 0xC
12

[root@rocky8 ~]# VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m " $VAR
welcome to Magedu

[root@centos8 ~]# VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m\n" $VAR
welcome
to
Magedu

[root@centos8 ~]# VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m\n" "$VAR"
welcome to Magedu

算术运算

Shell允许在某些情况下对算术表达式进行求值,比如:let和declare 内置命令,(( ))复合命令和算术扩展。求值以固定宽度的整数进行,不检 查溢出,尽管除以0 被困并标记为错误。运算符及其优先级,关联性和值与C语言相同。以下运算符列表分组为等优先级运算符级别。级别按降序排列优先。

注意:bash 只支持整数,不支持小数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+ -                          # addition, subtraction
* / % # multiplication, division, remainder, %表示取模,即取余数,示例:9%4=1,5%3=2
i++ i-- # variable post-increment and post-decrement
++i --i # variable pre-increment and pre-decrement
= *= /= %= += -= <<= >>= &= ^= |= # assignment
- + # unary minus and plus
! ~ # logical and bitwise negation
** # exponentiation 乘方,即指数运算
<< >> # left and right bitwise shifts
<= >= < > # comparison
== != # equality and inequality
& # bitwise AND
| # bitwise OR
^ # bitwise exclusive OR
&& # logical AND
|| # logical OR
expr?expr:expr # conditional operator
expr1 , expr2 # comma

乘法符号有些场景中需要转义

实现算术运算:

1
2
3
4
5
6
7
let var=expression
((var=expression))
var=$[expression]
var=$((expression))
var=$(expr arg1 arg2 ...)
declare -i var=number
echo expression|bc

范例:不转义,无法进行算术运算

1
2
3
4
5
6
[root@rocky86 0817]# i=123;j=456;
[root@rocky86 0817]# k=i+j;echo $k
i+j

[root@rocky86 0817]# k=$i+$j;echo $k
123+456

范例:转义

1
2
3
4
5
6
7
8
9
[root@rocky86 0817]# i=123;j=456;
[root@rocky86 0817]# let k=i+j;echo $k
579

[root@rocky86 0817]# ((k=i+j));echo $k
579

[root@rocky86 0817]# k=$[i+j];echo $k
579

范例:expr 命令

1
2
3
4
5
6
7
8
9
# 表达式中间要有空格
[root@rocky86 0817]# expr 10+20
10+20

[root@rocky86 0817]# expr 10 + 20
30

[root@rocky86 0817]# expr 10 \* 20
200

内建的随机数生成器变量

1
$RANDOM                   # 取值范围:0-32767

范例:生成 0 - 49 之间随机数

1
2
3
4
5
6
7
8
[root@rocky86 0817]# echo $[$RANDOM%50]
6

[root@rocky86 0817]# echo $[$RANDOM%50]
15

[root@rocky86 0817]# echo $[$RANDOM%50]
33

范例:随机字体颜色

1
echo -e "\033[1;$[RANDOM%7+31]mhello\033[0m"

5

增强型赋值:

1
2
3
4
5
6
7
+=                      # i+=10 相当于 i=i+10
-= # i-=j 相当于 i=i-j
*=
/=
%=
++ # i++,++i 相当于 i=i+1
-- # i--,--i 相当于 i=i-1

范例:i++与++i

1
2
3
4
5
[root@rocky86 0817]# i=1;let j=i++;echo $i $j
2 1

[root@rocky86 0817]# i=1;let j=++i;echo $i $j
2 2

范例:

1
2
3
4
5
6
7
8
9
10
[root@centos8 ~]# let i=10*2
[root@centos8 ~]# echo $i
20

[root@centos8 ~]# ((j=i+10))
[root@centos8 ~]# echo $j
30

[root@rocky8 ~]# i=10;((i++));echo $i
11

范例:

1
2
3
4
5
6
7
8
9
[root@centos8 ~]# i=10
[root@centos8 ~]# let i+=20 # 相当于let i=i+20
[root@centos8 ~]# echo $i
30

[root@centos8 ~]# j=20
[root@centos8 ~]# let i*=j
[root@centos8 ~]# echo $i
600

范例:自增,自减

1
2
3
4
5
[root@centos8 ~]# unset i j ; i=1; let j=i++; echo "i=$i,j=$j"
i=2,j=1

[root@centos8 ~]# unset i j ; i=1; let j=++i; echo "i=$i,j=$j"
i=2,j=2

范例:

1
2
3
4
5
6
7
8
[root@centos8 ~]# expr 2 * 3
expr: syntax error: unexpected argument ‘anaconda-ks.cfg’

[root@centos8 ~]# ls
anaconda-ks.cfg

[root@centos8 ~]# expr 2 \* 3
6

范例:

1
2
[root@centos8 ~]# echo "scale=3;20/3"|bc
6.666

范例:

1
2
3
4
5
[root@centos8 ~]# i=10
[root@centos8 ~]# j=20
[root@centos8 ~]# declare -i result=i*j
[root@centos8 ~]# echo $result
200

范例:

鸡兔同笼,是中国古代著名典型趣题之一,记载于《孙子算经》之中。今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?

  1. 让鸡和兔子都抬一条腿到天上去,则地上还有59条腿,分别属于1条腿的鸡和3条腿的兔子,94-35=59;
  2. 让鸡和兔子都抬两条腿到天上去,则地上还有24条腿,分别属于0条腿的鸡和2条腿的兔子,59-35=24;
  3. 那么,兔子的数量是 24/2=12只,则鸡的数量是 35-12=23只;
1
2
3
4
5
6
7
8
9
10
11
12
[root@centos8 scripts]# cat chook_rabbit.sh 
#!/bin/bash
HEAD=$1
FOOT=$2
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHICKEN=$[HEAD-RABBIT]
echo RABBIT:$RABBIT
echo CHICKEN:$CHICKEN

[root@centos8 scripts]# ./chook_rabbit.sh 30 80
RABBIT:10
CHOOK:20

逻辑运算

与或非

6

true, false

bool 二进制表示 说明
true 1
false 0

与:& 和0相与结果为0,和1相与结果保留原值, 一假则假,全真才真

变量1 变量2 与运算 与运算结果
0 0 0&0 0
0 1 0&1 0
1 0 1&0 0
1 1 1&1 1

范例

1
2
3
4
5
6
7
[root@ubuntu1804 ~]# x=$[2&6]
[root@ubuntu1804 ~]# echo $x
2

[root@ubuntu1804 ~]# x=$[7&3]
[root@ubuntu1804 ~]# echo $x
3

或:| 和1相或结果为1,和0相或结果保留原值,一真则真,全假才假

变量1 变量2 或运算 或运算结果
0 0 0|0 0
0 1 0|1 1
1 0 1|0 1
1 1 1|1 1

范例:

1
2
3
4
5
6
7
[root@ubuntu1804 ~]# x=$[2|6]
[root@ubuntu1804 ~]# echo $x
6

[root@ubuntu1804 ~]# x=$[7|3]
[root@ubuntu1804 ~]# echo $x
7

非:!

变量 运算 非运算结果
1 ! 1 0
0 ! 0 1

异或:^

异或的两个值,相同为假,不同为真。两个数字X,Y异或得到结果Z,Z再和任意两者之一X异或,将得出另一个值Y

变量1 变量2 异或运算 异或运算结果
0 0 0^0 0
0 1 0^1 1
1 0 1^0 1
1 1 1^1 1

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos8 ~]# true 
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# false
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# ! true
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# ! false
[root@centos8 ~]# echo $?
0

范例: 变量互换

1
2
3
4
5
[root@centos8 ~]# x=10;y=20;temp=$x;x=$y;y=$temp;echo x=$x,y=$y
x=20,y=10

[root@centos8 ~]# x=10;y=20;x=$[x^y];y=$[x^y];x=$[x^y];echo x=$x,y=$y
x=20,y=10

短路运算

短路与 &&

command1 command2 短路与运算 短路与运算结果
true true cmd1 && cmd2 true
true false cmd1 && cmd2 false
false true cmd1 && cmd2 false
false false cmd1 && cmd2 false

当cmd1 的结果为false时,整个表达式就是 false, cmd2 不参与运算,这就是所谓的短路

短路或 ||

command1 command2 短路或运算 短路与运算结果
true true cmd1 || cmd2 true
true false cmd1 || cmd2 true
false true cmd1 || cmd2 true
false false cmd1 || cmd2 false

当cmd1 的结果为true时,整个表达式就是 true, cmd2 不参与运算,这就是所谓的短路

短路与和或组合

command1 command2 command3 短路运算 短路与运算结果
true true true cmd1 && cmd2 || cmd3 true
true true false cmd1 && cmd2 || cmd3 true
true false true cmd1 && cmd2 || cmd3 true
true false false cmd1 && cmd2 || cmd3 false
false true true cmd1 && cmd2 || cmd3 true
false true false cmd1 && cmd2 || cmd3 false
false false false cmd1 && cmd2 || cmd3 false

当cmd1 的结果为true时,会执行 cmd2;

当cmd1 的结果为false时,会执行 cmd3;

cmd1 || cmd2 && cmd3 这种语法是错误的,不使用;

条件测试命令

条件测试:

判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程,实现评估布尔声明,以便用在条件性环境下进行执行

若真,则状态码变量 $? 返回0

若假,则状态码变量 $? 返回1

条件测试命令

1
2
3
test expr
[ arg... ] # 同 test
[[ expression ]] # 增加版的[],支持[],支持扩展正则表达式和通配符

注意:[] 中的表达式,前后要有空格

常用选项:文件判断相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-a FILE         # 如果文件存在则为真
-b FILE # 如果文件为块特殊文件则为真
-c FILE # 如果文件为字符特殊文件则为真
-d FILE # 如果文件为目录则为真
-e FILE # 如果文件存在则为真
-f FILE # 如果文件存在且为常规文件则为真
-g FILE # 如果文件的组属性设置打开则为真
-h FILE # 如果文件为符号链接则为真
-L FILE # 如果文件为符号链接则为真
-k FILE # 如果文件的粘滞 (sticky) 位设定则为真
-p FILE # 如果文件为命名管道则为真
-r FILE # 如果当前用户对文件有可读权限为真
-s FILE # 如果文件存在且不为空则为真
-S FILE # 如果文件是套接字则为真

-t FD # 如果文件描述符在一个终端上打开则为真
-u FILE # 如果文件设置了SUID特殊权限则为真
-w FILE # 如果当前用户对文件有可写权限为真
-x FILE # 如果当前用户对文件有可执行权限为真
-O FILE # 如果当前用户是文件属主为真
-G FILE # 如果当前用户对文件属组为真
-N FILE # 如果文件上次被读取之后修改过则为真

FILE1 -nt FILE2 # 如果 file1 文件新于 file2 文件则为真(根据修改日期)
FILE1 -ot FILE2 # 如果 file1 文件旧于 file2 文件则为真
FILE1 -ef FILE2 # 如果 file1 文件是 file2 文件的硬链接则为真

常用选项:字符串判断相关

1
2
3
4
5
6
7
8
9
-z STRING                 # 判断字符串是否为空,为空则为真
-n STRING # 如果字符串不为空则为真
STRING # 同上

# 两个字符串变量比较,一定要有空格,如果没有空格,就变成赋值了
STRING1 = STRING2 # 如果 string1 和 string2 字符串相同则为真
STRING1 != STRING2 # 如果 string1 和 string2 字符串不相同则为真
STRING1 < STRING2 # 只比较第一个字符,以字母表顺序来比较,string1在string2 之前则为真,< 要转义
STRING1 > STRING2 # 只比较第一个字符,以字母表顺序来比较,string1在string2 之后则为真, > 要转义

常用选项:数学相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 数字相关  格式 arg1 OP arg2
-eq # 等于
-ne # 不等于
-lt # 小于
-le # 小于等于
-gt # 大于
-ge # 大于等于


# 算术表达式比较
== # 相等
!= # 不相等
<= # 小于或等于
>= # 大于或等于
< # 小于
> # 大于

常用选项:其它

1
2
3
4
5
6
-o OPTION                      # 如果在shell 中开启了某项,则为真     
-v VAR # 如果变量被设置为为真
-R VAR # 如果变量被设置且被引用则为真
! EXPR # 表达式结果取反
EXPR1 -a EXPR2 # 如果 expr1 和 expr2 都为真则为真
EXPR1 -o EXPR2 # 如果 expr1 和 expr2 有一个为真则为真

变量测试

范例:判断变量是否被定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@centos8 ~]# unset x
[root@centos8 ~]# test -v x
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# x=10
[root@centos8 ~]# test -v x
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# y=
[root@centos8 ~]# test -v y
[root@centos8 ~]# echo $?
0

# 注意 [ ] 中需要空格,否则会报下面错误
[root@centos8 ~]# [-v name]
-bash: [-v: command not found

[root@centos8 ~]# [ -v name ]
[root@centos8 ~]# echo $?
0

数值测试

范例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@centos8 ~]# i=10
[root@centos8 ~]# j=8
[root@centos8 ~]# [ $i -lt $j ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# [ $i -gt $j ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# [ i -gt j ]
-bash: [: i: integer expression expected

范例:算术表达式比较

1
2
3
4
5
6
7
8
9
10
11
[root@centos8 ~]# x=10;y=10;(( x == y ));echo $?
0

[root@centos8 ~]# x=10;y=20;(( x == y ));echo $?
1

[root@centos8 ~]# x=10;y=20;(( x != y ));echo $?
0

[root@centos8 ~]# x=10;y=10;(( x != y ));echo $?
1

范例:

1
2
3
4
5
[root@centos8 ~]# x=10;y=20;(( x > y ));echo $?
1

[root@centos8 ~]# x=10;y=20;(( x < y ));echo $?
0

字符串测试

test和 [ ] 字符串测试用法

1
2
3
4
5
6
7
8
9
10
-z STRING                           # 判断字符串是否为空,为空则为
-n STRING # 如果字符串不为空则为真
STRING # 同上


# 两个字符串变量比较,一定要有空格,如果没有空格,就变成赋值了
STRING1 = STRING2 # 如果 string1 和 string2 字符串相同则为真
STRING1 != STRING2 # 如果 string1 和 string2 字符串不相同则为真
STRING1 < STRING2 # 只比较第一个字符,以字母表顺序来比较,string1在string2 之前则为真,< 要转义
STRING1 > STRING2 # 只比较第一个字符,以字母表顺序来比较,string1在string2 之后则为真, > 要转义

[[ ]] 字符串测试用法

1
2
3
4
5
6
7
[[ expression ]] 用法
== # 左侧字符串是否和右侧的PATTERN相同
# 注意:此表达式用于[[ ]]中,PATTERN为通配符


=~ # 左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配
# 注意: 此表达式用于[[ ]]中为扩展的正则表达式

建议:当使用正则表达式或通配符使用[[ ]],其它情况一般使用 [ ]

范例:使用 [ ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[root@centos8 ~]# unset str
[root@centos8 ~]# [ -z "$str" ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# str=""
[root@centos8 ~]# [ -z "$str" ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# str=" "
[root@centos8 ~]# [ -z "$str" ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# [ -n "$str" ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# unset str
[root@centos8 ~]# [ -n "$str" ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# [ "$str" ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# str=magedu
[root@centos8 ~]# [ "$str" ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# str=magedu
[root@centos8 ~]# [ "$str" ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# str1=magedu
[root@centos8 ~]# str2=mage
[root@centos8 ~]# [ $str1 = $str2 ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# str2=magedu
[root@centos8 ~]# [ $str1 = $str2 ]
[root@centos8 ~]# echo $?
0

范例:在比较字符串时,建议变量放在“ ”中

1
2
3
4
5
6
7
8
9
10
11
[root@centos8 ~]# [ "$NAME" ]
[root@centos8 ~]# NAME="I love linux"
[root@centos8 ~]# [ $NAME ]
-bash: [: love: binary operator expected

[root@centos8 ~]# [ "$NAME" ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# [ I love linux ]
-bash: [: love: binary operator expected

范例:比较符要转义

1
2
3
4
5
6
7
8
9
10
# 这个写法有问题
[root@rocky86 ~]# a=a;b=b; [ $a > $b ]; echo $?
0

# 这个才是正确的
[root@rocky86 ~]# a=a;b=b; [ "$a" \> "$b" ]; echo $?
1

[root@rocky86 ~]# a=b;b=a; [ "$a" \> "$b" ]; echo $?
0

范例: [[ ]] 和通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
[root@centos8 ~]# FILE="a*"
[root@centos8 ~]# echo $FILE
a*

[root@centos8 ~]# [[ $FILE == a* ]]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# FILE="ab"
[root@centos8 ~]# [[ $FILE == a* ]]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# FILE="a*"

# [[]]中如果不想使用通配符*,只想表达*本身,可以用" "引起来
[root@centos8 ~]# [[ $FILE == a"*" ]]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# FILE="ab"
[root@centos8 ~]# [[ $FILE == a"*" ]]
[root@centos8 ~]# echo $?
1

# [[]]中如果不想使用通配符*,只想表达*本身,也可以使用转义符
[root@centos8 ~]# [[ $FILE == a\* ]]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# FILE="a\b"
[root@centos8 ~]# [[ $FILE == a\* ]]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# FILE="a*"
[root@centos8 ~]# [[ $FILE == a\* ]]
[root@centos8 ~]# echo $?
0

# 通配符?
[root@centos8 script]# FILE=abc
[root@centos8 script]# [[ $FILE == ??? ]]
[root@centos8 script]# echo $?
0

[root@centos8 script]# FILE=abcd
[root@centos8 script]# [[ $FILE == ??? ]]
[root@centos8 script]# echo $?
1

# 通配符
[root@centos8 ~]# NAME="linux1"
[root@centos8 ~]# [[ "$NAME" == linux* ]]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# [[ "$NAME" == "linux*" ]]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# NAME="linux*"
[root@centos8 ~]# [[ "$NAME" == "linux*" ]]
[root@centos8 ~]# echo $?
0

# 结论:[[ == ]] == 右侧的 * 做为通配符,不要加"",只想做为*符号使用时, 需要加 "" 或转义

范例: 判断合理的考试成绩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos8 script]#  SCORE=101
[root@centos8 script]# [[ $SCORE =~ 100|[0-9]{1,2} ]]
[root@centos8 script]# echo $?
0

[root@centos8 script]# [[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]
[root@centos8 script]# echo $?
1

[root@centos8 script]# SCORE=10
[root@centos8 script]# [[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]
[root@centos8 script]# echo $?
0

[root@centos8 script]# SCORE=abc
[root@centos8 script]# [[ $SCORE =~ ^(100|[0-9]{1,2})$ ]]
[root@centos8 script]# echo $?
1

范例:使用 [[ ]] 判断文件后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 通配符
[root@centos8 ~]# FILE=test.log
[root@centos8 ~]# [[ "$FILE" == *.log ]]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# FILE=test.txt
[root@centos8 ~]# [[ "$FILE" == *.log ]]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# [[ "$FILE" != *.log ]]
[root@centos8 ~]# echo $?
0

# 正则表达式
[root@centos8 ~]# [[ "$FILE" =~ \.log$ ]]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# FILE=test.log
[root@centos8 ~]# [[ "$FILE" =~ \.log$ ]]
[root@centos8 ~]# echo $?
0

范例: 判断合法的非负整数

1
2
3
4
5
6
7
8
9
[root@centos8 ~]# N=100
[root@centos8 ~]# [[ "$N" =~ ^[0-9]+$ ]]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# N=Magedu10
[root@centos8 ~]# [[ "$N" =~ ^[0-9]+$ ]]
[root@centos8 ~]# echo $?
1

范例: 判断合法IP

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@centos8 ~]# IP=1.2.3.4
[root@centos8 ~]# [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# IP=1.2.3.4567
[root@centos8 ~]# [[ "$IP" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}$ ]]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][09]|25[0-5])$ ]]
[root@centos8 ~]# echo $?
1

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos7 ~]# cat check_ip.sh 
#!/bin/bash
#
# ****************************************************
# Author: jose
# QQ: 123456
# Date: 2022-08-17
# FileName: set3.sh
# URL: http://www.magedu.com
# Description: test
# Copyright(C): 2022 All right
# ***************************************************
IP=$1
[[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
&& echo $IP is valid || echo $IP is invalid

文件测试

范例: -e和-a 表示判断文件的存在性,建议使用-e

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 文件是否不存在
[root@centos8 ~]# [ -a /etc/nologin ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# ! [ -a /etc/nologin ]
[root@centos8 ~]# echo $?
0

# 文件是否存在
[root@centos8 ~]# [ -a /etc/issue ]
[root@centos8 ~]# echo $?
0

# 取反后结果却没有变化
[root@centos8 ~]# [ ! -a /etc/issue ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# ! [ -a /etc/issue ]
[root@centos8 ~]# echo $?
1

# 文件是否存在
[root@centos8 ~]# ! [ -e /etc/issue ]
[root@centos8 ~]# echo $?
1

# 此为推荐写法
[root@centos8 ~]# [ ! -e /etc/issue ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# [ -d /etc ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# [ -d /etc/issue ]
[root@centos8 ~]# echo $?
1
[root@centos8 ~]# [ -L /bin ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# [ -L /bin/ ]
[root@centos8 ~]# echo $?
1

文件权限测试:

注意:最终结果由用户对文件的实际权限决定,而非文件属性决定

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@centos8 ~]# [ -w /etc/shadow ] 
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# [ -x /etc/shadow ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# [ -w test.txt ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# chattr +i test.txt
[root@centos8 ~]# lsattr test.txt
----i-------------- nianling.txt

[root@centos8 ~]# [ -w test.txt ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# chattr -i test.txt
[root@centos8 ~]# [ -w test.txt ]
[root@centos8 ~]# echo $?
0

文件属性测试:

1
2
3
4
5
6
7
8
[root@rocky86 ~]# [ -s /etc/passwd ];echo $?
0

[root@rocky86 ~]# [ -s nofile ];echo $?
1

[root@rocky86 ~]# [ -O /etc/passwd ];echo $?
0

关于 () 和 {}

1
2
3
( cmd1;cmd2;... ) 和 { cmd1;cmd2;...; } 都可以将多个命令组合在一起,批量执行
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境
{ list; } 不会开启子shell, 在当前shell中运行,会影响当前shell环境,左侧要有空格,右侧要有; 结束

范例: () 和 {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[root@centos8 ~]# name=mage;(echo $name;name=wang;echo $name );echo $name
mage
wang
mage

[root@centos8 ~]# name=mage;{ echo $name;name=wang;echo $name; } ;echo $name
mage
wang
wang

[root@centos8 ~]# umask
0022

[root@centos8 ~]# (umask 066;touch f1.txt)
[root@centos8 ~]# ll f1.txt
-rw------- 1 root root 0 Dec 23 16:58 f1.txt

[root@centos8 ~]# umask
0022

[root@centos8 ~]# ( cd /data;ls )
test.log

[root@centos8 ~]# pwd
/root

[root@centos8 ~]# { cd /data;ls; }
test.log

[root@centos8 data]# pwd
/data

[root@centos8 data]#

# ()会开启子shell
[root@centos8 ~]# echo $BASHPID
1920

[root@centos8 ~]# ( echo $BASHPID;sleep 100)
1979

[root@centos8 ~]# pstree -p
├─sshd(719)───sshd(1906)───sshd(1919)─┬─bash(1920)───bash(1979)───sleep(1980)

# { } 不会开启子shell
[root@centos8 ~]# echo $BASHPID
1920

[root@centos8 ~]# { echo $BASHPID; }
1920

组合测试条件

第一种方式

1
2
3
4
5
[ EXPRESSION1 -a EXPRESSION2 ]        # 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真

[ EXPRESSION1 -o EXPRESSION2 ] # 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真

[ ! EXPRESSION ] # 取反

说明: -a 和 -o 需要使用测试命令进行,[[ ]] 不支持

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[root@centos8 ~]#  FILE="/data/scrips/test.sh"
[root@centos8 ~]# ll /data/scrips/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh

[root@centos8 ~]# [ -f $FILE -a -x $FILE ]
[root@centos8 ~]# echo $?
1

[root@centos8 ~]# chmod +x /data/scripts/test.sh
[root@centos8 ~]# ll /data/scripts/test.sh
-rwxr-xr-x 1 root root 382 Dec 23 09:32 /data/script40/test.sh

# 并且
[root@centos8 ~]# [ -f $FILE -a -x $FILE ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# chmod -x /data/scripts/test.sh
[root@centos8 ~]# ll /data/scripts/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh

# 或者
[root@centos8 ~]# [ -f $FILE -o -x $FILE ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# [ -x $FILE ]
[root@centos8 ~]# echo $?
1

# 取反
[root@centos8 ~]# [ ! -x $FILE ]
[root@centos8 ~]# echo $?
0

[root@centos8 ~]# ! [ -x $FILE ]
0

第二种方式

7

条件表达式

1
2
3
4
5
6
7
COMMAND1 && COMMAND2         #  并且,短路与,代表条件性的 AND THEN 
# 如果 COMMAND1 成功,将执行 COMMAND2;否则,不执行 COMMAND2

COMMAND1 || COMMAND2 # 或者,短路或,代表条件性的 OR ELSE
# 如果 COMMAND1 成功,将执行 COMMAND2;否则,不执行 COMMAND2

! COMMAND # 非,取反

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@centos8 ~]# test "A" = "B" && echo "Strings are equal" 
[root@centos8 ~]# test "A"-eq "B" && echo "Integers are equal"
[root@centos8 ~]# [ "A" = "B" ] && echo "Strings are equal"
[root@centos8 ~]# [ "$A" -eq "$B" ] && echo "Integers are equal"
[root@centos8 ~]# [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
[root@centos8 ~]# [ -z "$HOSTNAME" -o "$HOSTNAME" = "localhost.localdomain" ]&&
hostname www.magedu.com

[root@centos8 ~]# id wang &> /dev/null || useradd wang
[root@centos8 ~]# id zhang &> /dev/null || useradd zhang
[root@centos8 ~]# getent passwd zhang
zhang:x:1002:1002::/home/zhang:/bin/bash
[root@centos8 ~]# grep -q no_such_user /etc/passwd || echo 'No such user'
No such user

范例:

1
2
3
4
5
6
7
8
9
[root@centos8 ~]#  [ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]] && chmod +x $FILE
[root@centos8 ~]# ping -c1 -W1 172.16.0.1 &> /dev/null && echo '172.16.0.1 is up' || (echo '172.16.0.1 is unreachable'; exit 1)
172.16.0.1 is up

[root@centos8 ~]# IP=10.0.0.111;ping -c1 -W1 $IP &> /dev/null && echo $IP is up || echo $IP is down
10.0.0.111 is down

[root@centos8 ~]# IP=10.0.0.1;ping -c1 -W1 $IP &> /dev/null && echo $IP is up || echo $IP is down
10.0.0.1 is up

条件组合

1
2
COMMAND1 && COMMAND2 || COMMAND3 
COMMAND1 || COMMAND2 && COMMAND3

8

9

范例:轮盘游戏

1
[root@centos7 ~]# [ $[RANDOM%6] -eq 0 ] && rm -rf /* || echo "Luckly boy"

范例:&& 和 || 组合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@centos8 ~]# NAME=wang; id $NAME &> /dev/null && echo "$NAME is exist"
wang is exist

[root@centos8 ~]# NAME=wange; id $NAME &> /dev/null || echo "$NAME is not exist"
wange is not exist

[root@centos8 ~]# NAME=wange; id $NAME &> /dev/null && echo "$NAME is exist" || echo "$NAME is not exist"
wange is not exist

[root@centos8 ~]# NAME=wang; id $NAME &> /dev/null && echo "$NAME is exist" || echo "$NAME is not exist"
wang is exist

[root@centos8 ~]# NAME=wang; id $NAME &> /dev/null && echo "$NAME is exist" || echo "$NAME is not exist"
wang is exist

[root@centos8 ~]# NAME=wang; id $NAME &> /dev/null || echo "$NAME is not exist" && echo "$NAME is exist"
wang is exist

[root@centos8 ~]# NAME=wange; id $NAME &> /dev/null || echo "$NAME is not exist"&& echo "$NAME is exist"
wange is not exist
wange is exist

# 结论:如果&&和 || 混合使用,&& 要在前,|| 放在后
[root@centos8 ~]# NAME=wange; id $NAME &> /dev/null && echo "$NAME is exist" || useradd $NAME
[root@centos8 ~]# id wange
uid=1002(wange) gid=1002(wange) groups=1002(wange)

[root@centos8 ~]# NAME=wangge; id $NAME &> /dev/null && echo "$NAME is exist" || ( useradd $NAME; echo $NAME is created )
wangge is created

[root@centos8 ~]# id wangge
uid=1003(wangge) gid=1003(wangge) groups=1003(wangge)

[root@centos8 ~]# NAME=wanggege; id $NAME &> /dev/null && echo "$NAME is exist" || { useradd $NAME; echo $NAME is created; }
wanggege is created

范例:网络状态判断

1
2
3
4
5
6
7
8
9
10
[root@centos8 ~]# cat /data/scripts/ping.sh
#!/bin/bash

IP=172.16.0.1
ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || { echo "$IP is unreachable"; exit; }
echo "Script is finished"

[root@centos8 ~]# bash /data/scripts/ping.sh
172.16.0.1 is up
Script is finished

范例:

1
2
[root@rocky8 ~]# . /etc/os-release; [[ $ID == "rocky" ]] && [[ $VERSION_ID == 8* ]] && echo Rocky8 || echo CentOS8
Rocky8

范例:磁盘空间的判断

1
2
3
4
[root@centos8 ~]# cat /data/script/disk_check.sh 
#!/bin/bash
WARNING=80
SPACE_USED=`df|grep '^/dev/sd'|tr -s ' ' %|cut -d% -f5|sort -nr|head -1`[ "$SPACE_USED" -ge $WARNING ] && { echo "disk used is $SPACE_USED,will be full" ; mail -s diskwarning root; }

范例:磁盘空间和Inode号的检查脚本

1
2
3
4
5
6
7
[root@centos8 scripts]# cat disk_check.sh
#!/bin/bash

WARNING=80
SPACE_USED=`df | grep '^/dev/sd'|grep -oE '[0-9]+%'|tr -d %| sort -nr|head -1`
INODE_USED=`df -i | grep '^/dev/sd'|grep -oE '[0-9]+%'|tr -d %| sort -nr|head -1`
[ "$SPACE_USED" -gt $WARNING -o "$INODE_USED" -gt $WARNING ] && echo "DISK USED:$SPACE_USED%,INODE_USED:$INODE_USED,will be full" | mail -s "DISK Warning" root@wangxiaochun.com

使用read命令来接受输入

使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变 量,所有剩余单词都被分配给最后一 个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置 变量REPLY

格式:

1
2
3
4
5
6
7
8
read [options] [name ...]

# 常见选项
-p 'PROMPT' # 指定要显示的提示
-s # 静默输入,一般用于密码
-n N # 指定输入的字符长度N
-d 'CHAR' # 输入结束符
-t N # TIMEOUT为N秒

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@centos8 ~]# read
M51

[root@centos8 ~]# echo $REPLY
M51

[root@centos8 ~]# read NAME TITLE
wang cto

[root@centos8 ~]# echo $NAME
wang

[root@centos8 ~]# echo $TITLE
cto

[root@centos8 ~]# read -p "Please input your name: " NAME
Please input your name: wang

[root@centos8 ~]# echo $NAME
wang

范例:标准输入重定向赋值

1
2
3
4
5
6
7
8
9
10
11
[root@centos8 ~]#  read x y z <<< "I love you"
[root@centos8 ~]# echo $x
I

[root@centos8 ~]# echo $y
love

[root@centos8 ~]# echo $z
you

[root@centos8 ~]#

范例:交互式输入参数

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# vim test6.sh
#!/bin/bash

read -p "Please input your name: " NAME
read -p "Please input your age: " AGE
echo $NAME $AGE

[root@rocky86 ~]# bash test6.sh
Please input your name: jose
Please input your age: 123
jose 123

范例:管道符

1
2
3
4
5
6
#管道符是开启子进程,所以这样没值
[root@centos8 ~]# echo magedu | read NAME
[root@centos8 ~]# echo $NAME

[root@centos8 ~]# echo magedu | { read NAME; echo $NAME; }
magedu

管道符左右两边都是子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#没有开启子进程
[root@rocky86 ~]# echo $BASHPID;{ echo $BASHPID;sleep 100; }
2679
2679

#管道符左右两边都开启了子进程,
#但管道符左边的没有输出,因为重定向给右边了,右边也不接收标准输入重定向,所以没有输出

[root@rocky86 ~]# echo $BASHPID;{ echo $BASHPID;sleep 100; } | { echo $BASHPID;sleep 100; }
2679
6856

[jose@rocky86 ~]$ pstree -p | grep sleep
|-sshd(1032)-+-sshd(2648)---sshd(2672)---bash(2679)-+-bash(6855)---sleep(6858)
| | `-bash(6856)---sleep(6857)



#read 接收并查看
[root@rocky86 ~]# echo $BASHPID;{ echo $BASHPID;sleep 100; } | { read pid;echo $pid; echo $BASHPID;sleep 100; }
2679
6944
6945

[jose@rocky86 ~]$ pstree -p | grep sleep
|-sshd(1032)-+-sshd(2648)---sshd(2672)---bash(2679)-+-bash(6944)---sleep(6946)
| | `-bash(6945)---sleep(6947)

范例:面试题 read和输入重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@centos8 scripts]# cat test.txt
1 2

[root@centos8 scripts]# read i j < test.txt ; echo i=$i j=$j
i=1 j=2

[root@centos8 scripts]# echo 1 2 | read x y ; echo x=$x y=$y
x= y=

[root@centos8 ~]# echo 1 2 | ( read x y ; echo x=$x y=$y )
x=1 y=2

[root@centos8 ~]# echo 1 2 | { read x y ; echo x=$x y=$y; }
x=1 y=2

范例:判断用户输入的是否为 YES

1
2
3
4
5
[root@centos8 scripts]# cat read.sh
#!/bin/bash
read -p "Are you rich?yes or no: " ANSWER
[[ $ANSWER =~ ^([Yy]|[Yy][Ee][Ss])$ ]] && echo "You are rich" || echo "Good Good Study,Day Day Up!"

范例: 判断用户输入的是否为 YES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@ubuntu2004:~# cat yesorno.sh
#!/bin/bash

read -p "Please input yes or no: " input

answer=`echo $input| tr 'A-Z' 'a-z'`

[ $answer = 'yes' -o $answer = 'y' ] && echo YES

[ $answer = 'no' -o $answer = 'n' ] && echo NO

root@ubuntu2004:~# cat yesorno2.sh
#!/bin/bash
read -p "Please input yes or no: " input

[[ $input =~ ^([Yy][Ee][Ss]|[Yy])$ ]] && echo YES
[[ $input =~ ^([Nn][Oo]|[Nn])$ ]] && echo NO

范例: 检查主机的网络状态

1
2
3
4
5
6
7
[root@centos8 script]#cat check_host.sh
#!/bin/bash

read -p "Please input a IP: " IP
[[ "$IP" =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$
]] || { echo "IP is invalid";exit; }
ping -c1 -W1 $IP &> /dev/null && echo $IP is up || echo $IP is down

bash shell 的配置文件

bash shell的配置文件很多,可以分成下面类别

按生效范围划分两类

全局配置:针对所有用户皆有效

1
2
3
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc

个人配置:只针对特定用户有效

1
2
~/.bash_profile
~/.bashrc

shell登录两种方式分类

交互式登录

  • 直接通过终端输入账号密码登录
  • 使用 su - UserName 切换的用户

配置文件生效和执行顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#放在每个文件最前
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
~/ .bash_ profile
~/ .bashrc
/etc/bashrc #此文件执行两次


#放在每个文件最后
/etc/profile.d/*.sh
/etc/bashrc
/etc/profile
/etc/bashrc #此文件执行两次
~/.bashrc
~/.bash_profile

注意:文件之间的调用关系,写在同一个文件的不同位置,将影响文件的执行顺序

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#修改配置文件,在最前面加入内容
[root@rocky86 ~]# vim /etc/profile
# /etc/profile

echo "/etc/profile-begin"



[root@rocky86 ~]# vim /etc/bashrc
# /etc/bashrc

echo "/etc/bashrc-begin"



#重新登录,查看
......
Last login: Thu Aug 18 15:54:00 2022 from 10.0.0.1
/etc/profile-begin
/etc/bashrc-begin
/etc/bashrc-begin
[root@rocky86 ~]#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#修改配置文件,在最后面加入内容
[root@rocky86 ~]# vim /etc/profile
......
echo "/etc/profile-end"

[root@rocky86 ~]# vim /etc/bashrc
......
echo "/etc/bashrc-end"

#重新登录,查看
......
Last login: Thu Aug 18 17:04:11 2022 from 10.0.0.1
/etc/bashrc-end
/etc/profile-end
/etc/bashrc-end

非交互式登录

  • su UserName
  • 图形界面下打开的终端
  • 执行脚本
  • 任何其它的bash实例

执行顺序:

1
2
3
/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc

按功能划分分类

内容 Profile类 bashrc类
交互式登录shell配置
非交互式登录shell配置
全局配置文件 /etc/profile,/etc/profile.d/*.sh /etc/bashrc
个人配置文件 ~/.bash_profile ~/.bashrc
功能作用 定义环境变量,运行命令或脚本 定义命令别名和函数,定义本地变量

编辑配置文件生效

修改profile和bashrc文件后需生效两种方法:

  • 重新启动shell进程
  • source|. 配置文件

注意:source 会在当前shell中执行脚本,所有一般只用于执行置文件,或在脚本中调用另一个脚本的场景

范例:

1
. ~/.bashrc

Bash 退出任务

保存在~/.bash_logout文件中(用户),在退出登录shell时运行

功能:

  • 创建自动备份
  • 清除临时文件

流程控制

条件选择

条件判断分绍

单分支条件

10

多分支条件

11

12

选择执行 if 语句

格式:

1
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS;]... [ else COMMANDS; ] fi

单分支

1
2
3
if 判断条件;then
条件为真的分支代码
fi

双分支

1
2
3
4
5
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi

多分支

1
2
3
4
5
6
7
8
9
10
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码
...
else
以上条件都为假的分支代码
fi

说明:

  • 多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
  • if 语句可嵌套

范例:判断操作系统

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# vim os-v1.sh
#!/bin/bash

. /etc/os-release

if [ $ID = "ubuntu" ]; then
echo "ubuntu"
elif [[ $ID =~ rocky|centos|rhel ]]; then
echo "rhel"
else
echo "other os"
exit 100
fi

范例:

1
2
3
4
5
6
7
8
9
#根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null; then
echo 'station1 is UP'
elif grep -q 'station1' ~/maintenance.txt; then
echo 'station1 is undergoing maintenance'
else
echo 'station1 is unexpectedly DOWN!'
exit 1
fi

范例:身体质量指数 (BMI)

参考链接:

1
https://baike.baidu.com/item/标准体重/1694152?fr=aladdin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@rocky86 ~]# cat if_bmi.sh
#!/bin/bash

read -p "请输入身高(m为单位): " HIGH

if [[ ! "$HIGH" =~ ^[0-2](\.[0-9]{1,2})?$ ]];then
echo "输入错误的身高!"
exit 1
fi

read -p "请输入体重(kg为单位): " WEIGHT

if [[ ! "$WEIGHT" =~ ^[0-9]{1,3}$ ]];then
echo "输入错误的体重!";
exit 2;
fi

BMI=`echo $WEIGHT/$HIGH^2|bc`

if [ $BMI -le 18 ] ;then
echo "太瘦了,多吃点!"
elif [ $BMI -lt 24 ] ;then
echo "身材很棒!"
else
echo "太胖了,注意节食,加强运动!"
fi

条件判断 case 语句

格式:

1
case WORD in [PATTERN [| PATTERN] ... ) COMMANDS ;;]... esac
1
2
3
4
5
6
7
8
9
10
11
12
case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
默认分支
;;
esac

case支持glob风格的通配符:

1
2
3
4
*                   # 任意长度任意字符
? # 任意单个字符
[] # 指定范围内的任意单个字符
| # 或者,a|b

范例:判断用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@rocky86 ~]# vim case_yesorno.sh
#!/bin/bash

read -p "Do you agree(yes/no)? " INPUT

INPUT=`echo $INPUT | tr 'A-Z' 'a-z'`

case $INPUT in
y|yes)
echo "You input is YES"
;;
n|no)
echo "You input is NO"
;;
*)
echo "Input fales,please input yes or no!"
;;
esac

范例:判断用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky86 ~]# vim case_yesorno2.sh
#!/bin/bash

read -p "Do you agree(yes/no)" INPUT
case $INPUT in
[yY]|[yY][eE][sS])
echo "You input is YES"
;;
[nN]|[nN][oO])
echo "You input is NO"
;;
*)
echo "Input false , please input yes or no!"
;;
esac

范例: 文件后缀处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@rocky86 ~]# vim suffix.sh
#!/bin/bash
read -p "please input a file: " FILE
SUFFIX=`echo $FILE | grep -Eo "[^.]+$"`
case $SUFFIX in
gz)
echo gzip
;;
bz2)
echo bzip2
;;
xz)
echo xz
;;
Z)
echo compress
;;
zip)
echo zip
;;
*)
echo other
;;
esac

范例:运维菜单实现版本2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[root@rocky86 ~]# vim work_menu.sh
#!/bin/bash

echo -en "\E[$[RANDOM%7+31];1m"
cat <<EOF
请选择:
1)备份数据库
2)清理日志
3)软件升级
4)软件回滚
5)删库跑路
EOF

echo -en '\E[0m'

read -p "请输入上面数字1-5: " MENU

case $MENU in
1)
echo "执行备份数据库"
#./backup.sh
;;
2)
echo "清理日志"
;;
3)
echo "软件升级"
;;
4)
echo "软件回滚"
;;
5)
echo "删库跑路"
;;
*)
echo "INPUT FALSE!"
esac

循环

循环执行介绍

将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件

重复运行次数

  • 循环次数事先已知
  • 循环次数事先未知

常见的循环的命令:for, while, until

程序先进行语句判断,如真则执行循环语句,然后再进行语句判断,直至语句判断失败才跳出;

13

循环 for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#CentOS7的for帮助比CentOS8全面
[root@centos7 ~]#help for
for: for NAME [in WORDS ... ] ; do COMMANDS; done
Execute commands for each member in a list.

The `for' loop executes a sequence of commands for each member in a
list of items. If `in WORDS ...;' is not present, then `in "$@"' is
assumed. For each element in WORDS, NAME is set to that element, and
the COMMANDS are executed.

Exit Status:
Returns the status of the last command executed.
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
Arithmetic for loop.

Equivalent to
(( EXP1 ))
while (( EXP2 )); do
COMMANDS
(( EXP3 ))
done
EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is
omitted, it behaves as if it evaluates to 1.

Exit Status:
Returns the status of the last command executed.

格式1:

1
2
3
4
5
6
7
8
9
10
11
12
13
for NAME [in WORDS ... ] ; do COMMANDS; done

#方式1
for 变量名 in 列表;do
循环体
done


#方式2
for 变量名 in 列表
do
循环体
done

执行机制:

  • 依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
  • 如果省略 [in WORDS … ] ,此时使用位置参数变量 in “$@”

for 循环列表生成方式:

  • 直接给出列表
  • 整数列表:如 {start..end}
  • 返回列表的命令:如 $(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:$@,$*,$#

范例:面试题,计算1+2+3+…+100 的结果

1
2
3
4
5
6
7
8
[root@centos8 ~]# sum=0;for i in {1..100};do let sum+=i;done ;echo $sum
sum=5050
[root@centos8 ~]# seq -s+ 100|bc
5050
[root@centos8 ~]# echo {1..100}|tr ' ' +|bc
5050
[root@centos8 ~]# seq 100|paste -sd +|bc
5050

范例: 100以内的奇数之和

1
2
3
4
5
6
[root@centos8 ~]# sum=0;for i in {1..100..2};do let sum+=i;done;echo sum=$sum
sum=2500
[root@centos8 ~]# seq -s+ 1 2 100| bc
2500
[root@centos8 ~]# echo {1..100..2}|tr ' ' + | bc
2500

范例:从变量中获取列表

1
2
3
4
5
6
7
8
9
10
[root@centos8 ~]# cat /data/scripts/for_sum.sh 
#!/bin/bash
sum=0
for i in $* ; do
let sum+=i
done
echo sum=$sum

[root@centos8 ~]# bash /data/scripts/for_sum.sh 1 2 3 4 5 6
sum=21

范例: 批量创建用户

1
2
3
4
5
6
[root@centos8 ~]# cat createuser.sh 
#!/bin/bash
[ $# -eq 0 ] && { echo "Usage: createuser.sh USERNAME ..." ;exit 1 ; }
for user ;do
id $user &> /dev/null && echo $user is exist || { useradd $user ; echo $user is created; }
done

范例: 批量创建用户和并设置随机密码

1
2
3
4
5
6
7
8
9
[root@centos8 script]#cat user_for.sh
#!/bin/bash
for i in {1..10};do
useradd user$i
PASS=`cat /dev/urandom | tr -dc '[:alnum:]' |head -c12`
echo $PASS | passwd --stdin user$i &> /dev/null
echo user$i:$PASS >> /data/user.log
echo "user$i is created"
done

范例: 九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
13
for i in {1..9};do
for j in `seq $i`;do
echo -e "${j} * ${i}=$[j*i]\t\c"
done
echo
done

for j in {1..9};do
for i in `seq $j`;do
echo -e "\E[1;$[RANDOM%7+31]m${i}x${j}=$[i*j]\E[0m\t\c"
done
echo
done

范例: 倒状的九九乘法表

1
2
3
4
5
6
for i in {1..9};do
for j in $(seq `echo $[10-$i]`);do
echo -ne "${j}x`echo $[10-i]`=$(((10-i)*j))\t"
done
echo
done

生产案例:将指定目录下的文件所有文件的后缀改名为 bak 后缀

1
2
3
4
5
6
DIR=/data/test
cd $DIR || { echo 无法进入 $DIR;exit 1; }
for FILE in * ;do
PRE=`echo $FILE|grep -Eo ".*\."`
mv $FILE ${PRE}bak
done

范例:M37期面试题,要求将目录YYYY-MM-DD/中所有文件,移动到YYYY-MM/DD/下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#1.创建YYYY-MM-DD格式的目录,当前日期一年前365天到目前共365个目录,里面有10个文件.log后缀的文件
[root@centos8 ~]#cat for_dir.sh
#!/bin/bash
PDIR=/data/test
for i in {1..365};do
DIR=`date -d "-$i day" +%F`
mkdir -pv $PDIR/$DIR
cd $PDIR/$DIR
for j in {1..10};do
touch $RANDOM.log
done
done

#2.将上面的目录移动到YYYY-MM/DD/下
#!/bin/bash

DIR=/data/test
cd $DIR || { echo 无法进入 $DIR;exit 1; }
for subdir in * ;do
YYYY_MM=`echo $subdir |cut -d"-" -f1,2`
DD=`echo $subdir |cut -d"-" -f3`
[ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/null
mv $subdir/* $YYYY_MM/$DD
done
rm -rf $DIR/*-*-*

面试题:扫描一个网段:192.168.1.0/24,判断此网段中主机在线状态,将在线的主机的IP打印出来

1
2
3
4
5
6
7
8
#!/bin/bash
NET=192.168.1
for ID in {1..254}; do
{
ping -c1 -w1 $NET.$ID &>/dev/null && echo $NET.$ID is up | tee -a host_list.log || echo $NET.$ID is down
}&
done
wait

M47期面试题

1
有若干只兔和鸡,兔和鸡加起来一共有100条腿,请写一个简单的shell算出兔和鸡各多少只可能组合(假设所有的鸡和兔子的腿都是健全的,且兔和鸡至少为1只)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/bin/bash
#暴力解法,无优化,硬循环,循环 10000次
count=0
for x in {1..100}; do
for y in {1..100};do
let count++
if [ $[4*x + 2*y] -eq 100 ]; then
echo "小白兔="$x"; 小鸡="$y
fi
done
done
echo ""
echo "循环次数: " $count

#加break,在满足条件的情况下,退出里层循环,减少循环次数
#在小白兔的数量确定的情况下,则小鸡的数量肯定也是只有一个值能满足,所以条件成立就可以直接退出
count=0
for x in {1..100}; do
for y in {1..100};do
let count++
if [ $[4*x + 2*y] -eq 100 ]; then
echo "小白兔="$x"; 小鸡="$y
break
fi
done
done
echo ""
echo "循环次数: " $count
#接着优化
#总共100条腿
#一只小白兔4条腿,一只小鸡2条腿,所以,最多25只小白兔,最多50只小鸡
count=0
for x in {1..24}; do
for y in {1..48};do
let count++
if [ $[4*x + 2*y] -eq 100 ]; then
echo "小白兔="$x"; 小鸡="$y
break
fi
done
done
echo ""
echo "循环次数: " $count

格式2

双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的

变量操作:

14

1
2
3
4
5
6
for (( exp1; exp2; exp3 )); do COMMANDS; done

for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done

说明:

  • 控制变量初始化:仅在运行到循环代码段时执行一次
  • 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
for((sum=0,i=1;i<=100;i++));do
let sum+=i
done
echo sum=$sum
for((sum=0,i=1;i<=100;sum+=i,i++));do
true
done
echo sum=$sum


[root@ubuntu1804 ~]#bash sum.sh
sum=5050
sum=5050

范例:九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#语法1实现
for i in {1..9};do
for j in `seq $i`;do
echo -e "${j}x$i=$((j*i))\t\c"
done
echo
done
#语法2实现
for((i=1;i<10;i++));do
for((j=1;j<=i;j++));do
echo -e "${j}x$i=$((j*i))\t\c"
done
echo
done

范例:等腰三角形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
read -p "请输入三角形的行数: " line
for((i=1;i<=line;i++));do
for((k=0;k<=line-i;k++));do
echo -e ' \c'
done
for((j=1;j<=2*i-1;j++));do
echo -e '*\c'
done
echo
done

[root@centos8 scripts]#bash for_triangle.sh
请输入三角形的行数: 10
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************

范例:生成进度

1
2
[root@rocky8 ~]# for ((i = 0; i <= 100; ++i)); do printf "\e[4D%3d%%" $i;sleep 0.1s; done
100%[root@rocky8 ~]#

循环 while

格式:

1
2
3
4
5
6
7
8
9
10
while COMMANDS; do COMMANDS; done

while CONDITION; do
循环体
done

while CONDITION
do
循环体
done

说明:

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

进入条件:CONDITION为 true

退出条件:CONDITION为 false

无限循环

1
2
3
4
5
6
7
while true; do
循环体
done

while : ; do
循环体
done

范例:

1
2
[root@centos8 ~]# sum=0;i=1;while ((i<=100));do let sum+=i;let i++;done;echo $sum
5050

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#配置发邮件的邮箱
[root@centos8 ~]#cat .mailrc
set from=29308620@qq.com
set smtp=smtp.qq.com
set smtp-auth-user=29308620@qq.com
set smtp-auth-password=esvnhbnqocirbicf
set smtp-auth=login
set ssl-verify=ignore


[root@centos8 ~]#cat while_diskcheck.sh
#!/bin/bash
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" 29308620@qq.com
fi
sleep 10
done

范例: 国际象棋棋盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/bash
flag=0
for i in {1..8};do
flag=$[$i%2]
for j in {1..8};do
if [ $[$j%2] = $flag ]; then
echo -en "\033[41m \033[0m";
else
echo -en "\033[43m \033[0m";
fi
done
echo
done
echo
echo

flag=0
for i in {1..8};do
flag=$[$i%2]
for j in {1..8};do
if [ $[$j%2] = $flag ]; then
echo -en "\033[41m \033[0m";
else
echo -en "\033[43m \033[0m";
fi
done

echo

for j in {1..8};do
if [ $[$j%2] = $flag ]; then
echo -en "\033[41m \033[0m";
else
echo -en "\033[43m \033[0m";
fi
done

echo
for j in {1..8};do
if [ $[$j%2] = $flag ]; then
echo -en "\033[41m \033[0m";
else
echo -en "\033[43m \033[0m";
fi
done
echo
done

while 特殊用法 while read

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

格式:

1
2
3
while read line; do
循环体
done < /PATH/FROM/SOMEFILE

说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@centos8 ~]#echo magedu | read X ; echo $X

[root@centos8 ~]#echo magedu | while read X ; do echo $X;done
magedu

[root@centos8 ~]#echo magedu | { read X ; echo $X; }
magedu

[root@centos8 ~]#echo magedu | ( read X ; echo $X )
magedu

[root@centos8 ~]#echo mage wang zhang | ( read X Y Z; echo $X $Y $Z )
mage wang zhang

[root@centos8 ~]#echo mage wang zhang | while read X Y Z; do echo $X $Y $Z;done
mage wang zhang

范例:检测网站是否可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# cat url.txt 
http://www.baidu.com
http://www.google.com
http://www.facebook.com
http://www.jd.com

[root@rocky86 ~]# cat check_url.sh
#!/bin/bash

while read url;do
curl $url &> /dev/null || echo "$url fail"
done < url.txt

#管道写法
cat url.txt | while read url;do
curl $url &> /dev/null || echo "$url fail"
done

范例:查看/sbin/nologin的shell类型的用户名和UID

1
2
3
4
5
6
7
8
[root@rocky86 ~]# cat nologin.sh
#!/bin/bash

while read line;do
if [[ "$line" =~ /sbin/nologin$ ]];then
echo $line | cut -d":" -f1,3
fi
done < /etc/passwd

循环 until

格式:

1
2
3
4
5
until CONDITION; do COMMANDS; done

until CONDITION; do
循环体
done

说明:

进入条件: CONDITION 为false

退出条件: CONDITION 为true

范例:

1
2
3
4
5
[root@centos8 ~]#sum=0;i=1;until ((i>100));do let sum+=i;let i++;done;echo $sum
5050

[root@rocky86 ~]# sum=0;i=1;until ! ((i<=100));do let sum+=i;let i++;done;echo $sum
5050

无限循环

1
2
3
until false; do
循环体
Done

循环控制语句 continue

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

格式:

1
2
3
4
5
6
7
8
9
while CONDITION1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done

范例:

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# for i in {1..5};do [ $i -eq 3 ] && continue;echo $i;done
1
2
4
5

[root@rocky86 0821]# for i in {1..5};do if [ $i -eq 3 ];then continue;fi; echo $i;done
1
2
4
5

范例:循环嵌套中的continue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@rocky86 ~]# cat continue_for.sh
#!/bin/bash

for i in {1..4}; do
for j in {a..c};do
if [ $j = 'b' ]; then
continue
fi
echo $i $j
done
echo
done

#执行
[root@rocky86 ~]# bash continue_for.sh
1 a
1 c
2 a
2 c
3 a
3 c
4 a
4 c

continue 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 ~]# cat continue_for.sh
#!/bin/bash

for i in {1..4}; do
for j in {a..c};do
if [ $j = 'b' ]; then
continue 2
fi
echo $i $j
done
echo
done

#执行
[root@rocky86 ~]# bash continue_for.sh
1 a
2 a
3 a
4 a

循环控制语句 break

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

格式:

1
2
3
4
5
6
7
8
9
while CONDITION1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done

范例:

1
2
3
[root@rocky86 ~]# for i in {1..5};do [ $i -eq 3 ] && break;echo $i;done
1
2

范例:嵌套循环中的break

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 ~]# cat break_for.sh
#!/bin/bash

for i in {1..4}; do
for j in {a..c};do
if [ $j = 'b' ]; then
break
fi
echo $i $j
done
echo
done

#执行
[root@rocky86 ~]# bash break_for.sh
1 a
2 a
3 a
4 a

break 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# cat break_for.sh
#!/bin/bash

for i in {1..4}; do
for j in {a..c};do
if [ $j = 'b' ]; then
break 2
fi
echo $i $j
done
echo
done


#执行
[root@rocky86 ~]# bash break_for.sh
1 a

范例:猜字游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky86 ~]# cat guess.sh 
#!/bin/bash

NUM=$[RANDOM%10]
while read -p "输入 0-9 之间的数字: " INPUT ;do
if [ $INPUT -eq $NUM ];then
echo "恭喜你猜对了!"
break
elif [ $INPUT -gt $NUM ];then
echo "数字太大了,重新猜!"
else
echo "数字太小了,重新猜!"
fi
done

循环控制 shift 命令

shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。

参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@rocky86 ~]# cat shift.sh
#!/bin/bash

until [ -z "$1" ]; do
echo "$@ "
shift
done

[root@rocky86 ~]# bash s.sh a b c d e f g
a b c d e f g
b c d e f g
c d e f g
d e f g
e f g
f g
g

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky86 ~]# cat shift.sh 
#!/bin/bash

while [ -n "$1" ];do
echo $1
shift
done


[root@rocky86 ~]# bash shift.sh a b c d
a
b
c
d

shift 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@rocky86 ~]# cat shift2.sh 
#!/bin/bash

while [ -n "$1" ];do
echo $1
shift 2
if [ $? -ne 0 ];then
break
fi
done


[root@rocky86 ~]# bash shift2.sh 1 2 3 4 5 6
1
3
5

范例:批量创建用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# cat shift_users.sh 
#!/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"

循环与菜单 select

格式:

1
2
3
4
5
select NAME [in WORDS ... ;] do COMMANDS; done

select NAME in list ;do
循环体命令
done

说明:

  • select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示 PS3 提示符, 等待用户输入
  • 用户输入菜单列表中的某个数字,执行相应的命令
  • 用户输入菜单列表中的某个数字,会将对应的WORD值赋值给NAME变量
  • 用户输入被保存在内置变量 REPLY 中
  • select 是个无限循环,因此要用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环
  • select 经常和 case 联合使用
  • 与 for 循环类似,可以省略 in list,此时使用位置参量

范例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# PS3="中午吃什么: "; select i in 肯德基 麦当劳 沙县小吃 兰州拉面; do echo $i;done 
1) 肯德基
2) 麦当劳
3) 沙县小吃
4) 兰州拉面
中午吃什么: 1
肯德基
中午吃什么: 2
麦当劳
中午吃什么: 100

中午吃什么:

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@rocky86 ~]# cat select.sh 
#!/bin/bash

PS3="请选择: "
select i in 备份数据库 清理日志 重启服务 退出; do
case $REPLY in
1)
echo "开始备份数据库..."
sleep 2
echo "数据库备份完成"
;;
2)
echo "开始清理日志..."
sleep 2
echo "清理完成"
;;
3)
echo "开始重启服务..."
sleep 2
echo "重启完成"
;;
4)
break
;;
*)
echo "选项错误"
;;
esac
done

函数 function

函数介绍

函数 function

是由若干条shell命令组成的语句块,实现代码重用和模块化编程

它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部 分

函数和shell程序区别

  • Shell程序在子Shell中运行
  • 函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改

管理函数

函数由两部分组成:函数名和函数体

查看帮助

1
2
3
[root@rocky86 ~]# help function
......
......

定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#语法一
func_name(){
...函数体...
}

#语法二
function func_name {
...函数体...
}

#语法三
function func_name(){
...函数体...
}

查看函数

1
2
3
4
5
6
7
8
9
10
11
#查看当前已定义的函数名
declare -F

#查看当前已定义的函数定义
declare -f

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

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

删除函数

1
unset func_name

函数调用

函数的调用方式

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止

交互式环境调用函数

交互式环境下定义和使用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#定义
[root@rocky86 ~]# test_func(){
> echo "this is cli function"
> }

#调用
[root@rocky86 ~]# test_func
this is cli function

#查看
[root@rocky86 ~]# declare -f test_func
test_func ()
{
echo "this is cli function"
}

在脚本中定义及使用函数

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

范例:在脚本中使用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# cat func1.sh 
#!/bin/bash

#函数定义
hello(){
echo "this is test function - hello"
}

#第一次调用
hello

#第二次调用
hello

范例:函数调用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@rocky86 ~]# cat func2.sh 
#!/bin/bash

func1(){
echo "func1"
func2
}
func2(){
echo "func2"
}
func1

#调用
[root@rocky86 ~]# bash func2.sh
func1
func2

范例:函数间互相调用,死循环,爆栈,耗尽资源,高级语言会有此错误检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@rocky86 ~]# cat func2.sh 
#!/bin/bash

func1(){
echo "func1"
sleep 1
func2
}

func2(){
echo "func2"
sleep 1
func1
}

func1

使用函数文件

可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数

函数文件名可任意选取,但最好与相关任务有某种联系,例如:functions

一旦函数文件载入shell,就可以在命令行或脚本中调用函数。

可以使用delcare -f 或set 命令查看所有定 义的函数,其输出列表包括已经载入shell的所有函数

若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

实现函数文件的过程:

  1. 创建函数文件,只存放函数的定义
  2. 在shell脚本或交互式shell中加载函数文件
  3. 调用函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#定义函数文件
[root@rocky86 ~]# cat functions
#!/bin/bash
func1(){
echo "functions-func1"
}
func2(){
echo "functions-func2"
}

#命令行引入
[root@rocky86 ~]# . functions

#这种写法也可以
[root@rocky86 ~]# source functions

#函数调用
[root@rocky86 ~]# func1;func2
functions-func1
functions-func2

#脚本文件中引用
[root@rocky86 ~]# cat func3.sh
#!/bin/bash

. functions
func1
func2

#执行
[root@rocky86 ~]# bash func3.sh
functions-func1
functions-func2

函数返回值

函数默认返回值是函数体中最后一条命令的退出状态码;

也可以使用return 自定义函数的返回值,在函数中使用 return,return 之后的语句将不会被执行

return 的用法:

return语句 返回值
return 由return语句的前一行命令执行结果决定
return 0 无错误返回
return 1-255 有错误返回

范例:获取函数返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[root@rocky86 0820]# cat var4.sh
#!/bin/bash

var_func1(){
echo "func1 ======="
}

#默认返回值
var_func2(){
echo "func2 ======="
return
}

var_func3(){
echo "func3 ======"
errcmd
}

#自定义返回值,return 之后的语句将不会被执行
var_func4(){
echo "func4 aaaaaaaaaaaaaa"
return 123
echo "func4 bbbbbbbbbb"
}

var_func1
echo -e "func1 返回值是 $?\n"

var_func2
echo -e "func2 返回值是 $?\n"

var_func3
echo -e "func3 返回值是 $?\n"

var_func4
echo -e "func4 返回值是 $?\n"

#执行
[root@rocky86 0820]# bash var4.sh
func1 =======
func1 返回值是 0

func2 =======
func2 返回值是 0

func3 ======
var4.sh: line 26: errcmd: command not found
func3 返回值是 127

func4 aaaaaaaaaaaaaa
func4 返回值是 123

范例:return 和 exit 的区别

return 只会中断函数执行,但exit 会中断整个脚本的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 0820]# cat var5.sh 
#!/bin/bash

var_func1(){
echo "func1 ======="
return 123
echo "func1 ======="
}

echo 123
var_func1
echo "func1 返回值是 "$?
echo 456

[root@rocky86 0820]# bash var5.sh
123
func1 =======
func1 返回值是 123
456
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 0820]# cat var6.sh
#!/bin/bash

var_func1(){
echo "func1 ======="
exit 123
echo "func1 ======="
}
echo 123
var_func1
echo "func1 返回值是 "$?
echo 456

[root@rocky86 0820]# bash var6.sh
123
func1 =======

[root@rocky86 0820]# echo $?
123

环境函数

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

定义环境函数:

1
2
export -f function_name 
declare -xf function_name

查看环境函数:

1
2
export -f
declare -xf

范例:定义和查看

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 0820]# cli_func1(){ echo "cli-func1"; }; cli_func2(){ echo "cli-func2"; };export -f cli_func2;
[root@rocky86 0820]# export -f
cli_func2 ()
{
echo "cli-func2"
}
declare -fx cli_func2
which ()
{
( alias;
eval ${which_declare} ) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot$@
}
declare -fx which

范例:调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@rocky86 0820]# cat var7.sh 
#!/bin/bash

echo "var7 start BASHPID $BASHPID ====================== "
cli_func1
cli_func2
echo "var7 end ======================"

#执行
[root@rocky86 0820]# echo $BASHPID;
2259

[root@rocky86 0820]# ./var7.sh
var7 start BASHPID 5122 ======================
./var7.sh: line 16: cli_func1: command not found
cli-func2
var7 end ======================

函数参数

函数可以接受参数:

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

范例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# cat msg.sh
#!/bin/bash

msg(){
if [ -n "$1" -a -n "$2" ];then
echo -e "\E[1;$2m$1\E[0m"
fi
}

msg "ok" 31
msg "fail" 32
msg "FBI-Warning" 34

函数变量

变量作用域:

变量类型 特点
普通变量 只有当前shell进程中有效,函数外定义,可以在函数内修改
环境变量 当前shell和子shell有效
本地变量 作用域在函数内,函数结束会自动销毁

在函数中定义本地变量

1
local NAME=VALUE

示例:普通变量可以在函数中被修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[root@rocky86 0820]# cat functions 
#!/bin/bash
var_func2(){
echo "func2 start ==================="
echo $var1
var1=789
echo $var1
echo "func2 end ====================="
}


[root@rocky86 0820]# cat var1.sh
#!/bin/bash
. functions

var1=123

var_func1(){
echo "func1 start ==================="
echo $var1
var1=456
echo $var1
echo "func1 end ===================="
}

echo $var1
var_func1
echo $var1
var_func2
echo $var1


#执行
[root@rocky86 0820]# bash var1.sh
123
func1 start ===================
123
456
func1 end ====================
456
func2 start ===================
456
789
func2 end =====================
789

示例:本地变量只能在函数内定义

1
2
3
4
5
6
7
8
9
10
[root@rocky86 0820]# cat var2.sh 
#!/bin/bash

local var1=123
echo $var1


#执行报错
[root@rocky86 0820]# bash var2.sh
var2.sh: line 14: local: can only be used in a function

示例:本地变量只作用在函数内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@rocky86 0820]# cat var2.sh 
#!/bin/bash

var1=123
var_func1(){
echo "func1 start ========================"
echo $var1
local var1=456
local var2=789
echo $var1 $var2
echo "func1 end ========================="
}
echo $var1 $var2
var_func1
echo $var1 $var2
#执行
[root@rocky86 0820]# bash var2.sh
123
func1 start ========================
123
456 789
func1 end =========================
123

示例:环境变量可以传到子进程中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@rocky86 0820]# cat var33.sh 
#!/bin/bash

echo -e "var33.sh start BASHPID $BASHPID ==================\n"

echo "var1=$var1 var2=$var2 var3=$var3"

echo -e "\nvar33.sh end =====================================\n"


[root@rocky86 0820]# cat var3.sh
#!/bin/bash

var1=123
export var2=456
declare -x var3=789

echo -e "var3.sh start BASHPID $BASHPID ============\n\n"
bash var33.sh
echo "var1=$var1 var2=$var2 var3=$var3"

[root@rocky86 0820]# bash var3.sh
var3.sh start BASHPID 4490 ============

var33.sh start BASHPID 4491 ==================

var1= var2=456 var3=789

var33.sh end =====================================

var1=123 var2=456 var3=789

函数递归

函数递归:

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

递归特点:

  • 函数内部自已调用自已
  • 必须有结束函数的出口语句,防止死循环

范例:无出口递归

1
2
3
4
5
6
7
8
9
10
[root@rocky86 ~]# num=1;cli_func1(){ echo "num="$num; let num++; cli_func1; };cli_func1
......
......
num=8587

Connection closed.

Disconnected from remote host(rocky86-150) at 11:44:30.

Type `help' to learn how to use Xshell prompt.

阶乘

阶乘是基斯顿·卡曼(Christian Kramp,1760~1826)于 1808 年发明的运算符号,是数学术语。

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!

阶乘公式

1
2
n!=1×2×3×...×(n-1)×n
0!=1, n!=(n-1)!×n

用递归实现阶乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky86 ~]# cat fac.sh 
#!/bin/bash

fac(){
if [ $1 -gt 1 ];then
echo $[$1 * $(fac $[$1-1])]
else
echo 1
fi
}

fac $1

[root@rocky86 ~]# bash fac.sh 5
120

斐波拉契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引 入,故又称为“兔子数列”

指的是这样一个数列:1、1、2、3、5、8、13、21、34、……

在数学上,斐波那契数列以如下被以递推的方法定义

1
F(0)=0, F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2, n ∈ N*)

用递归求斐波拉契数列第N项的值

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# cat fib.sh 
#!/bin/bash

fib(){
if [ $1 -gt 1 ];then
echo $[ $[ $(fib $[$1-1]) + $(fib $[$1-2]) ] ]
else
echo $1
fi
}

fib $1

循环加递归,求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 ~]# cat fib2.sh 
#!/bin/bash

fib(){
if [ $1 -gt 1 ];then
echo $[ $(fib $[$1-1]) + $(fib $[$1-2]) ]
else
echo $1
fi
}

sum=0
for ((i=$1;i<=$2;i++));do
let sum+=$(fib $i)
done
echo $sum

[root@rocky86 ~]# bash fib2.sh 1 10
143

斐波拉契数列递归算法展开

15

逻辑炸弹

fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。

由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源

1
2
:(){ :|:& };:
bomb(){ bomb | bomb & };bomb

其它脚本相关工具

信号捕捉 trap

trap 命令可以捕捉信号,修改信号原来的功能,实现自定义功能

在脚本或程序的执行过程中,我们可以通过发送信号的方式,打断或终止程序的执行过程,为了避免这种情况,我们可以使用信号捕捉,来 自定义信号处理。

格式:

1
2
3
4
5
6
7
8
9
10
trap [-lp] [[arg] signal_spec ...]

#常用选项
-l #显示所有信号
-p #显示所有自定义的信号

trap 'command' signal #自定义指定信号的处理方式
trap '' signal #忽略指定的信号
trap '-' signal #恢复信号默认操作
trap func EXIT #退出时执行func

查看所有信号:

1
2
3
4
5
[root@rocky86 ~]# trap -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
......
......

信号的三种表示方法

1
2
3
4
5
3) SIGQUIT

3 #信号ID
SIGQUIT #完整写法,大小写都支持
QUIT #简短写法,大小写都支持

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@rocky86 ~]# cat trap.sh
#!/bin/bash

trap "echo '捕捉到了 ctrl+c信号'" int
trap "echo '捕捉到了 ctrl+\信号'" quit

trap -p
for i in {1..15};do
sleep 1
echo $i
done

trap '' int
trap -p
for i in {16..30};do
sleep 1
echo $i
done

trap '-' int
trap -p
for i in {31..45};do
sleep 1
echo $i
done

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# cat trap_fin.sh
#!/bin/bash

finish(){
echo "finish at `date +%F-%T`" | tee -a /tmp/trap_fin.log
}

trap finish exit

while true;do
echo "`date +%F-%T`"
sleep 1
done

创建临时文件 mktemp

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

格式:

1
2
3
4
5
6
7
mktemp [OPTION]... [TEMPLATE]

#常用选项
-d|--directory #创建目录
-u|--dry-run #只输出命令,不执行
-p DIR|--tmpdir[=DIR] #指明临时文件所存放目录位置
-t #将文件保存到$TMPDIR 定义的目录中,如果该变量未定义,则保存到/tmp 目录中

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@rocky86 ~]# mktemp
/tmp/tmp.YQXxc3bHdI

[root@rocky86 ~]# mktemp XXXX
UYtW

#如果指定了文件名,则后面一定要有X,至少要3个,X会被替换成随机串
[root@rocky86 ~]# mktemp test
mktemp: too few X's in template ‘test’

[root@rocky86 ~]# mktemp testXXX
test19C


#创建并赋值给变量
[root@rocky86 ~]# tmp=`mktemp`
[root@rocky86 ~]# echo $tmp
/tmp/tmp.wQAdrqn1UR


#创建目录
[root@rocky86 ~]# mktemp -d
/tmp/tmp.2iLac1ruHt

[root@rocky86 ~]# ll /tmp/tmp.2iLac1ruHt/
total 0

范例:假删除

1
2
3
4
5
6
[root@rocky86 ~]# cat trash.sh
#!/bin/bash

DIR=`mktemp -d /tmp/trash-$(date +%F_%H-%M-%S)XXXXXX`
mv $* $DIR
echo $* is move to $DIR

安装复制文件 install

install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合

格式

1
2
3
4
5
6
7
8
9
10
11
12
install [OPTION]... [-T] SOURCE DEST
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...


#常用选项
-m|--mode=MODE #指定权限,默认755
-v|--verbose #显示过程
-o|--owner=OWNER #指定属主
-g|--group=GROUP #指定属组
-d|--directory DIR #指定目录,如果不存在就创建

范例:

1
2
3
4
5
6
7
8
9
10
#如果目录不存在,则创建目录,并将权限设成777,如果目录存在,则修改其权限
[root@rocky86 ~]# install -m 777 -d testdir
[root@rocky86 ~]# ll -d testdir
drwxrwxrwx. 2 root root 6 May 25 12:46 testdir


#将文件复制到指定目录,并指定属主属组,权限
[root@rocky86 ~]# install -m 777 -o mage -g root fin.sh /tmp/fin-tmp.sh
[root@rocky86 ~]# ll /tmp/fin-tmp.sh
-rwxrwxrwx. 1 mage root 153 May 25 12:58 /tmp/fin-tmp.sh

数组 array

数组介绍

变量:存储单个元素的内存空间

数组:存储多个元素的连续的内存空间,相当于多个变量的集合

数组名和索引

索引的编号从0开始,属于数值索引

索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash 4.0版本之后开始支持

bash的数组支持稀疏格式(索引不连续)

声明数组

1
2
3
4
5
#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME

#关联数组必须先声明,再使用
declare -A ARRAY_NAME

注意:两者不可相互转换

数组赋值

一次只赋值一个元素

格式:

1
ARRAY_NAME[INDEX]=VALUE

范例:

1
2
[root@rocky86 ~]# weekdays[0]="Sunday"
[root@rocky86 ~]# weekdays[4]="Thursday"

一次赋值全部元素

格式:

1
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

范例:

1
2
3
4
[root@rocky86 ~]# title=("ceo" "coo" "cto")
[root@rocky86 ~]# num=({0..10})
[root@rocky86 ~]# alpha=({a..g})
[root@rocky86 ~]# file=( *.sh )

只赋值特定元素

格式:

1
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

范例:

1
[root@rocky86 ~]# weekdays=([0]="Sunday",[4]="Thursday")

交互式数组值对赋值

格式:

1
read -a ARRAY

范例:

1
2
[root@rocky86 ~]# read -a test
a b c d

显示所有数组

格式:

1
declare -a

范例:

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# declare -a
declare -a ARRAY_NAME=([0]="VALUE")
declare -a BASH_ARGC=()
declare -a BASH_ARGV=()
declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="7")
declare -a BASH_LINENO=()
declare -ar BASH_REMATCH=()
declare -a BASH_SOURCE=()
declare -ar BASH_VERSINFO=([0]="4" [1]="4" [2]="20" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
declare -a DIRSTACK=()
......

引用数组

引用特定的数组元素

格式:

1
2
#如果省略[INDEX]表示引用下标为0的元素
${ARRAY_NAME[INDEX]}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@rocky86 ~]# echo ${title[1]}
coo

[root@rocky86 ~]# echo ${title}
ceo

[root@rocky86 ~]# echo ${title[2]}
cto

[root@rocky86 ~]# echo ${title[3]}
[root@rocky86 ~]#

区分这三种写法

1
2
3
4
5
6
[root@rocky86 ~]# echo $title
ceo
[root@rocky86 ~]# echo ${title[0]}
ceo
[root@rocky86 ~]# echo $title[0]
ceo[0]

引用数组所有元素

格式:

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

范例:

1
2
3
4
5
[root@rocky86 ~]# echo ${title[@]}
ceo coo cto

[root@rocky86 ~]# echo ${title[*]}
ceo coo cto

遍历数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@rocky86 ~]# arr=({a..e})
[root@rocky86 ~]# for i in ${arr[*]};do echo $i; done;
a
b
c
d
e

[root@rocky86 ~]# unset arr[2];unset arr[4]


#用连续下标遍历,下标不连续时会有问题
[root@rocky86 ~]# for ((i=0;i<${#arr[*]};i++));do echo ${arr[$i]};done
a
b

[root@rocky86 ~]#


#先取出所有下标,再遍历
[root@rocky86 ~]# for i in ${!arr[@]};do echo ${arr[$i]};done
a
b
d
e

数组的长度,即数组中元素的个数

格式:

1
2
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

范例:

1
2
[root@rocky86 ~]# echo ${#title[*]}
3

数组的所有下标

格式:

1
2
${!ARRAY_NAME[*]}
${!ARRAY_NAME[@]}

范例:

1
2
3
4
5
[root@rocky86 ~]# echo ${!title[@]}
0 1 2

[root@rocky86 ~]# echo ${!title[*]}
0 1 2

删除数组

删除数组中的某元素,会导致稀疏格式

格式:

1
unset ARRAY[INDEX]

范例:

1
2
3
4
5
6
[root@rocky86 ~]# echo ${title[*]}
ceo coo cto

[root@rocky86 ~]# unset title[1]
[root@rocky86 ~]# echo ${title[*]}
ceo cto

删除整个数组

格式:

1
unset ARRAY

范例:

1
2
[root@rocky86 ~]# unset title
[root@rocky86 ~]# echo ${title[*]}

数组数据处理

数组切片

格式:

1
2
3
4
5
6
7
8
9
10
11
${ARRAY[@]:offset:number}
${ARRAY[*]:offset:number}


offset #要跳过的元素个数
number #要取出的元素个数


#取偏移量之后的所有元素
{ARRAY[@]:offset}
{ARRAY[*]:offset}

范例:

1
2
3
4
5
6
7
8
9
[root@rocky86 ~]# num=({0..10})
[root@rocky86 ~]# echo ${num[*]}
0 1 2 3 4 5 6 7 8 9 10

[root@rocky86 ~]# echo ${num[*]:2:3}
2 3 4

[root@rocky86 ~]# echo ${num[*]:6}
6 7 8 9 10

向数组中追加元素

格式:

1
2
ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value

范例:

1
2
3
4
5
6
[root@rocky86 ~]# num[${#num[@]}]=11
[root@rocky86 ~]# echo ${#num[@]}
12

[root@rocky86 ~]# echo ${num[@]}
0 1 2 3 4 5 6 7 8 9 10 11

关联数组

关联数组与普通数组区别:

  • 关联数组要先声明,才能使用,普通数组可以不用声明
  • 关联数组可以自定义下标,普通数组必须用数字

格式:

1
2
declare -A ARRAY_NAME 
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)

范例:必须先声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@rocky86 ~]# test[a]=123
[root@rocky86 ~]# echo ${test[*]}
123

[root@rocky86 ~]# test[b]=456
[root@rocky86 ~]# echo ${test[*]}
456


#此处再声明就失败,因为test现在是普通数组
[root@rocky86 ~]# declare -A test
-bash: declare: test: cannot convert indexed to associative array


#删除之后重新赋值
[root@rocky86 ~]# unset test
[root@rocky86 ~]# declare -A test
[root@rocky86 ~]# test[ceo]=mage
[root@rocky86 ~]# test[coo]=zhang
[root@rocky86 ~]# echo ${test[ceo]}
mage

[root@rocky86 ~]# echo ${test[cto]}
[root@rocky86 ~]# echo ${test[coo]}
zhang

[root@rocky86 ~]# echo ${test[*]}
mage zhang


#遍历
[root@rocky86 ~]# for i in ${test[*]};do echo ${i};done
mage
zhang


#遍历下标,根据下标取元素内容
[root@rocky86 ~]# for i in ${!test[*]};do echo ${test[$i]};done
mage
zhang

范例:关联数组赋值必须指定下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#报错
[root@rocky86 ~]# declare -A studentList
[root@rocky86 ~]# studentList=(tom jerry)
-bash: studentList: tom: must use subscript when assigning associative array
-bash: studentList: jerry: must use subscript when assigning associative array


[root@rocky86 ~]# studentList=([stu1]=tom [stu2]=jerry)
[root@rocky86 ~]# echo ${studentList[*]}
jerry tom

[root@rocky86 ~]# echo ${!studentList[*]}
stu2 stu1


#根据下标遍历
[root@rocky86 ~]# for i in ${!studentList[*]};do echo $i=${studentList[$i]};done
stu2=jerry
stu1=tom

范例:显示所有关联数组下标

1
2
3
[root@rocky86 ~]# declare -A title=([ceo]=mage [coo]=zhang [cto]=wang)
[root@rocky86 ~]# echo ${!title[@]}
ceo cto coo

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@rocky86 ~]# declare -A student
[root@rocky86 ~]# student[name1]=lijun
[root@rocky86 ~]# student[name2]=ziqing
[root@rocky86 ~]# student[age1]=18
[root@rocky86 ~]# student[age2]=16

[root@rocky86 ~]# echo ${student[*]}
16 18 ziqing lijun

[root@rocky86 ~]# echo ${!student[*]}
age2 age1 name2 name1

[root@rocky86 ~]# for i in {1..5};do echo student{name$i]=${student[name$i]};done
student{name1]=lijun
student{name2]=ziqing
student{name3]=
student{name4]=
student{name5]=


[root@rocky86 ~]# for i in {1..2};do echo ${student[name$i]}: ${student[age$i]};done;
lijun: 18
ziqing: 16

范例

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@rocky86 ~]# cat rand.sh
#!/bin/bash
declare -i min max
declare -a nums
for((i=0;i<10;i++));do
nums[$i]=$RANDOM
if [ $i -eq 0 ];then
min=${nums[0]}
max=${nums[0]}
else
if [ ${nums[$i]} -gt $max ]; then
max=${nums[$i]}
fi

if [ ${nums[$i]} -lt $min ]; then
min=${nums[$i]}
fi
fi
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min

管道实现

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# cat rand2.sh
#!/bin/bash
declare -a nums
for((i=0;i<10;i++));do
nums[$i]=$RANDOM

done
min=`echo ${nums[*]} | tr ' ' '\n' | sort -n | head -n1`
max=`echo ${nums[*]} | tr ' ' '\n' | sort -nr | head -n1`

echo "All numbers are ${nums[*]}"
echo MAX $max
echo MIN $min

字符串处理

字符串切片

基于偏移量取字符串

格式:

1
2
3
4
5
6
${#var}                     #返回字符串变量var的字符的长度,一个汉字算一个字符
${var:offset} #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset:number} #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var: -length} #取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
${var:offset:-length} #从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var: -length:-offset} #先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意:-length前空格,并且length必须大于offset

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@rocky86 ~]# str=abcd1234
[root@rocky86 ~]#echo ${#str}
8

[root@rocky86 ~]# echo ${str:2}
cd1234

[root@rocky86 ~]# echo ${str:2:4}
cd12

[root@rocky86 ~]# echo ${str: -3}
234

[root@rocky86 ~]## echo ${str:2:-1}
cd123

[root@rocky86 ~]# echo ${str: -6:-2}
cd12

#一个中文算一个字符长度
[root@rocky86 ~]# str2=我爱中国
[root@rocky86 ~]# echo ${#str2}
4

基于模式取子串

格式:

1
2
3
4
5
6
7
8
9
#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}

#从var变量的值中删除以word开头的部分
${var#word}

#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模式,以最后一个word为界删左留右
${var##*word}
${var##word}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@rocky86 ~]# str=abcd1234abcd1234

#删除第一次出现的cd及其左边的内容
[root@rocky86 ~]# echo ${str#*cd}
1234abcd1234

#删除左边第一个ab
[root@rocky86 ~]# echo ${str#ab}
cd1234abcd1234

#删除最后第一次出现的cd及其左边的内容
[root@rocky86 ~]# echo ${str##*cd}
1234

[root@rocky86 ~]# echo ${str##ab}
cd1234abcd1234

范例:保留文件名

1
2
3
[root@rocky86 ~]# nginx_url=http://nginx.org/download/nginx-1.20.2.tar.gz
[root@rocky86 ~]# echo ${nginx_url##*/}
nginx-1.20.2.tar.gz
1
2
3
4
5
6
7
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以从右向左的第一个word为界删右留左
${var%word*}
${var%word}

#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符,即贪婪模式,以从右向左的最后一个word为界删右留左
${var%%word*}
${var%%word}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# str=abcd1234abcd12345

#从右边开始查找,删除第一个ab及右边的字符串
[root@rocky86 ~]# echo ${str%ab*}
abcd1234

#删除右边的12345
[root@rocky86 ~]# echo ${str%12345}
abcd1234abcd

#从右边开始查找,删除最后一个123及右边的字符串
[root@rocky86 ~]# echo ${str%%123*}
abcd

#删除右边第一12345
[root@rocky86 ~]# echo ${str%%12345}
abcd1234abcd

范例:

1
2
3
4
5
6
[root@rocky86 ~]# url=http://www.magedu.com:8080
[root@rocky86 ~]# echo ${url##*:}
8080

[root@rocky86 ~]# echo ${url%%:*}
http

查找替换

格式:

1
2
3
4
5
6
7
8
9
10
11
#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之,懒惰模式
${var/pattern/substr}

#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之,贪婪模式
${var//pattern/substr}

#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}

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

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# str=abcd1234abcd12345

#从左到右,替换第一个123为XYZ
[root@rocky86 ~]# echo ${str/123/XYZ}
abcdXYZ4abcd12345

#从左到右,替换所有123为XYZ
[root@rocky86 ~]# echo ${str//123/XYZ}
abcdXYZ4abcdXYZ45

#替换行首的abc为XYZ
[root@rocky86 ~]# echo ${str/#abc/XYZ}
XYZd1234abcd12345

#替换行尾的12345为XYZ
[root@rocky86 ~]# echo ${str/%12345/XYZ}
abcd1234abcdXYZ

查找并删除

格式:

1
2
3
4
5
6
7
8
9
10
11
#删除var表示的字符串中第一次被pattern匹配到的字符串,懒惰模式
${var/pattern}

#删除var表示的字符串中所有被pattern匹配到的字符串,贪婪模式
${var//pattern}

#删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}

#删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# str=abcd1234abcd12345

#从左到右删除第一个1234
[root@rocky86 ~]# echo ${str/1234}
abcdabcd12345

#从左到右删除所有1234
[root@rocky86 ~]# echo ${str//1234}
abcdabcd5

#删除行首abcd
[root@rocky86 ~]# echo ${str/#abcd}
1234abcd12345

#删除行尾12345
[root@rocky86 ~]# echo ${str/%12345}
abcd1234abcd

字符大小写转换

格式:

1
2
3
4
5
#把var中的所有小写字母转换为大写
${var^^}

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

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# str=abcd1234ABCD12345

#所有小写转大写
[root@rocky86 ~]# echo ${str^^}
ABCD1234ABCD12345

#所有大写转小写
[root@rocky86 ~]# echo ${str,,}
abcd1234abcd12345

#tr实现大小写转换
[root@rocky86 ~]# echo $str | tr 'a-z' 'A-Z'
ABCD1234ABCD12345

变量扩展

格式:

1
2
3
#扩展以所有prefix开头的变量
${!prefix*}
${!prefix@}

范例:模糊匹配变量名

1
2
3
4
5
6
[root@rocky86 ~]# file1=a;file2=b;file3=c
[root@rocky86 ~]# echo ${!file*}
file1 file2 file3

[root@rocky86 ~]# echo ${!file@}
file1 file2 file3

高级变量

高级变量赋值

$str 为变量名,expr 为具体字符串

这些组合可以省掉一些 if,else 的判断代码

变量配置方式 str沒有配置 str为空字符串 str已配置为非空字符串
var=${str-expr} var=expr var= var=$str
var=${str:-expr} var=expr var=expr var=$str
var=${str+expr} var= var=expr var=expr
var=${str:+expr} var= var= var=expr
var=${str=expr} str=expr; var=expr str不变; var= str不变; var=$str
var=${str:=expr} str=expr; var=expr str=expr; var=expr str不变; var=$str
var=${str?expr} expr 输出至 stderr var= var=$str
var=${str:?expr} expr 输出至 stderr expr输出至stderr var=$str

范例:

1
2
3
4
5
6
7
8
9
10
[root@rocky86 ~]# str1=abc
[root@rocky86 ~]# echo ${str1-xyz}
abc

[root@rocky86 ~]# str1=""
[root@rocky86 ~]# echo ${str1-xyz}

[root@rocky86 ~]# unset str1
[root@rocky86 ~]# echo ${str1-xyz}
xyz

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky86 ~]# title=ceo
[root@rocky86 ~]# name=${title:-mage}
[root@rocky86 ~]# echo $name
ceo

[root@rocky86 ~]# title=
[root@rocky86 ~]# name=${title:-mage}
[root@rocky86 ~]# echo $name
mage

[root@rocky86 ~]# unset title
[root@rocky86 ~]# name=${title:-mage}
[root@rocky86 ~]# echo $name
mage

高级变量用法-有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
declare [-aAfFgilnrtux] [-p] [name[=value] ...]
#选项:
-f #显示已定义的所有函数名及其内容
-F #仅显示已定义的所有函数名
-p #显示每个变量的属性和值
-a #声明或显示定义为数组的变量
-A #将变量定义为关联数组
-i #声明或显示定义为整型的变量
-l #声明或显示定义为小写的变量
-n #变量引用另外一个变量的值
-r #声明或显示只读变量
-t #声明或显示具有trace(追踪)属性的变量
-u #声明或显示定义为大写的变量
-x #显示环境变量和函数,相当于export

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#显示所有己定义的函数和函数体
[root@rocky86 ~]# declare -f

#显示所有己定义的函数,仅函数名
[root@rocky86 ~]# declare -F

#显示所有己定义的变量属性及值
[root@rocky86 ~]# declare -p

#显示所有普通数组
[root@rocky86 ~]# declare -a

#定义一个普通数组
[root@rocky86 ~]# declare -a arr1

#显示所有关联数组
[root@rocky86 ~]# declare -A

#定义一个关联数组
[root@rocky86 ~]# declare -A arr2

#显示所有整型变量
[root@rocky86 ~]# declare -i

#定义一个整型变量
[root@rocky86 ~]# declare -i int1
[root@rocky86 ~]# int1=abcd
[root@rocky86 ~]# echo $int1
0

#显示所有小写变量
[root@rocky86 ~]# declare -l

#定义一个整型变量
[root@rocky86 ~]# declare -l lower1
[root@rocky86 ~]# lower1=1234abcdABCD
[root@rocky86 ~]# echo $lower1
1234abcdabcd

#变量引用
[root@rocky86 ~]# str1=1234
[root@rocky86 ~]# declare -n str2=str1
[root@rocky86 ~]# echo $str1 $str2
1234 1234

#str2 会跟着改变
[root@rocky86 ~]# str1=abcd
[root@rocky86 ~]# echo $str1 $str2
abcd abcd

#str1 也会跟着改变
[root@rocky86 ~]#str2=xyz
[root@rocky86 ~]# echo $str1 $str2
xyz xyz

#显示所有只读变量
[root@rocky86 ~]# declare -r

#只读变量在定义时就要赋值
[root@rocky86 ~]# declare -r r1
[root@rocky86 ~]# r1=123
-bash: r1: readonly variable

[root@rocky86 ~]# declare -r r2=123
[root@rocky86 ~]# echo $r2
123

变量间接引用

eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@rocky86 ~]# CMD=whoami
[root@rocky86 ~]# echo $CMD
whoami

[root@rocky86 ~]# eval $CMD
root

[root@rocky86 ~]# n1=6
[root@rocky86 ~]# echo {1..$n1}
{1..6}

#两次展开
[root@rocky86 ~]# eval echo {1..$n1}
1 2 3 4 5 6

[root@rocky86 ~]# for i in `eval echo {1..$n1}`;do echo i=$i;done
i=1
i=2
i=3
i=4
i=5
i=6

[root@rocky86 ~]# a=aa;b=bb
[root@rocky86 ~]# $a$b=cc
bash: aabb=cc: command not found...

[root@rocky86 ~]# eval $a$b=cc
[root@rocky86 ~]# echo $a$b
aabb

[root@rocky86 ~]# echo $aabb
cc

间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为

格式:

1
2
variable1=variable2
variable2=value

范例:

1
2
3
4
5
6
7
8
[root@rocky86 ~]# var1=str
[root@rocky86 ~]# str=abcd
[root@rocky86 ~]# echo $var1
str
[root@rocky86 ~]# echo \$$var1
$str
[root@rocky86 ~]# eval echo \$$var1
abcd

bash Shell提供了两种格式实现间接变量引用

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#方法1
#变量赋值
eval tempvar=\$$variable1

#显示值
eval echo \$$variable1
eval echo '$'$variable1
echo $tmpvar


#方法2
#变量赋值
tempvar=${!variable1}

#显示值
echo ${!variable1}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky86 ~]# ceo=name
[root@rocky86 ~]# name=mage
[root@rocky86 ~]# eval echo \$$ceo
mage

[root@rocky86 ~]# eval tmp=\$$ceo
[root@rocky86 ~]# echo $tmp
mage

[root@rocky86 ~]# echo ${!ceo}
mage

[root@rocky86 ~]# tmp2=${!ceo}
[root@rocky86 ~]# echo $tmp2
mage

面试题:下面脚本执行结果输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# cat var123.sh
#!/bin/bash

var(){
local var="$1"
echo "${!var}"
}
var 1 a
var 2 a b
var 3 a b c


[root@rocky86 ~]# bash var123.sh