Shell 脚本基础

1 编程基础

Linus:Talk is cheap, show me the code

1.1 程序组成

Shell 脚本基础_第1张图片

1.2 程序编程风格

面向过程语言

  • 做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现情况B,做什么处理
  • 问题规模小,可以步骤化,按部就班的处理
  • 以指令为中心,数据服务于指令
  • C,Shell

面向对象语言

  • 一种认识世界,分析世界的方法论,将万事万物抽象为各种对象
  • 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
  • 对象是类的具象,是一个实体
  • 问题规模大,复杂系统
  • 以数据为中心,指令服务于数据
  • Java,C#,Python,Golang 等

1.3 编程语言

计算机:运行二进制指令
编程语言:人与计算机之间交互的语言。分为两种:低级语言和高级语言
Shell 脚本基础_第2张图片

1.4 编程逻辑处理方式

循环结构流程:

2 shell脚本语言的基本用法

Shell 是什么?
Shell 是一个应用程序,它连接了用户和Linux 内核,让用户能够更加高效,安全,低成本使用Linux内核。这就是Shell的本质。
Shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序。
Shell 脚本基础_第3张图片
Shell 作为一种命令语言和程序设计语言,优势在于它能够实现自动化运维,提高运维效率,帮助运维人员摆脱繁杂的操作,让运维工作变得得心应手。Shell脚本结合了延展性和高效的特点。保持独有的编程特色,并不断的优化。

2.1 shell脚本的用途

2.2 shell脚本基本结构

Shell 脚本编程:是基于过程式,解释执行的语言
编程语言的基本结构:
Shell 脚本:包含一些命令或者声明,并且符合一定格式的文本文件
格式要求:首行 shebang 机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl

2.3 shell脚本脚本创建过程

第一步:使用文本编辑器来创建文本文件
第一行必须包括 Shell 声明序列:#!
示例:

#!/bin/bash

添加注释,注释以 # 开头
第二步:添加执行权限
给予执行权限,在命令行上指定脚本的绝对路径或者相对路径
第三步:运行脚本
直接运行解释器,将脚本作为解释器程序的参数运行

2.4 shell脚本注释规范

脚本代码开头约定:

2.5 第一个脚本

#!SHEBANG
CONFIGURATION_VARIABLES
FUNCTION_DEFINITIONS
MAIN_CODE
范例:Shell 脚本范例

$ vim hello-world.sh
#!/bin/bash
# 第一个脚本
echo "hello,world"

## 运行脚本
# 方法1:
$ bash hello-world.sh
hello,world
# 方法2:可以使用相对路径或者是绝对路径
$ chmod +x hello-world.sh
$ ./hello-world.sh

# 互联网下载并执行脚本
# 执行远程主机的脚本
$ curl http://10.0.0.6/hello.sh | bash
$ curl -s http://10.0.0.6/hello.sh | bash

范例:添加环境变量

$ mkdir -pv /data/shell
$ echo "PATH=/data/shell:$PATH" >> /etc/profile.d/personal-env.sh
$ source /etc/profile.d/personal-env.sh
$ echo $PATH
/data/shell:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
# 在该/data/shell下添加脚本并赋予执行权限即可在任何位置运行

范例:~/.vimrc 格式

set number
set cursorline
set autoindent
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:		wangj")
	call setline(5,"#QQ:			  1281570031")
	call setline(6,"#Date:			".strftime("%Y-%m-%d"))
	call setline(7,"#FileName:		".expand("%"))
	call setline(8,"#URL:			http://...com")
	call setline(9,"#Description:	 The test script")
	call setline(10,"#Copyright(C):	 ".strftime("%Y")."All rights reserved")
	call setline(11,"#**************************************************#")
	call setline(12,"")
	endif
endfunc
autocmd BufNewFile * normal G

范例:备份脚本

vim backup-etc.sh
#!/bin/bash
#用于备份/etc目录
echo -e "^[[1;40;46mStarting backing etc...^[[0m"                                                                              
sleep 2
mkdir -p /data
cp -av /etc /data/etc-$(date +%F_%T) &> /dev/null
echo -e "^[[1;40;46mBackup is finished,see /data...^[[0m"

2.6 shell脚本调试

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

# bash -n /path/to/some_script

调试执行,会执行命令,用于检查逻辑错误(调试并执行)

# bash -x /path/to/some_script

范例:

bash -n hello-world.sh
hello-world.sh: line 10: warning: here-document at line 5 delimited by end-of-file (wanted `EOF')
cat -A hello-world.sh
#!/bin/bash$
echo "^[[1;40;46mhello,world^[[0m"$
hostname$
$
cat > /data/app.conf <<-'EOF'$
line1$
line2$
EOF $
$
echo "finished"$
# 或者使用vim 中的:set list选项
vim hello-world.sh
#!/bin/bash$
echo "^[[1;40;46mhello,world^[[0m"$
hostname$
$
cat > /data/app.conf <<-'EOF'$
line1$
line2$
EOF $
$
echo "finished"$

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

2.7 变量

2.7.1 变量

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

2.7.2 变量类型

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

2.7.3 编程语言分类

Shell 脚本基础_第4张图片
静态和动态语言:
强类型和弱类型语言:

2.7.4 shell中变量命名法则

2.7.4.1 命名要求
2.7.4.2 命名习惯

2.7.5 变量定义和引用

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

name='value'

value 可以是以下多种形式:

直接字串:name="root"
变量引用:name="$USER"
命令引用:name="COMMAND" 或者 name=$(COMMAND)

变量引用:

${name}
$name

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

⚡TITLE="CTO"
⚡echo $TITLE
CTO
⚡echo "I am $TITLE"
I am CTO
⚡echo 'I am $TITLE'
I am $TITLEUSER=`whoami`
⚡NAME=$USER
⚡echo $NAME
root
⚡echo $USER
root

⚡FILE=`ls /run` (FILE=`ls /etc/*`)
⚡echo $FILE

$ NUMS=`seq 10`
$ echo $NUMS
$ echo "$NUMS"

$ NAME="wang 
> zhang
> zhao
> li"
$ echo $NAME
wang zhang zhao li
$ echo "$NAME"
wang
zhang
zhao
li

范例:变量引用

$ NAME=wangj
$ AGE=18
$ echo $NAME
wangj
$ echo $AGE
18

$ echo $NAME $AGE
wangj 18
$ echo $NAME$AGE
wangj18
$ echo $NAME_$AGE
18
$ echo ${NAME}_$AGE
wangj_18

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

⚡TITLE=CTO
⚡NAME=wang
⚡TITLE=$NAME
⚡echo $NAME
wang

⚡NAME=wangj
⚡echo $NAME
wangj
⚡echo $TITLE
wang

范例:变量追加值

$ TITLE=CTO
$ TITLE+=:wang
$ echo $TITLE
CTO:wang`

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

$ CMD=hostname
$ CMD
CentOS8-Server

$ CMD=pwd
$ $CMD
/root

显示已定义的所有变量:

set

删除变量:

unset name(不需要添加$)

范例:

$ NAME=wangj
$ TITLE=CEO
$ echo ${NAME} ${TITLE}
wangj CEO

$ unset NAME TITLE
$	echo ${NAME} ${TITLE}

范例:显示系统信息

vim system_info.sh
#!/bin/bash
#查看系统的基本信息
BASECOLOR="^[[1;34m"
GREENCOLOR="^[[1;32m"
COLOREND="^[[0m"

echo -e "$GREENCOLOR------------------Host Systeminfo------------------$COLOREND"
echo -e "HOSTNAME:  $BASECOLOR`hostname`$COLOREND"
echo -e "IPADDR:    $BASECOLOR`ifconfig eth0|grep -o "\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}"|head -n1`$COLOREND"
#echo -e "IPADDR:    $BASECOLOR`hostname -I`$COLOREND"
echo -e "OSVERSION: $BASECOLOR`cat /etc/redhat-release`$COLOREND"
echo -e "KERNEL:    $BASECOLOR`uname -r`$COLOREND"
echo -e "CPU:      $BASECOLOR`lscpu|grep "^Model name"|tr -s " " | cut -d ":" -f 2`$COLOREND"
echo -e "MEMORY:    $BASECOLOR`free -h|grep "Mem"|tr -s " "|cut -d" " -f2`$COLOREND"
#echo -e "DISK:      $BASECOLOR`lsblk |grep "^vd"|tr -s " "|cut -d" " -f 4|head -n1`$COLOREND"
echo -e "DISK:      $BASECOLOR`lsblk | tail -n$(echo $(lsblk|wc -l)-1|bc)|grep "disk"|head -n1| tr -s " "|cut -d" " -f4`$COLOREND"    
echo -e "$GREENCOLOR---------------------------------------------------$COLOREND"

Shell 脚本基础_第5张图片
范例:备份 /etc 目录

$ vim backup-variable-etc.sh
#!/bin/bash
#备份目录/文件
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"
SRC="/etc"
DEST="/data"
DATE=$(date +%F_%T)

echo -e "${BASECOLOR}Starting backuping ${SRC}...${ENDCOLOR}"
sleep 2
cp -av ${SRC} ${DEST}${SRC}-${DATE} &> /dev/null
echo -e "${BASECOLOR}Backup is finished${ENDCOLOR}"
2.7.5.1 练习
# 1、编写脚本 systeminfo.sh,显示当前主机系统信息,包括:主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小
# 2、编写脚本 backup.sh,可实现每日将/etc/目录备份到/backup/etcYYYY-mm-dd中
# 3、编写脚本 disk.sh,显示当前硬盘分区中空间利用率最大的值
# 4、编写脚本 links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序
# 1、编写脚本 systeminfo.sh,显示当前主机系统信息,包括:主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小
~ vim system_info.sh
#!/bin/bash
#查看系统的基本信息
BASECOLOR="^[[1;34m"
GREENCOLOR="^[[1;32m"
COLOREND="^[[0m"

echo -e "$GREENCOLOR------------------Host Systeminfo------------------$COLOREND"
echo -e "HOSTNAME:  $BASECOLOR`hostname`$COLOREND"
echo -e "IPADDR:    $BASECOLOR`ifconfig eth0|grep -o "\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}"|head -n1`$COLOREND"
#echo -e "IPADDR:    $BASECOLOR`hostname -I`$COLOREND"
echo -e "OSVERSION: $BASECOLOR`cat /etc/redhat-release`$COLOREND"
echo -e "KERNEL:    $BASECOLOR`uname -r`$COLOREND"
echo -e "CPU:      $BASECOLOR`lscpu|grep "^Model name"|tr -s " " | cut -d ":" -f 2`$COLOREND"
echo -e "MEMORY:    $BASECOLOR`free -h|grep "Mem"|tr -s " "|cut -d" " -f2`$COLOREND"
#echo -e "DISK:      $BASECOLOR`lsblk |grep "^vd"|tr -s " "|cut -d" " -f 4|head -n1`$COLOREND"
echo -e "DISK:      $BASECOLOR`lsblk | tail -n$(echo $(lsblk|wc -l)-1|bc)|grep "disk"|head -n1| tr -s " "|cut -d" " -f4`$COLOREND"    
echo -e "$GREENCOLOR---------------------------------------------------$COLOREND"

# 2、编写脚本 backup.sh,可实现每日将/etc/目录备份到/backup/etcYYYY-mm-dd中
~ vim backup.sh
#!/bin/bash
#备份指定目录
# Shell Env
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"
DIR=${1}
DEST=/data

[ $# -eq 0 ] && { echo -e "^[[1;31mError Args\nUsage: $0 ^[[0m"; exit ; }

mkdir -p /data
echo -e "${BASECOLOR}Starting backup ${DIR}...${ENDCOLOR}"
sleep 1

cp -av ${DIR} ${DEST}${DIR}`date +%F` &> /dev/null

echo -e "${BASECOLOR}Finish backup ${DIR}...\nSee /data...${ENDCOLOR}"

# 3、编写脚本 disk.sh,显示当前硬盘分区中空间利用率最大的值
~ vim disk.sh
#!/bin/bash
# 显示当前硬盘分区中空间利用率最大的值
# Shell ENV
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

DISKUSE=`df | tail -n $(echo "$(df|wc -l)-1"|bc)|grep -Eo "[0-9]+%"|sort -nr|head -n1`
echo -e "${BASECOLOR}The system disk max_use is ${DISKUSE}${ENDCOLOR}"

# 4、编写脚本 links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序
~ vim links.sh
#!/bin/bash
#显示正连接本主机的每个远程主机的IPv4地址和连接数
# Shell ENV
BASECOLOR="\E[1;46m"
ENDCOLOR="\E[0m"

SS=`netstat -antl | grep "ESTABLISHED"|tr -s " " :|cut -d":" -f 6|sort -nr|uniq -c|sort -nr`

echo -e "${BASECOLOR}The connection Server IP and links:\n${SS}${ENDCOLOR}"

2.7.6 环境变量

每个进程中有自己独有的环境变量
环境变量:
环境变量声明和赋值

# 声明并赋值
export name=VALUE
declare -x name=VALUE

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

变量引用

${name}
$name

显示所有环境变量

# env
# printenv
# export
# declare -x
# set

查看指定进程的环境变量

$ cat /proc/$PID/environ
$ cat /proc/$BASHPID/environ

删除环境变量

unset name

Bash内建的环境变量

# PATH:表示命令存放的路径
# SHELL:表示当前终端使用的是哪种SHELL类型
# UID::表示当前登录用户的UID编号
# USER:表示当前登录用户的用户名
# HOME:表示当前登录用户的家目录
# SHLVL:表示当前终端的嵌套深度
# LANG:表示当前系统使用的编码
# MAIL:表示当前登录用户的邮箱存放路径
# HOSTNAME:表示当前系统使用的主机名
# HISTSIZE:表示当前系统中记录history的数量
# _ 下划线:表示前一个命令的最后一个参数

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

$ cat /proc/$BASHPID/environ
$ cat /proc/$BASHPID/environ | tr -s "\0" "\n"

2.7.7 只读变量

只读变量:只能声明定义,但是后续不能修改和删除,即常量
只读变量,只有进程退出以后,才会删除。
声明只读变量:

readonly name
declare -r name

查看只读变量:

readonly [-p]:-p的选项添加与不添加显示效果一致
declare -r

范例:只读变量

$ readonly PI=3.1415926
$ echo $PI
3.1415926
$ PI=3.14
bash: PI: readonly variable
$ exit
$ echo $PI

2.7.8 位置变量

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

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

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

清空所有位置变量

set --

范例:位置变量

$ vim args.sh      
#!/bin/bash
echo -e "1st args is ${1}"
echo -e "2st args is ${2}"
echo -e "3st args is ${3}"
echo -e "10st args is ${10}"

echo "All args is $*"
echo "All args is $@"

echo "The args number is $#"

echo "The scriptname is $0"

$ bash args.sh {1..10} 
1st args is 1
2st args is 2
3st args is 3
10st args is 10
All args is 1 2 3 4 5 6 7 8 9 10
All args is 1 2 3 4 5 6 7 8 9 10
The args number is 10
The scriptname is args.sh

范例:$* 和 $@ 的区别

$ vim f1.sh
#!/bin/bash
echo -e "f1.sh:all args are $@"
echo -e "f1.sh:all args are $*"

./file.sh "$*"
###
$ vim f2.sh
#!/bin/bash
echo -e "f2.sh:all args are $@"
echo -e "f2.sh:all args are $*"

./file.sh "$@"
###
$ vim file.sh
#!/bin/bash
echo "file.sh:1st args is $1"
# 执行结果
$ ./f1.sh a b c
f1.sh:all args are a b c
f1.sh:all args are a b c
file.sh:1st args is a b c

$ ./f2.sh a b c
f2.sh:all args are a b c
f2.sh:all args are a b c
file.sh:1st args is a

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

$ vim security-rm.sh
#!/bin/bash
#安全移动文件
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"
DIR="/tmp/`date +%F_%T`"
mkdir -p ${DIR}
mv $* ${DIR}
echo -e "${BASECOLOR}Move $* to ${DIR}${ENDCOLOR}"

$ chmod +x security-rm.sh
$ alias rm="/data/shell/security-rm.sh"
$ touch {1..10}.txt
$ rm {1..10}.txt
Move 1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt 10.txt to /tmp/2022-05-14_20:57:32
$ ls /tmp/2022-05-14_20:57:32
10.txt  1.txt  2.txt  3.txt  4.txt  5.txt  6.txt  7.txt  8.txt  9.txt

范例:设置SCP远程上传文件到服务器

$ upload-scp.sh
#!/bin/bash
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"
USER="root"
IPADDR="139.198.105.99"
SRC=$*
DEST="${USER}@${IPADDR}"
DESTDIR="/tmp"

echo -e "${BASECOLOR}Starting scp upload file...${ENDCOLOR}"
sleep 1
scp ${SRC} ${DEST}:${DESTDIR}
echo -e "${BASECOLOR}End upload file finished...${ENDCOLOR}"

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

$ softlink.sh 
#!/bin/bash
# ******************************* #
echo "The scriptname is $0"

$ ln -s softlink.sh a.sh
$ ln -s softlink.sh b.sh
$ chmod +x *.sh
# 执行结果
$ ./a.sh         
The scriptname is ./a.sh
$ ./b.sh         
The scriptname is ./b.sh

2.7.9 退出状态码变量

当我们浏览网页时,有时会看到下图所显示的数字,表示网页的错误信息,我们称为状态码,在Shell脚本中也有相似的技术表示程序执行的相应状态
Shell 脚本基础_第6张图片
进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功或者失败, ? 保存状态码的相关数字,不同的值反应成功或者失败, ?保存状态码的相关数字,不同的值反应成功或者失败,?取值范例0-255

$? 的值为0					 #代表成功
$? 的值为是1 到 255 #代表失败

范例:

# -c1:发送一次ping,-W1:等待时间为1秒
ping -c1 -W1 hostdown &> /dev/null
echo $?
$ ping -c1 -W1 139.198.105.99 &> /dev/null
$ echo $?
0

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

exit [n]

注意:
范例:设置退出状态码变量

$ vim hello-world.sh 
#!/bin/bash
echo "hello,world"
exit 10
$ chmod +x hello-world.sh
$ ./hello-world.sh
hello,world
$ echo $?
10

2.7.10 展开命令行

展开命令执行顺序

把命令行分成单个命令词
展开别名
展开大括号的声明({})
展开波浪符声明(~)
命令替换$() 和 ``
再次把命令行分成命令词
展开文件通配(*、?、|abc|等等)
准备I/O重导向(<、>)
运行命令

防止扩展

反斜线(\)会使随后的字符按原意翻译

范例:

$ echo "You cost :\$5.00"
You cost :$5.00

加引号来防止扩展

单引号('')防止所有扩展
双引号("")也可以防止扩展,但是以下清空例外:$(美元符号)

范例:加引号来防止扩展

$ echo "echo $USER"
echo root
$ echo 'echo $USER'
echo $USER
$ echo '$USER'
$USER
$ echo `echo $USER`
root
$ echo $(echo $USER)
root
# 打印斜线
$ echo '\'
\
$ echo "\\"
\

变量扩展

` `:反引号,命令替换
\:反斜线,禁止单个字符扩展
!:叹号,历史命令替换;(!!将上一个命令重新执行)
[13:45:40 root@Ubuntu2004Server /data/script]#echo '\'
\
[13:45:43 root@Ubuntu2004Server /data/script]#!!
echo '\'
\
' ':最傻,不能识别命令和变量
" ":居中,可以识别变量,不能识别命令
` `:最聪明,可以识别命令和变量

2.7.11 脚本安全和 set

set 命令:可以用来定制 Shell 环境
$- 变量

$ echo $-
himBHs

$ set +h
$ echo $-
imBHs
$ hash
-bash: hash: hashing disabled

$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
$ echo $-
imBHs
$ set +B
$ echo $-
imHs
$ echo {1..10}
{1..10}

set 命令实现脚本安全

$ 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

范例:

$ DIR=/data
$ cd $DIR
$ rm -rf *
# rm -rf $DIr/*

2.8 格式化输出 printf

格式:

printf "指定的格式" "文本1" "文本2" ……

Shell 脚本基础_第7张图片
常用格式替换符

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

说明:
%#s 中的数字代表此替换符中的输出字符宽度,不足补空格。默认是右对齐,%-10s表示10个字符宽,- 表示左对齐
%03d 表示3位宽度,不足前面用 0 补全,超出位数原样输出
%.2f 中的2表示小数点后显示的小数位数
常用转义字符

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

范例:

$ printf "%s" 1 2 3 4
1234
$ printf "%s\n" 1 2 3 4
1
2
3
4
$ printf "%f\n" 1 2 3 4
1.000000
2.000000
3.000000
4.000000

# .2f 表示保留两位小数
$ printf "%.2f\n" 1 2 3 4
1.00
2.00
3.00
4.00

$ printf "(%s)" 1 2 3 4;echo
(1)(2)(3)(4)
$ printf " (%s) " 1 2 3 4;echo ""
 (1)  (2)  (3)  (4)
$ printf "(%s)\n" 1 2 3 4
(1)
(2)
(3)
(4)

$ printf "%s %s\n" 1 2 3 4
1 2
3 4
$ printf "%s %s %s\n" 1 2 3 4
1 2 3
4

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

# 将十进制的17转换为十六进制
$ printf "%x" 17
11

# 将十六进制C转换为十进制
$ printf "%d" 0xC
12

$ VAR="Welcome to Kubesphere";printf "^[[1;32m%s\n^[[0m" $VAR
Welcome
to
Kubesphere
$ VAR="Welcome to Kubesphere";printf "^[[1;32m%s^[[0m" "${VAR}"
Welcome to Kubesphere
$ VAR="Welcome to Kubesphere";printf "^[[1;32m%s ^[[0m" ${VAR}
Welcome to Kubesphere

2.9 算术运算

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

* / % 			multiplication,division,remainder,%表示取模,即取余数,示例:9%4=1,5%3=2
+ -					addition,subtraction
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
||					logica1 OR
expr?expr:expr 		conditional operator
expr1 , expr2			comma

乘法符号有些场景中需要转义
实现算术运算:

*(1)let var=算术表达式
# [],(())可以识别变量,因为在[],(())中默认字符串会添加"
*(2) ((var=算术表达式)) # 和上面等价
*(3)var=$[算术表达式]
(4)var=$((算术表达式))
(5)var=$(expr arg1 arg2 arg3 ...)
(6)declare -i var=数值
*(7)echo "算术表达式" | bc

内建的随机数生成器变量:

$RANDOM		取值范围:0-32767

范例:

# 生成 0-49之间随机数
$ echo $[$RANDOM%50] ( echo $[RANDOM%50] )
26

# 随机字体颜色
$ echo -e "^[[1;$[RANDOM%7+31]mhello^[[0m"
hello

Shell 脚本基础_第8张图片
范例:

$ a=10 ; b=20
$ c=$a+$b
$ echo $c
10+20

# let var=算术表达式
$ let c=$a+$b
$ echo $c
30
# var=$((算术表达式))
$ c=$(( a*b ))
$ echo $c
200
# var=$[算术表达式]
$ c=$[ b/a ]
$ echo $c
2
# var=$(expr arg1 arg2 arg3 ...)
$ type expr
expr is /usr/bin/expr
$ expr --help
$ a=10 ; b=10
$ expr $a * $b
expr: syntax error: unexpected argument ‘args.sh’
$ expr $a \* $b
100
# declare -i var=数值
$ a=10 ; b=10
$ declare -i x
$ x=a+b
$ echo $x
20

# 不支持浮点数
$ c=$[a/b]
$ echo $c
0
$ expr $a \* $b
200

$ declare -i x
$ x=a+b
$ echo $x
30
$ echo $a+$b | bc
30

增强型赋值:

+=i+=10 相当于 i=i+10
-=: i-=j 相当于 i=i-j
*=: i*=j 相当于 i=i*j
/=: i/=j 相当于 i=i/j 
%=: i%=j 相当于 i=i%j
++ i++,++i 相当于 i=i+1
-- i--,--i 相当于 i=i-1

格式:

let varOPEvalue

范例:

$ i=100;j=$[++i];echo i=$i, j=$j
i=101, j=101

$ i=100;j=$[i++];echo i=$i, j=$j
i=101, j=100

### 自加3后自赋值
let count+=3

$ i=10 ; i=$[ i+=20 ] ; echo $i	# 相当于let i=i+20
30
$ j=20 ; i=$[ i*=j ] ; echo $i	# 相当于let i=i*y
600

### 自增,自减
let var+=1
let var++
let var-=1
let var--

$ unset i j; i=10; j=$[i++]; echo i=$i,j=$j
i=11,j=10
$ unset i j; i=10; j=$[++i]; echo i=$i,j=$j
i=11,j=11

### 乘法符号有些场景中需要转义
$ expr 2 * 3
expr: syntax error: unexpected argument ‘anaconda-ks.cfg’
$ expr 2 \* 3
6

$ echo "scale=3;20/3"|bc
6.666

范例:今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?
鸡兔同笼,是中国古代著名典型趣题之一,大约在1500年前,《孙子算经》中就记载了这个有趣的问题。

vim chook_rabbit.sh
#!/bin/bash
#今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?
#定义变量
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

read -p "${BASECOLOR}请输入雉兔同笼中头的数量:${ENDCOLOR}" HEAD
read -p "${BASECOLOR}请输入雉兔同笼中脚的数量:${ENDCOLOR}" FOOT                                                                      

#HEAD=${1}
#FOOT=${2}
#计算过程
RABBIT=$[$[FOOT-2*HEAD]/2]
CHOOK=$[HEAD-RABBIT]

#echo -e "${BASECOLOR}请输入雉兔同笼中头的数量和脚的数量:${ENDCOLOR}"
echo -e "${BASECOLOR}雉兔同笼中鸡的数量是:${CHOOK}${ENDCOLOR}"
echo -e "${BASECOLOR}雉兔同笼中兔的数量是:${RABBIT}${ENDCOLOR}"

$ bash chook_rabbit.sh
请输入雉兔同笼中头的数量:35
请输入雉兔同笼中脚的数量:94
雉兔同笼中鸡的数量是:23
雉兔同笼中兔的数量是:12

Shell 脚本基础_第9张图片

2.10 逻辑运算

$? = 0 —> 真
$? != 0 —> 假
Shell 脚本基础_第10张图片
true,false(1,0)

1:真
○ 0:假

# 注意,以上为二进制
11 = 110 = 001 = 000 = 0
11 = 110 = 101 = 100 = 0
! 1 = 0		! true! 0 = 1		! false
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0

范例:

$ true
$ echo $?
0

$ false
$ echo $?
1

$ !true
true
$ echo $?
0

$ !false
false
$ echo $?
1
# 二进制与&,或|
# x=$[6&2]
# echo $x
2
# x=$[7&3]
# echo $x
3
# x=$[7|3]
# echo $x
7
# x=$[5|2]
# echo $x
7

范例:变量互换

# 方式1:
$ x=10;y=20;temp=$x;x=$y;y=$temp;echo x=$x,y=$y
x=20,y=10
# 方式2:
$ x=10;y=20;x=$[x^y];y=$[x^y];x=$[x^y];echo x=$x,y=$y
x=20,y=10

短路运算
Shell 脚本基础_第11张图片

2.11 条件测试命令

条件测试:
判断某个需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程
评估布尔声明,以便用在条件性执行中
条件测试命令:
注意:EXPRESSION 前后必须有空白字符
范例:查看帮助信息

$ help test
test: test [expr]
    Evaluate conditional expression.

    Exits with a status of 0 (true) or 1 (false) depending on
    the evaluation of EXPR.  Expressions may be unary or binary.  Unary
    expressions are often used to examine the status of a file.  There
    are string operators and numeric comparison operators as well.

    The behavior of test depends on the number of arguments.  Read the
    bash manual page for the complete specification.

    File operators:

      -a FILE        True if file exists.
      -b FILE        True if file is block special.
      -c FILE        True if file is character special.
      -d FILE        True if file is a directory.
      -e FILE        True if file exists.
      -f FILE        True if file exists and is a regular file.
      -g FILE        True if file is set-group-id.
      -h FILE        True if file is a symbolic link.
      -L FILE        True if file is a symbolic link.
      -k FILE        True if file has its `sticky' bit set.
      -p FILE        True if file is a named pipe.
      -r FILE        True if file is readable by you.
      -s FILE        True if file exists and is not empty.
      -S FILE        True if file is a socket.
      -t FD          True if FD is opened on a terminal.
      -u FILE        True if the file is set-user-id.
      -w FILE        True if the file is writable by you.
      -x FILE        True if the file is executable by you.
      -O FILE        True if the file is effectively owned by you.
      -G FILE        True if the file is effectively owned by your group.
      -N FILE        True if the file has been modified since it was last read.

      FILE1 -nt FILE2  True if file1 is newer than file2 (according to
                       modification date).

      FILE1 -ot FILE2  True if file1 is older than file2.

      FILE1 -ef FILE2  True if file1 is a hard link to file2.

    String operators:

      -z STRING      True if string is empty.

      -n STRING
         STRING      True if string is not empty.

      STRING1 = STRING2
                     True if the strings are equal.
      STRING1 != STRING2
                     True if the strings are not equal.
      STRING1 < STRING2
                     True if STRING1 sorts before STRING2 lexicographically.
      STRING1 > STRING2
                     True if STRING1 sorts after STRING2 lexicographically.

    Other operators:

      -o OPTION      True if the shell option OPTION is enabled.
      -v VAR     True if the shell variable VAR is set
      ! EXPR         True if expr is false.
      EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
      EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.

      arg1 OP arg2   Arithmetic tests.  OP is one of -eq, -ne,
                     -lt, -le, -gt, or -ge.

    Arithmetic binary operators return true if ARG1 is equal, not-equal,
    less-than, less-than-or-equal, greater-than, or greater-than-or-equal
    than ARG2.

    Exit Status:
    Returns success if EXPR evaluates to true; fails if EXPR evaluates to
    false or an invalid argument is given.
    
$ type [
[ is a shell builtin

$ help [
[: [ arg... ]
    Evaluate conditional expression.

    This is a synonym for the "test" builtin, but the last argument must
    be a literal `]', to match the opening `['.
[[ ... ]]: [[ expression ]]
    Execute conditional command.

    Returns a status of 0 or 1 depending on the evaluation of the conditional
    expression EXPRESSION.  Expressions are composed of the same primaries used
    by the `test' builtin, and may be combined using the following operators:

      ( EXPRESSION )    Returns the value of EXPRESSION
      ! EXPRESSION              True if EXPRESSION is false; else false
      EXPR1 && EXPR2    True if both EXPR1 and EXPR2 are true; else false
      EXPR1 || EXPR2    True if either EXPR1 or EXPR2 is true; else false

    When the `==' and `!=' operators are used, the string to the right of
    the operator is used as a pattern and pattern matching is performed.
    When the `=~' operator is used, the string to the right of the operator
    is matched as a regular expression.

    The && and || operators do not evaluate EXPR2 if EXPR1 is sufficient to
    determine the expression's value.

    Exit Status:
    0 or 1 depending on value of EXPRESSION.

$ help [[
[[ ... ]]: [[ expression ]]
    Execute conditional command.

    Returns a status of 0 or 1 depending on the evaluation of the conditional
    expression EXPRESSION.  Expressions are composed of the same primaries used
    by the `test' builtin, and may be combined using the following operators:

      ( EXPRESSION )    Returns the value of EXPRESSION
      ! EXPRESSION              True if EXPRESSION is false; else false
      EXPR1 && EXPR2    True if both EXPR1 and EXPR2 are true; else false
      EXPR1 || EXPR2    True if either EXPR1 or EXPR2 is true; else false

    When the `==' and `!=' operators are used, the string to the right of
    the operator is used as a pattern and pattern matching is performed.
    When the `=~' operator is used, the string to the right of the operator
    is matched as a regular expression.

    The && and || operators do not evaluate EXPR2 if EXPR1 is sufficient to
    determine the expression's value.

    Exit Status:
    0 or 1 depending on value of EXPRESSION.

2.11.1 变量测试

-v VAR 变量 VAR 是否设置;专门用来测试变量是否定义,所以不需要添加 $
示例:判断 NAME 变量是否定义

[ -v NAME ] (NAME 不需要添加 $)

# 判断 NAME 变量是否定义
[ -v NAME ]

# 判断 NAME 变量是否定义并且是名称引用,Bash 4.4 新特性
[ -R NAME ]

范例:变量测试

$ unset x ; test -v x ; echo $?
1
$ x=10 ; test -v x ; echo $?
0

### 注意: [  ] 需要空格,否则汇报下面的错误
$ x=10 ; [-v x] ; echo $?
-bash: [-v: command not found
127
$ x=10 ; [ -v x ] ; echo $?
0

2.11.2 数值测试

-eq : 是否等于(equal)
-ne : 是否不等于(not equal)
-lt : 是否小于(Less than)
-le : 是否小于等于
-gt : 是否大于(Greater than)
-ge : 是否大于等于
$ [ 10 -gt 8 ] ; echo $?
0
$ [ 10 -lt 8 ] ; echo $?
1
$ i=10 ; j=20 ; [ $i -lt $j ] ; echo $?
0
$ i=10 ; j=20 ; [ $i -gt $j ] ; echo $?
1
# 报错:期望是一个整数型
$ [ i -gt j ]
-bash: [: i: integer expression expected

2.11.3 字符串测试

***-z "STRING":字符串是否为空,空为真,不空为假
***-n "STRING":字符串是否不空,不空为真,空位假; 默认参数

= :是否等于(判断字符串是否等于)
!= :是否不等于(判断字符串是否不等于)
> :acsii 码 是否大于 acsii 码
< :是否小于

***== :左侧字符串是否和右侧的 PATTERN 相同
	注意:此表达式用于 [[  ]]中,PATTERN 为通配符
***=~ :左侧字符串是否能够被右侧的 PATTERN 所匹配
	注意:此表达式用于 [[  ]]中:扩展的正则表达式
### 单中括号-无法使用通配符和正则表达式
# -z "STRING":字符串是否为空,空为真,不空为假;不定义和空串都是为空
$ unset str ; [ -z "$str" ] ; echo $?
0
$ str="" ; [ -z "$str" ] ; echo $?
0
$ str=" " ; [ -z "$str" ] ; echo $?
1
$ str=wang ; [ -z "$str" ] ; echo $?
1

# -n "STRING":字符串是否不空,不空为真,空为假;默认为 -n 选项
$ unset str ; [ -n "$str" ] ; echo $?
1
$ str="" ; [ -n "$str" ] ; echo $?
1
$ str=" " ; [ "$str" ] ; echo $?
0
$ str=wang ; [ "$str" ] ; echo $?
0

# = :是否等于
# = 等号两端需要添加空格
$ str1=wang ; str2=zong ; [ "$str1" = "$str2" ] ; echo $?
1
$ str1=wang ; str2=wang ; [ "$str1" = "$str2" ] ; echo $?
0

### 双中括号-可以使用通配符和正则表达式
# == :左侧字符串是否和右侧的 PATTERN 相同;注意:此表达式用于 [[  ]]中,PATTERN 为文件通配符
# = 等号两端需要添加空格;注意:此表达式用于 [[  ]]中:使用的是通配符
$ FILE=test.log ; [[ "$FILE" == *.log ]] ; echo $?
0
$ FILE=test.log ; [[ "$FILE" == *.txt ]] ; echo $?
1
$ FILE=test.log ; [[ "$FILE" != *.txt ]] ; echo $?
0

# =~ :左侧字符串是否能够被右侧的 PATTERN 所匹配;注意:此表达式用于 [[  ]]中:扩展的正则表达式
$ FILE=test.log ; [[ "$FILE" =~ \.log$ ]] ; echo $?
0
$ FILE=test.log ; [[ "$FILE" =~ .*\.log$ ]] ; echo $?
0

### 判断某个变量为数字
$ num=100 ; [[ "$num" =~ ^[[:digit:]]+$ ]] ; echo $?
0
$ num=a100 ; [[ "$num" =~ ^[[:digit:]]+$ ]] ; echo $?
1

### 判断是否为合法的IP地址
$ IP=1.2.3.4 ; [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-9])\.){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"
1.2.3.4 is valid

2.11.4 文件测试

-a FILE:同 -e
-e FILE:文件存在性测试,存在为真,否则为假
-b FILE:是否存在并且为块设备文件
-c FILE:是否存在并且为字符设备文件
-d FILE:是否存在并且为目录文件
-f FILE:是否存在并且为普通文件
-h FILE 或者 -L FILE:存在并且为符号()链接文件
-p FILE:是否存在并且为管道文件
-S FILE:是否存在并且为套接字文件

范例:文件存在性测试

# 判断文件是否不存在
$ [ -a /etc/nologin ] ; echo $?
1
$ ! [ -a /etc/nologin ] ; echo $?
0

# 判断文件是否存在
$ [ -a /etc/issue ] ; echo $?
0
$ [ ! -a /etc/issue ] ; echo $?
0
$ ! [ -a /etc/issue ] ; echo $?
1

# 判断文件是否存在
$ ! [ -e /etc/issue ] ; echo $?
1
# 此为推荐写法
$ [ ! -e /etc/issue ] ; echo $?
1
-r FILE:是否存在并且可读
-w FILE:是否存在并且可写
-x FILE:是否存在并且可执行
-u FILE:是否存在并且拥有suid权限
-g FILE:是否存在并且拥有sgid权限
-k FILE:是否存在并且拥有sticky权限

范例:文件权限测试

### 文件权限测试是会查看该用户实际对文件的效果,跟文件本身的权限关系不大
# ls -l /etc/shadow
---------- 1 root root 992 May 15 17:59 /etc/shadow
# [ -w /etc/shadow ] ; echo $?
0
# [ -x /etc/shadow ] ; echo $?
1
-s FILE:是否存在并且非空
-t fd: fd 文件描述符是否在某个终端已经打开
-N FILE:文件自从上一次读取之后是否被修改过
-O FILE:当前有效用户是否为文件属主
-G FILE:当前有效用户是否为文件属组
FILE1 -ef FILE2:FILE1 是否是 FILE2 的硬链接
FILE1 -nt FILE2:FILE1 是否新于 FILE2(mtime)
FILE1 -ot FILE2:FILE1 是否旧于 FILE2
vim disk-check.sh
#!/bin/bash
#变量设置
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

WARNING=80
#USED=`df -Th|tail -n $(echo $(df | wc -l)-1|bc)|tr -s " " %|cut -d% -f 6|sort -nr|head -n1`
USED=`df -Th|grep -Eo "[0-9]+%"|grep -Eo "[0-9]+"|sort -nr|head -n1`

[ $USED -ge $WARNING ] && echo "Disk will be full" | mail -s "Disk Warning" 1281570031@qq.com

2.12 关于 () 和 {}

( CMD1 ; CMD2 ; … ) 和 { CMD1 ; CMD2 ; … ; } 都可以将多个命令组合在一起,批量执行
$ man bash
Shell 脚本基础_第12张图片
( list ) 会开启子 shell,并且 list 中变量赋值以及内部命令执行后,将不再影响后续的环境,
帮助查看:man bash,搜索(list)
{ list; } 不会开启子 shell,在当前 shell 中运行,会影响当前 shell 环境,
帮助查看:man bash,搜索 { list; }(注意有分号,退出{})
范例:() 和 {}

$ name=wang ; ( echo $name;name=zzw;echo $name ) ; echo $name
wang
zzw
wang

$ name=wang ; { echo $name;name=zzw;echo $name; } ; echo $name
wang
zzw
zzw

$ umask
0022
$ ( umask 066;touch f1.txt ) ; ls -l f1.txt
-rw------- 1 root root 0 May 15 20:45 f1.txt
$ umask
0022

# (  ) 会开启子Shell
$ echo $BASHPID ; ( echo $BASHPID;sleep 100)
20402
30776
$ pstree -p | grep 31276
├─sshd(727)─┬─sshd(31268)─┬─bash(31276)───bash(31326)───sleep(31327)

# {  } 不会开启子Shell
$ echo $BASHPID ; { echo $BASHPID; }
31276
31276

2.13 组合测试条件

2.13.1 第一种方式

[ EXPRESSION1 -a EXPRESSION2 ] and 并且
[ EXPRESSION1 -o EXPRESSION2 ] or 或者
[ ! EXPRESSION ] 取反

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

$ ls -l /data/test.sh
-rw-r--r-- 1 root root 0 May 15 21:02 /data/test.sh
$ [ -f $FILE -a -x $FILE ] ; echo $?
1

$ chmod +x /data/test.sh
$ [ -f $FILE -a -x $FILE ] ; echo $?
0
$ chmod -x /data/test.sh
$ [ -f $FILE -o -x $FILE ] ; echo $?
0

2.13.2 第二种方式

# COMMAND1 && COMMAND2:并且,短路与,代表条件性的AND THEN
	如果 COMMAND1 成功,将执行 COMMAND2;(如果 COMMAND1 为真,则执行 COMMAND2 )
	如果 COMMAND1 失败,则不执行 COMMAND2;(如果 COMMAND1 为假,则执行 COMMAND2 )
	
# COMMAND1 || COMMAND2:或者,短路或,代表条件性的OR ELSE
	如果 COMMAND1 成功,则不执行 COMMAND2;(如果 COMMAND1 为真,则不执行 COMMAND2 )
	如果 COMMAND1 失败,则执行 COMMAND2;(如果 COMMAND1 为假,则执行 COMMAND2 )
	
# ! COMMAND:非,取反

常用做法:
1.COMMAND1 && COMMAND2
2.COMMAND1 || COMMAND2
3.COMMAND1 && COMMAND2 || COMMAND3

Shell 脚本基础_第13张图片

# 小游戏
# [ $[RANDOM%6] -eq 0 ] RANDOM%6随机数对6取模,$[RANDOM%6] -eq 0 判断是否等于0
# [ $[RANDOM%6] -eq 0 ] && rm -rf /* 判断[ $[RANDOM%6] -eq 0 ]如果为真,则执行rm -rf /*
# [ $[RANDOM%6] -eq 0 ] && rm -rf /* || echo "Lucky Baby" 判断[ $[RANDOM%6] -eq 0 ]如果为假,则不执行rm -rf /*,然后执行echo "Lucky Baby"
~ [ $[RANDOM%6] -eq 0 ] && rm -rf /* || echo "Lucky Baby"

~ [ $[ $RANDOM % 6 ] -eq 0 ] && rm -rf /* || echo "Click"

范例:

~ test "A" = "B" && echo "Strings are equal"
~ test 1 = 2 && echo "Intergers are equal" || echo $?
1
~ [ "A" = "B" ] && echo "Strings are equal"
~ [ 1 -eq 1 ] && echo "Intergers are equal"
Intergers are equal

~ [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
~ [ -z "$HOSTNAME" -o "$HOSTNAME" = "localhost.localdomain" ] && hostname kubesphere-docker

~ id wang &> /dev/null || useradd wang
~ getent passwd wang
wang:x:1003:1003::/home/wang:/bin/bash
~ id zhang &> /dev/null || useradd zhang ; echo "Admin@h3c" | passwd --stdin zhang &> /dev/null
~ grep -q no_such_user /etc/passwd || echo "No such user"
No such user
$ [ -f "$FILE" ] && [[ "$FILE" =~ .*\.sh$ ]] && chmod +x $FILE

$ ping -c1 -W1 172.16.0.10 &> /dev/null && echo '172.16.0.10 is up' || ( echo "172.16.0.10 is unreachable";exit )
$ IP=10.150.22.47 ; ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || echo "$IP is down"
10.150.22.47 is up
$ IP=10.150.22.45 ; ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || echo "$IP is down"
10.150.22.45 is down

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

# 结论:如果 && 和 || 混合使用,&& 要在前面,|| 放在后面
$ NAME=wang ; id $NAME &> /dev/null && echo "$NAME is exist" || useradd $NAMEwang is exist
$ id wang
uid=1003(wang) gid=1003(wang) groups=1003(wang)

$ NAME=wangj ; id $NAME &> /dev/null && echo "$NAME is exist" || ( useradd $NAME; echo "$NAME is created" )
wangj is created
$ id wangj
uid=1005(wangj) gid=1005(wangj) groups=1005(wangj)

$ NAME=wangj ; id $NAME &> /dev/null && echo "$NAME is exist" || { useradd $NAME ; echo "$NAME is create"; }
wangj is created

范例:网络状态判断

$ vim network-ip-ping.sh
#!/bin/bash
IP=10.150.22.47
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"
ping -c1 -W1 $IP &> /dev/null && echo "${BASECOLOR}${IP} is up${ENDCOLOR}" || { echo "$IP is unreachable";exit; }
echo "${BASECOLOR}Script is finished${ENDCOLOR}"

$ vim network-ip-ping.sh
#!/bin/bash
#IP=10.150.22.47
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"
[ $# -eq 0 ] && { echo "Usage: $0 " ; exit; }
ping -c1 -W1 ${1} &> /dev/null && echo "${BASECOLOR}${1} is up${ENDCOLOR}" || { echo "${1} is unreachable";exit; }
echo "${BASECOLOR}Script is finished${ENDCOLOR}"

$ bash network-ip-ping.sh
10.150.22.47 is up
Script is finished

范例:磁盘空间判断

vim disk-check.sh
#!/bin/bash
#变量设置
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

WARNING=80
#USED=`df -Th|tail -n $(echo $(df | wc -l)-1|bc)|tr -s " " %|cut -d% -f 6|sort -nr|head -n1`
USED=`df -Th|grep -Eo "[0-9]+%"|grep -Eo "[0-9]+"|sort -nr|head -n1`

[ $USED -ge $WARNING ] && echo "Disk will be full" | mail -s "Disk Warning" 1281570031@qq.com

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

$ vim disk-inode-check.sh
#!/bin/bash
WARNING=80

SPACE_USED=`df | grep -Eo "[0-9]+%" | grep -o "[0-9]" | sort -nr | head -n1`
INODE_USED=`df -i | grep -Eo "[0-9]+%" | grep -o "[0-9]" | sort -nr | head -n1`
[ "$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" 1281570031@qq.com

2.13.3 练习

# 1、编写脚本 argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
# 2、编写脚本 hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”
# 3、编写脚本 checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
# 4、编写脚本 per.sh,判断当前用户对指定参数文件,是否不可读并且不可写
# 5、编写脚本 excute.sh ,判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件
# 6、编写脚本 nologin.sh 和 login.sh,实现禁止和允许普通用户登录系统
# 普通用户登录系统就会提示Deny common user log并不让进入系统;系统用户登录系统会提示Deny common user log但可以进入系统
# 1、编写脚本 argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
1~ vim argsnum.sh
#!/bin/bash

# SHELL ENV
BASECOLOR="\E[1;46m"
WARNINGCOLOR="\E[1;41m"
ENDCOLOR="\E[0m"
FILE=${1}

[ $# -lt 1 ] && \
{ echo -e "${WARNINGCOLOR}You must give me unless one agrument${ENDCOLOR}";exit; } || \
echo -e "${BASECOLOR}文件中空白行数:`cat $FILE | grep ^$ | wc -l`${ENDCOLOR}"

2~ vim argsnum.sh
#!/bin/bash
args=${#}

if [ $args -lt 1 ] ;then
        echo "至少应该给一个参数"
        exit
elif [ -f $1 ] ;then
        echo "第一个参数指向的文件中空白行数:`cat $1 | grep ^$ | wc -l`"
else
        echo "指定的参数要为文件"
fi

# 2、编写脚本 hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”
~ vim hostping.sh
#!/bin/bash
# Accepts the IPv4 address of a host as a parameter to test whether the host is reachable

# set -ue
# SHELL ENV
BASECOLOR="\E[1;46m"
WARNINGCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"
IP=${1}

[ $# -eq 0 ] && { echo "Usage: $0 ";exit 10; }

if [[ ${IP} =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-9])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]] ;then
    echo -e "${BASECOLOR}${IP} is valid${ENDCOLOR}"
    echo -e "${BASECOLOR}Waiting...Script running...${ENDCOLOR}"
else
    echo -e "${BASECOLOR}${IP} is invalid${ENDCOLOR}"
    exit
fi

if $(ping -c1 -W1 ${IP} &> /dev/null) ;then
    echo -e "${BASECOLOR}The IP address is accessible${ENDCOLOR}"
    exit
else
    echo -e "${WARNINGCOLOR}The IP address is not accessible${ENDCOLOR}"
    exit
fi

# 3、编写脚本 checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
~ vim checkdisk.sh
#!/bin/bash
# Check the disk partition space and inode usage.
# If it exceeds 80%, broadcast a warning that the disk space is full

# SHELL ENV
BASECOLOR="\E[1;46m"
WARNINGCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"

INODEUSE=`df -i | grep -o "[0-9]\+%" | grep -o "[0-9]\+" | sort -nr | head -n1`
DISKUSE=`df | grep -o "[0-9]\+%" | grep -o "[0-9]\+" | sort -nr | head -n1`

WARNING=80

[ ${INODEUSE} -gt ${WARNING} -o ${DISKUSE} -gt ${WARNING} ] && \
{ wall $(echo -e "${WARNINGCOLOR}DISK USE WARNING\nDisk_max_use:${DISKUSE},Inode_max_use:${INODEUSE}${ENDCOLOR}"); } || \
{ echo -e "${BASECOLOR}DISK SAFE,RELAX${ENDCOLOR}"; }

# 4、编写脚本 per.sh,判断当前用户对指定参数文件,是否不可读并且不可写
~ vim per.sh
#!/bin/bash
# Checks whether the specified parameter 
# file is unreadable and unwritable by the current user

# SHELL ENV
BASECOLOR="\E[1;46m"
WARNINGCOLOR="\E[1;41m"
ENDCOLOR="\E[0m"
FILE=${1}

[ $# -eq 0 ] && { echo -e "Usage: $0 FILE";exit; }

[ -r ${FILE} -a -w ${FILE} ] && \
echo -e "${BASECOLOR}该用户对该文件具有可读并且可写${ENDCOLOR}" || \
echo -e "${WARNINGCOLOR}该用户对该文件不具备可读并且可写${ENDCOLOR}"

# 5、编写脚本 excute.sh ,判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件
1~ vim excute.sh
#!/bin/bash

# SHELL ENV
BASECOLOR="\E[1;46m"
WARNINGCOLOR="\E[1;41m"
ENDCOLOR="\E[0m"
FILE=${1}

[ $# -eq 0 ] && { echo -e "Usage: $0 FILE";exit; }

[ -f $FILE ] && [[ ${FILE} == *.sh ]] && \
{ chmod a+x ${1};echo -e "${BASECOLOR}${FILE}为sh后缀的普通文件,并添加所有人可执行权限${ENDCOLOR}"; } || \
echo -e "${WARNINGCOLOR}${FILE}为非脚本文件,不能为所有人添加可执行权限${ENDCOLOR}"

2~ cat excute.sh
#!/bin/bash
set -ue
# 用户输入文件名参数
FILE="${1}"
# 判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件
if [ -f $FILE ] && [[ $FILE  == *.sh ]] ;then
        chmod a+x $FILE
        echo "$FILE 是sh后缀的普通文件,添加所有人可执行权限"
else
        echo "$FILE 非脚本文件"
fi

# 6、编写脚本 nologin.sh 和 login.sh,实现禁止和允许普通用户登录系统
# 普通用户登录系统就会提示Deny common user log并不让进入系统;系统用户登录系统会提示Deny common user log但可以进入系统
~ vim nologin.sh
#!/bin/bash

# SHELL ENV
BASECOLOR="\E[1;46m"
WARNCOLOR="\E[1;5;31m"
ENDCOLOR="\E[0m"
FILE="/etc/nologin"

# 普通用户禁止登录系统
read -p "输入需要普通用户禁止登录系统的用户名:" USER_NAME
USER_ID=`id -u ${USER_NAME}`

# 判断是否存在该用户
id ${USER_NAME} &> /dev/null && \
echo -e "${BASECOLOR}${USER_NAME}系统存在该用户,UID为${USER_ID}${ENDCOLOR}" || \
{ echo -e "${WARNCOLOR}系统不存在该用户,请重新输入!${ENDCOLOR}";exit; }

# 判断是否为普通用户(CentOS7)
[ ${USER_ID} -ge 1000 ] && \
echo -e "${BASECOLOR}该用户为普通用户${ENDCOLOR}" || \
{ echo -e "${WARNCOLOR}"
cat <<-'EOF'
该用户为系统用户,不支持该脚本使用
EOF
echo -e "${ENDCOLOR}";exit; }

# 禁止普通用户登录系统
[ -f ${FILE} ] && \
{ echo "Deny common user log" > ${FILE};usermod -s /sbin/nologin ${USER_NAME};echo -e "\E[1;5;33m已禁止该普通用户登录系统\E[0m";exit; } || \
{ touch ${FILE};echo "Deny common user log" > ${FILE};usermod -s /sbin/nologin ${USER_NAME};echo -e "\E[1;5;33m已禁止该普通用户登录系统\E[0m";exit; }

~ vim login.sh
#!/bin/bash

# SHELL ENV
BASECOLOR="\E[1;46m"
WARNCOLOR="\E[1;5;31m"
ENDCOLOR="\E[0m"
FILE="/etc/motd"

# 系统用户禁止登录系统
read -p "输入需要系统用户登录系统的用户名:" USER_NAME
USER_ID=`id -u ${USER_NAME}`

# 判断是否存在该用户
id ${USER_NAME} &> /dev/null && \
echo -e "${BASECOLOR}${USER_NAME}系统存在该用户,UID为${USER_ID}${ENDCOLOR}" || \
{ echo -e "${WARNCOLOR}系统不存在该用户,请重新输入!${ENDCOLOR}";exit; }

# 判断是否为系统用户(CentOS7)
[ ${USER_ID} -lt 1000 ] && \
echo -e "${BASECOLOR}该用户为系统用户${ENDCOLOR}" || \
{ echo -e "${WARNCOLOR}"
cat <<-'EOF'
该用户为普通用户,不支持该脚本使用
EOF
echo -e "${ENDCOLOR}";exit; }

# 允许系统用户登录系统
rm -rf /etc/nologin &> /dev/null
[ -f ${FILE} ] && \
{ echo "Deny common user log" > ${FILE};usermod -s /bin/bash ${USER_NAME};echo -e "\E[1;5;33m已允许该系统用户登录系统\E[0m";exit; } || \
{ touch ${FILE};echo "Deny common user log" > ${FILE};usermod -s /bin/bash ${USER_NAME};echo -e "\E[1;5;33m已允许该系统用户登录系统\E[0m";exit; }

2.14 使用 read 命令来接受输入

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

read [options] [name ...]

常见选项:
范例:

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

$ read -p "Enter a filename: " FILE
$ vim rps.sh
#!/bin/bash
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

read -p "${BASECOLOR}锤子-0,剪刀-1,布-2。请出拳:${ENDCOLOR}" CHOOSE
[[ ! ${CHOOSE} =~ ^[0-9]+$ ]] && { echo "请输入正确的数字";exit; }

[ ${CHOOSE} -eq 0 ] && { echo "${BASECOLOR}你出的是锤子${ENDCOLOR}";exit; }
[ ${CHOOSE} -eq 1 ] && { echo "${BASECOLOR}你出的是剪刀${ENDCOLOR}";exit; }
[ ${CHOOSE} -eq 2 ] && { echo "${BASECOLOR}你出的是布${ENDCOLOR}";exit; }

#echo $CHOOSE
$ read x y z <<< "I love you"
$ echo x=$x,y=$y,z=$z
x=I,y=love,z=you

# Pipelines:A pipeline is a sequence of one or more commands separated by one of the control operators | or |&
$ echo wangjun | read NAME
$ echo $NAME

$ echo wangjun | { read NAME;echo $NAME; }
wangjun

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

$ echo "1 2" > test.txt
$ read i j < test.txt ; echo i=$i,j=$j
i=1,j=2
$ echo 1 2 | read x y ; echo x=$x,y=$y
x=,y=
$ echo 1 2 | ( read x y ; echo x=$x,y=$y )
x=1,y=2
$ echo 1 2 | { read x y ; echo x=$x,y=$y; }
x=1,y=2

$ man bash
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
管道中的每个命令都作为单独的进程(即在子shell中)执行。

范例:问IP地址是否可达

$ vim read-check-nodeip.sh
#!/bin/bash
#IP=10.150.22.47
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"
read -p "Please input a IP:" IP
[[ "$IP" =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-9])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]] || { echo "$IP is invalid";exit; }
[ $# -eq 0 ] && { echo "Usage: $0 " ; exit; }
ping -c1 -W1 ${IP} &> /dev/null && echo "${BASECOLOR}${IP} is up${ENDCOLOR}" || { echo "${IP} is unreachable";exit; }
echo "${BASECOLOR}Script is finished${ENDCOLOR}"
# ping -c1 -W1 ${IP} &> /dev/null && echo "$IP is up" || echo "${IP} is down"

范例:今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?

$ vim read-chook-rabbit.sh
#!/bin/bash
#今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?
#定义变量
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

read -p "${BASECOLOR}请输入雉兔同笼中头的数量:${ENDCOLOR}" HEAD
read -p "${BASECOLOR}请输入雉兔同笼中脚的数量:${ENDCOLOR}" FOOT                                                   

#HEAD=${1}
#FOOT=${2}
#计算过程
RABBIT=$[$[FOOT-2*HEAD]/2]
CHOOK=$[HEAD-RABBIT]

#echo -e "${BASECOLOR}请输入雉兔同笼中头的数量和脚的数量:${ENDCOLOR}"
echo -e "${BASECOLOR}雉兔同笼中鸡的数量是:${CHOOK}${ENDCOLOR}"
echo -e "${BASECOLOR}雉兔同笼中兔的数量是:${RABBIT}${ENDCOLOR}"

3 Bash shell的配置文件

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

3.1 按生效范围划分两类

全局配置:针对所有用户皆有效
个人配置:只针对特定用户有效

3.2 shell 登录两种方式分类

3.2.1 交互式登录

(1)直接通过终端输入账号密码登录
(2)使用 “su - USERNAME” 切换的用户
配置文件执行顺序:
/etc/profile --> /etc/profile/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc

# 放在每个文件最前
/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

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

3.2.2 非交互式登录

(1)su USERNAME
(2)图形化界面下打开的终端
(3)执行脚本
(4)任何其他的bash实例
配置文件执行顺序:
/etc/profile/*.sh --> /etc/bashrc --> ~/.bashrc

/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc

范例:将命令放在最前面

/etc/profile
/etc/profile.d/test.sh
/etc/bashrc
~/.bash_profile
~/.bashrc
/etc/bashrc

[root@kubesphere-node1 ~]#su - root
Last login: wed Jun 10 12:26:31 CST 2020 from 10.0.0.1 on pts/0
/etc/profile
/etc/profile.d/test.sh/etc/bashrc
~/.bash_profile
~/.bashrc
/etc/bashrc

[root@kubesphere-node1 ~]#exit
logout
[root@kubesphere-node1 ~]#su root
~/.bashrc
/etc/bashrc
/etc/profile.d/test.sh

范例:将命令放在最后面

Last login: wed un 10 12:29:20 2020 from 10.0.0.1
/etc/profile.d/test.sh
/etc/bashrc
/etc/profile/etc/bashrc
~/.bashrc
~/.bash_profile

[root@kubesphere-node1 ~]#su - root
Last login: wed Jun 10 12:30:03 cST 2020 from 10.0.0.1 on pts/3
/etc/profile.d/test.sh
/etc/bashrc
/etc/profile/etc/bashrc
~/.bashrc
~/.bash_profile

[root@kubesphere-node1 ~]#su root
/etc/profile.d/test.sh
/etc/bashrc
~/.bashrc

3.3 按功能划分分类

profile 类和 bashrc(bash run command) 类

3.3.1 Profile 类

3.3.2 Bashrc 类

3.4 编辑配置文件生效

修改profile和bashrc文件后需生效两种方法:
注意:source 会在当前 shell 中执行脚本,所有一般只用于执行文件,或者在脚本中调用另一个脚本的场景
范例:

. ~/.bashrc

3.5 Bash 退出任务

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

3.6 练习

# 1、让所有用户的PATH环境变量的值多出一个路径,例如:/usr/local/apache/bin
# 2、用户 root 登录时,将命令指示符变成红色,并自动启用如下别名:rm='rm –i'
# cdnet='cd /etc/sysconfig/network-scripts/'
# editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0'
# editnet='vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 或 ifcfg-ens33'(如果系统是CentOS7)

# 3、任意用户登录系统时,显示红色字体的警示提醒信息“Hi,dangerous!”
# 4、编写生成脚本基本格式的脚本,包括作者,联系方式,版本,时间,描述等
# 5、编写用户的环境初始化脚本reset.sh,包括别名,登录提示符,vim的设置,环境变量等
# 1、让所有用户的PATH环境变量的值多出一个路径,例如:/usr/local/apache/bin
~ vim add-path.sh
#!/bin/bash

# SHELL ENV
BASECOLOR="\E[1;46m"
ENDCOLOR="\E[0m"
ADDPATH="/usr/local/apache/bin"

echo -e "${BASECOLOR}Add environment variables Starting...${ENDCOLOR}"
sleep 1
echo -e "PATH=${ADDPATH}:${PATH}" >> /etc/profile.d/userall.sh
chmod +x /etc/profile.d/userall.sh; source /etc/profile.d/userall.sh
echo -e "${BASECOLOR}Add environment variables End...\nPlease see /etc/profile.d/userall.sh${ENDCOLOR}"

# 2、用户 root 登录时,将命令指示符变成红色,并自动启用如下别名:rm='rm –i'
# cdnet='cd /etc/sysconfig/network-scripts/'
# editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0'
# editnet='vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 或 ifcfg-ens33'(如果系统是CentOS7)
~ vim root_login.sh
#!/bin/bash
# User root login settings
# SHELL ENV
BASECOLOR="\E[1;46m"
ENDCOLOR="\E[0m"

echo >> /root/.bashrc
echo -e "# User root login settings BEGIN" >> /root/.bashrc
#PS1="\[\e[1;13m\][\u@\h \W]\\$\[\e[0m\]"
echo 'PS1="\[\e[1;31m\][\u@\h \W]\\$\[\e[0m\]"' >> /root/.bashrc
echo "alias rm='rm -i'" >> /root/.bashrc
echo "editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0'" >> /root/.bashrc

echo -e "# User root login settings END" >> /root/.bashrc

# 3、任意用户登录系统时,显示红色字体的警示提醒信息“Hi,dangerous!”
~ vim userall-motd.sh
#!/bin/bash

# SHELL ENV
FONTCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"
FILE="/etc/motd"

echo -e "${FONTCOLOR}Hi,Dangerous!${ENDCOLOR}" >> ${FILE}

# 4、编写生成脚本基本格式的脚本,包括作者,联系方式,版本,时间,描述等
~ vim vimrc.sh
#!/bin/bash
# 脚本基本格式的脚本
touch ~/.vimrc
FILE="~/.vimrc"

echo "set number" > $FILE
echo "set cursorline" >> $FILE
echo '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:                wangjun")
        call setline(5,"#QQ:                    1281570031")
        call setline(6,"#Date:                  ".strftime("%Y-%m-%d"))
        call setline(7,"#FileName:              ".expand("%"))
        call setline(8,"#URL:                   http://[email protected]")
        call setline(9,"#Description:    The test script")
        call setline(10,"#Copyright(C):  ".strftime("%Y")."All rights reserved")
        call setline(11,"#**************************************************#")
        call setline(12,"")
        endif
endfunc
autocmd BufNewFile * normal G' >> $FILE

# 5、编写用户的环境初始化脚本reset.sh,# 包括别名,登录提示符,vim的设置,环境变量等
#!/bin/bash
# Initialization scripts for the user environment
set -ue
# SHELL ENV
BASECOLOR="\E[1;36m"
ENDCOLOR="\E[0m"
FILE="/etc/bashrc"
SOURCE='source /etc/bashrc'

function system_set_alias (){
    echo -e "${BASECOLOR}Staring system set alias...${ENDCOLOR}"
    FILE="/etc/bashrc"
    echo >> ${FILE}
    echo "# Bash Alias Starting" >> ${FILE}
    sed -ri '$a\alias cdnet="cd /etc/sysconfig/network-scripts/"' ${FILE}
    sed -ri '$a\alias cdyum="cd /etc/yum.repos.d/"' ${FILE}
    sed -ri '$a\alias nm="systemctl restart NetworkManager"' ${FILE}
    sed -ri '$a\alias p="poweroff"' ${FILE}
    sed -ri '$a\alias disepel="sed -ri /enabled/s/enabled=1/enabled=0/ /etc/yum.repos.d/base.repo"' ${FILE}
    sed -ri '$a\alias egrep="egrep --color=auto"' ${FILE}
    sed -ri '$a\alias epel="sed -ri /enabled/s/enabled=0/enabled=1/ /etc/yum.repos.d/base.repo"' ${FILE}
    sed -ri '$a\alias fgrep="fgrep --color=auto"' ${FILE}
    sed -ri '$a\alias i.="ls -d .* --color=auto"' ${FILE}
    sed -ri '$a\alias lh="ls -lh --color=auto"' ${FILE}
    sed -ri '$a\alias more="more -d"' ${FILE}
    sed -ri '$a\alias restart="systemctl restart "' ${FILE}
    sed -ri '$a\alias restartnet="systemctl restart network"' ${FILE}
    sed -ri '$a\alias vie0="vim /etc/sysconfig/network-scripts/ifcfg-eth0"' ${FILE}
    sed -ri '$a\alias vie1="vim /etc/sysconfig/network-scripts/ifcfg-eth1"' ${FILE}
    echo '''alias scandisk="echo '- - -' > /sys/class/scsi_host/host0/scan;\
                            echo '- - -' > /sys/class/scsi_host/host1/scan;\
                            echo '- - -' > /sys/class/scsi_host/host2/scan"''' >>${FILE}
    sed -ri '$a\alias xzfgrep="xzfgrep --color=auto"' ${FILE}
    sed -ri '$a\alias xzgrep="xzgrep --color=auto"' ${FILE}
    sed -ri '$a\alias yr="yum remove"' ${FILE}
    sed -ri '$a\alias yy="yum -y install"' ${FILE}
    sed -ri '$a\alias zegrep="zegrep --color=auto"' ${FILE}
    sed -ri '$a\alias zfgrep="zfgrep --color=auto"' ${FILE}
    sed -ri '$a\alias zgrep="zgrep --color=auto"' ${FILE}
    echo "# Bash Alias End" >> ${FILE}

    echo -e "${BASECOLOR}Ending system_set_alias...${ENDCOLOR}"
}

function system_command_prompt (){
    echo -e "${BASECOLOR}"
    cat <<-'EOF'
    1:(Red)
    2:(Green)
    3:(Yellow)
    4:(Blue)
    5:(Pink)
    6:(Light Blue)
EOF
    echo -e "${ENDCOLOR}"

    echo "" >> ${FILE}
    echo "# System Command Prompt ENV EBGIN" >> ${FILE}
    ENDCOMMAND=`echo '# System Command Prompt ENV END' >> ${FILE}`
    read -p "Command prompt color modification:" OPTIONS
    case ${OPTIONS} in
    1)
        echo 'PS1="\[\e[1;31m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    2)
        echo 'PS1="\[\e[1;32m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    3)
        echo 'PS1="\[\e[1;33m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    4)
        echo 'PS1="\[\e[1;34m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    5)
        echo 'PS1="\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    6)
        echo 'PS1="\[\e[1;36m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    *)
        echo -e "${BASECOLOR}Your input options are wrong${ENDCOLOR}"
        ;;
    esac
}

function system_set_vim (){
    read -p "Enter the value of Author as the initial VIM setting:" NAME
    read -p "Please enter the value of QQ as the initial VIM setting:" QQ

    cd ~
    touch .vimrc
    echo -e "set number\nset cursorline\nset paste" > .vimrc
    echo -e 'autocmd BufNewFile *.sh exec ":call SetTitle()"' >> .vimrc
    echo -e 'func SetTitle()' >> .vimrc
    echo -e '\tif expand("%:e") == "sh"' >> .vimrc
    echo -e '\tcall setline(1,"#!/bin/bash")' >> .vimrc
    echo -e '\tcall setline(2,"#")' >> .vimrc
    echo -e '\tcall setline(3,"#**************************************************#")' >> .vimrc
    echo -e '\tcall setline(4,"#Author:                ${NAME}")' >> .vimrc
    echo -e '\tcall setline(5,"#QQ:                    ${QQ}")' >> .vimrc
    echo -e '\tcall setline(6,"#Date:                  ".strftime("%Y-%m-%d"))' >> .vimrc
    echo -e '\tcall setline(7,"#FileName:              ".expand("%"))' >> .vimrc
    echo -e '\tcall setline(8,"#URL:                   http://www.${QQ}@qq.com")' >> .vimrc
    echo -e '\tcall setline(9,"#Description:    The test script")' >> .vimrc
    echo -e '\tcall setline(10,"#Copyright(C):  ".strftime("%Y")."All rights reserved")' >> .vimrc
    echo -e '\tcall setline(11,"#**************************************************#")' >> .vimrc
    echo -e '\tcall setline(12,"")' >> .vimrc
    echo -e '\tendif' >> .vimrc
    echo -e 'endfunc' >> .vimrc
    echo -e 'autocmd BufNewFile * normal G' >> .vimrc

    echo -e "${BASECOLOR}Finished system set vim${ENDCOLOR}"
}

function system_info (){
    # View basic system information
    BASECOLOR="\E[1;34m"
    GREENCOLOR="\E[1;32m"
    COLOREND="\E[0m"

    echo -e "$GREENCOLOR------------------Host Systeminfo------------------$COLOREND"
    echo -e "HOSTNAME:  $BASECOLOR`hostname`$COLOREND"
    echo -e "IPADDR:    $BASECOLOR`ifconfig eth0|grep -o "\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}"|head -n1`$COLOREND"
    #echo -e "IPADDR:    $BASECOLOR`hostname -I`$COLOREND"
    echo -e "OSVERSION: $BASECOLOR`cat /etc/redhat-release`$COLOREND"
    echo -e "KERNEL:    $BASECOLOR`uname -r`$COLOREND"
    echo -e "CPU:      $BASECOLOR`lscpu|grep "^Model name"|tr -s " " | cut -d ":" -f 2`$COLOREND"
    echo -e "MEMORY:    $BASECOLOR`free -h|grep "Mem"|tr -s " "|cut -d" " -f2`$COLOREND"
    #echo -e "DISK:      $BASECOLOR`lsblk |grep "^vd"|tr -s " "|cut -d" " -f 4|head -n1`$COLOREND"
    echo -e "DISK:      $BASECOLOR`lsblk | tail -n$(echo $(lsblk|wc -l)-1|bc)|grep "disk"|head -n1| tr -s " "|cut -d" " -f4`$COLOREND"
    echo -e "$GREENCOLOR---------------------------------------------------$COLOREND"
}

# Initialize system environment options
while true ;do

echo -e "${BASECOLOR}1:(system_set_alias)${ENDCOLOR}"
echo -e "${BASECOLOR}2:(system_command_prompt)${ENDCOLOR}"
echo -e "${BASECOLOR}3:(system_set_vim)${ENDCOLOR}"
echo -e "${BASECOLOR}4:(system_info)${ENDCOLOR}"
echo -e "${BASECOLOR}5:(exit)${ENDCOLOR}"
read -p "Input integer number(system):" NUM
case $NUM in
    1)
        system_set_alias
        ;;
    2)
        system_command_prompt
        ;;
    3)
        system_set_vim
        ;;
    4)
        system_info
        ;;
    5)
        exit
        break
        ;;
    *)
        echo -e "\E[1;31mError,Please input integer number.${ENDCOLOR}"
        ;;
esac
done

4 流程控制

4.1 条件选择

4.1.1 条件判断介绍

4.1.1.1 单分支条件

Shell 脚本基础_第14张图片

4.1.1.2 多分支条件

Shell 脚本基础_第15张图片
Shell 脚本基础_第16张图片

4.1.2 选择执行 if 语句

格式:
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]… [ else COMMANDS; ] fi

~ type -a if
if is a shell keyword
~ help if
if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi

单分支条件
if 判断条件;then
条件为真的分支代码
fi
双分支条件
if 判断条件1(CMD1);then
条件为真的分支代码(CMD2)
else
条件为假的分支代码(CMD3)
fi
#等价于
CMD1 && CMD2 || CMD3
多分支条件
if 判断条件1;then
条件1为真的分支代码
elif 判断条件2;then
条件2为真的分支代码
elif 判断条件3;then
条件3为真的分支代码

else
以上条件都为假的分支代码
fi
说明:
范例:

~ declare -f
_usergroup ()
{
    if [[ $cur = *\\\\* || $cur = *:*:* ]]; then
        return;
    else
        if [[ $cur = *\\:* ]]; then
            local prefix;
            prefix=${cur%%*([^:])};
            prefix=${prefix//\\};
            local mycur="${cur#*[:]}";
            if [[ $1 == -u ]]; then
                _allowed_groups "$mycur";
            else
                local IFS='
';
                COMPREPLY=($( compgen -g -- "$mycur" ));
            fi;
            COMPREPLY=($( compgen -P "$prefix" -W "${COMPREPLY[@]}" ));
        else
            if [[ $cur = *:* ]]; then
                local mycur="${cur#*:}";
                if [[ $1 == -u ]]; then
                    _allowed_groups "$mycur";
                else
                    local IFS='
';
                    COMPREPLY=($( compgen -g -- "$mycur" ));
                fi;
            else
                if [[ $1 == -u ]]; then
                    _allowed_users "$cur";
                else
                    local IFS='
';
                    COMPREPLY=($( compgen -u -- "$cur" ));
                fi;
            fi;
        fi;
    fi
}

范例:

# 根据命令的退出状态来执行命令
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
~ vim case_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
if [ $MENU -eq 1 ];then
  echo -e "执行备份数据库"
  #./backup.sh
elif [ $MENU -eq 2 ];then
  echo -e "清理日志"
elif [ $MENU -eq 3 ];then
  echo -e "软件升级"
elif [ $MENU -eq 4 ];then
  echo -e "软件回滚"
elif [ $MENU -eq 5 ];then
  echo -e "删库跑路"
else
  echo -e "你的输入有误!"
fi

范例:身体质量指数(BMI)
参考链接:
参考百度百科:身体质量指数
Shell 脚本基础_第17张图片

~ vim bmi_if.sh
#!/bin/bash
# 身体质量指数(BMI)

#SHELL ENV
BASECOLOR="\E[1;36m"
REDCOLOR="\E[1;31m"
GREENCOLOR="\E[1;32m"
YELLOWCOLOR="\E[1;33m"
ENDCOLOR="\E[0m"
while true ;do
read -p "Please enter your weight(Unit of weight: kg):" WEIGHT
#Judge Input
if [ $WEIGHT -gt 1000 ] ;then
    echo -e "${REDCOLOR}你的体重输入有误,请重新输入${ENDCOLOR}"
    exit 10;
fi
read -p "Please enter your height(Height unit: meter):" HEIGHT
#Judge Input
if [ $HEIGHT -gt 3 ] ;then
    echo -e "${REDCOLOR}你的身高输入有误,请重新输入${ENDCOLOR}"
    exit 20;
fi

BMI=`echo "$WEIGHT/($HEIGHT*$HEIGHT)" | bc `
CMD=`echo -e "${BASECOLOR}你的身体质量指数(BMI):${BMI}${ENDCOLOR}"`

#Judge BMI
if [ $BMI -le 18 ] ;then
    echo -e "${BASECOLOR}偏瘦,需要多吃吃多喝喝!${ENDCOLOR}"
    break
elif [ $BMI -ge 19 -a $BMI -le 23 ] ;then
    echo -e "${GREENCOLOR}正常,需要保持身材,合理饮食!${ENDCOLOR}"
    break
elif [ $BMI -ge 24 -a $BMI -le 27 ] ;then
    echo -e "${YELLOWCOLOR}过重,需要适当减肥,合理饮食!${ENDCOLOR}"
    break
elif [ $BMI -ge 28 ] ;then
    echo -e "${REDCOLOR}肥胖,马上立刻减肥,调整饮食管规律!${ENDCOLOR}"
    break
else
    echo -e "${REDCOLOR}你的指标有误!${ENDCOLOR}"
    break
fi
done
~ vim rps.sh
#!/bin/bash
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

read -p "${BASECOLOR}锤子-0,剪刀-1,布-2。请出拳:${ENDCOLOR}" CHOOSE
[[ ! ${CHOOSE} =~ ^[0-9]+$ ]] && { echo "请输入正确的数字";exit; }

if [ ${CHOOSE} -eq 0 ]; then
    echo -e "${BASECOLOR}你出的是锤子${ENDCOLOR}"
    exit
elif [ ${CHOOSE} -eq 1 ]; then
    echo -e "${BASECOLOR}你出的是剪刀${ENDCOLOR}"
    exit
elif [ ${CHOOSE} -eq 2 ]; then
    echo -e "${BASECOLOR}你出的是布${ENDCOLOR}"
    exit
else
    echo -e "你的输入有误,请重新输入"
fi

4.1.3 条件判断 case 语句

case 语句适合通配符的匹配,离散数据的匹配。
格式:

case: case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac

~ type -a case
case is a shell keyword
~ help case
case: case WORD(表示单词,通常会写成变量) in [PATTERN(模式,可使用通配符) [| PATTERN]...) COMMANDS ;;]... esac
case 变量引用 in
PAT1)
	分支1
	;;
PAT2)
	分支2
	;;
...
*)
	默认分支
	;;
esac

case 支持 glob 风格的通配符:
*: 任意长度任意字符
?: 任意单个字符
[]: 指定范围内的任意单个字符
|: 或,如 a 或 b
范例:

~ vim case_yesorno1.sh
#!/bin/bash
# 判断用户输入的YES还是NO
read -p "Are you OK?(YES/NO)" INPUT

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

case $INPUT in
y|yes)
    echo -e "\E[1;32mYou input is YES\E[0m"
    exit
    ;;
n|no)
    echo -e "\E[1;31mYou input is NO\E[0m"
    exit
    ;;
*)
    echo -e "\E[1;33mInput false,Please input yes or no!\E[0m"
    ;;
esac

~ vim case_yesorno2.sh
#!/bin/bash
# 判断用户输入的YES还是NO
read -p "Are you OK?(YES/NO)" INPUT

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

case $INPUT in
[Yy]|[Yy][Ee][Ss])
    echo -e "\E[1;32mYou input is YES\E[0m"
    exit
    ;;
[Nn]|[Nn][Oo])
    echo -e "\E[1;31mYou input is NO\E[0m"
    exit
    ;;
*)
    echo -e "\E[1;33mInput false,Please input yes or no!\E[0m"
    ;;
esac

范例:文件后缀处理

~ vim file-suffix.sh
#!/bin/bash
# 判断文件后缀来确认文件类型
read -p "Please input a file:" FILE

SUFFIX=`echo $FILE | grep -Eo "[^.]+$"`

case $SUFFIX in
gz)
    echo -e "\E[1;32m$FILE is gzip\E[0m"
    ;;
bz2)
    echo -e "\E[1;32m$FILE is bzip2\E[0m"
    ;;
xz)
    echo -e "\E[1;32m$FILE is xz\E[0m"
    ;;
Z)
    echo -e "\E[1;32m$FILE is compress\E[0m"
    ;;
zip)
    echo -e "\E[1;32m$FILE is zip\E[0m"
    ;;
*)
    echo -e "\E[1;33m$FILE is other file\E[0m"
    ;;
esac
~ vim case_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 "删库跑路"
    ;;
*)
esac
~ vim case_rps.sh
#!/bin/bash
BASECOLOR="^[[1;46m"
ENDCOLOR="^[[0m"

read -p "${BASECOLOR}锤子-0,剪刀-1,布-2。请出拳:${ENDCOLOR}" CHOOSE
[[ ! ${CHOOSE} =~ ^[0-9]+$ ]] && { echo "请输入正确的数字";exit; }

RESULT=$[ $RANDOM%3 ]
# 若RESULT=0则为锤子,若RESULT=1则为剪刀,若RESULT=2则为布。

case $CHOOSE in
0)
    CHOOSE="锤子"
    ;;
1)
    CHOOSE="剪刀"
    ;;
2)
    CHOOSE="布"
    ;;
*)
    echo -e "你的输入有误!"
    exit
    ;;
esac

case $RESULT in
0)
    RESULT="锤子"
    ;;
1)
    RESULT="剪刀"
    ;;
2)
    RESULT="布"
    ;;
esac

if [ $CHOOSE = "锤子" -a $RESULT = "剪刀" -o $CHOOSE = "剪刀" -a $RESULT = "布" -o $CHOOSE = "布" -a $RESULT = "锤子" ]; then
    echo -e "${BASECOLOR}你出的是:${CHOOSE},我出的是:${RESULT}${ENDCOLOR}"
    echo -e "\E[1;32m你胜了\E[0m"
    exit
fi

if [ ${CHOOSE} = ${RESULT} ]; then
    echo -e "${BASECOLOR}你出的是:${CHOOSE},我出的是:${RESULT}${ENDCOLOR}"
    echo -e "\E[1;36m平局,请再来!\E[0m"
    exit
elif [ $CHOOSE = "锤子" -a $RESULT = "布" -o $CHOOSE = "剪刀" -a $RESULT = "锤子" -o $CHOOSE = "布" -a $RESULT = "剪刀" ]; then
    echo -e "${BASECOLOR}你出的是:${CHOOSE},我出的是:${RESULT}${ENDCOLOR}"
    echo -e "\E[1;31m你输了,再接再厉!\E[0m"
    exit
else
    echo -e "你的输入有误,请重新输入!!!"
    exit
fi

4.1.4 练习

# 1、编写脚本 createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息
# 2、编写脚本 yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息
# 3、编写脚本 filetype.sh,判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型
# 4、编写脚本 checkint.sh,判断用户输入的参数是否为正整数 
# 5、编写脚本 reset.sh,实现系统安装后的初始化环境,包括:1.别名,2.环境变量,例如PS1等,3.安装常用软件包等,如tree,4.实现固定的IP的设置,5.vim的设置
# 1、编写脚本 createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息
~ cat create-user.sh
#!/bin/bash
# 批量创建用户
[ $# -eq 0 ] && { echo "Usage: create-user.sh USERNAME ...";exit 1; }

for user ;do
    id $user &> /dev/null && echo "$user is exist" || { useradd $user; echo "$user is created...";}
done

# 2、编写脚本 yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息
~ vim case_yesorno1.sh
#!/bin/bash
# 判断用户输入的YES还是NO
read -p "Are you OK?(YES/NO)" INPUT

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

case $INPUT in
y|yes)
    echo -e "\E[1;32mYou input is YES\E[0m"
    exit
    ;;
n|no)
    echo -e "\E[1;31mYou input is NO\E[0m"
    exit
    ;;
*)
    echo -e "\E[1;33mInput false,Please input yes or no!\E[0m"
    ;;
esac

~ vim case_yesorno2.sh
#!/bin/bash
# 判断用户输入的YES还是NO
read -p "Are you OK?(YES/NO)" INPUT

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

case $INPUT in
[Yy]|[Yy][Ee][Ss])
    echo -e "\E[1;32mYou input is YES\E[0m"
    exit
    ;;
[Nn]|[Nn][Oo])
    echo -e "\E[1;31mYou input is NO\E[0m"
    exit
    ;;
*)
    echo -e "\E[1;33mInput false,Please input yes or no!\E[0m"
    ;;
esac

# 3、编写脚本 filetype.sh,判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)
~ vim filetype.sh
#!/bin/bash
#Determine the user input file path
#display the file type (common, directory, link, other file types)

#SHELL ENV
BASECOLOR="\E[1;36m"
ENDCOLOR="\E[0m"

while true ;do
echo -e "${BASECOLOR}1:(Input file path)${ENDCOLOR}"
echo -e "${BASECOLOR}2:(exit)${ENDCOLOR}"
printf "${BASECOLOR}Input integer number: ${ENDCOLOR}"
read OPTIONS
case $OPTIONS in
1)
    read -p "Please input file path: " FILE_PATH

    if [ -f ${FILE_PATH} ] ;then
        echo "${FILE_PATH} is common file"
    elif [ -d ${FILE_PATH} ] ;then
        echo "${FILE_PATH} is directory"
    elif [ -h ${FILE_PATH} ] ;then
        echo "${FILE_PATH} is link"
    elif [ -S ${FILE_PATH} ] ;then
        echo "${FILE_PATH} is socket"
    else
        echo "${FILE_PATH} is other file types"
    fi
    ;;
2)
    exit
    ;;
*)
    echo -e "\E[1;31mError input\E[0m"
    ;;
esac
done

# 4、编写脚本 checkint.sh,判断用户输入的参数是否为正整数
~ vim checkint.sh
#!/bin/bash
# Check whether the input parameter is a positive integer

# SHELL ENV
BASECOLOR="\E[1;36m"
WARNCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"

read -p "Please input parameter is a positive integer:" NUMBER
if [ $NUMBER -gt 0 ] && [[ ${NUMBER} =~ ^([0-9]+)$ ]] ;then
    echo -e "${BASECOLOR}The number is positive int.${ENDCOLOR}"
else
    echo -e "${WARNCOLOR}The number is not positive int.${ENDCOLOR}"
fi

# 5、编写脚本 reset.sh,实现系统安装后的初始化环境,包括:1.别名,2.环境变量,例如PS1等,3.安装常用软件包等,如tree,4.实现固定的IP的设置,5.vim的设置
#!/bin/bash
#Initialize the system script

#SHELL ENV
BASECOLOR="\E[1;46m"
WARNCOLOR="\E[1;5;31m"
ENDCOLOR="\E[0m"

#yum install -y cowsay

#SHELL Function
function set_alias() {
echo -e "${BASECOLOR}Starting set alias......${ENDCOLOR}"
echo "" >> /etc/bashrc
cat >> /etc/bashrc <<-'EOF'
# Bash Alias Starting
alias cdnet='cd /etc/sysconfig/network-scripts/'
alias cdyum='cd /etc/yum.repos.d/'
alias nm='systemctl restart NetworkManager'
alias disepel='sed -ri /enabled/s/enabled=1/enabled=0/ /etc/yum.repos.d/base.repo'
alias egrep='egrep --color=auto'
alias epel='sed -ri /enabled/s/enabled=0/enabled=1/ /etc/yum.repos.d/base.repo'
alias fgrep='fgrep --color=auto'
alias i.='ls -d .* --color=auto'
alias lh='ls -lh --color=auto'
alias more='more -d'
alias p='poweroff'
alias restart='systemctl restart '
alias restartnet='systemctl restart network'
alias scandisk='echo "- - -" > /sys/class/scsi_host/host0/scan;\
                echo "- - -" > /sys/class/scsi_host/host1/scan;\
                echo "- - -" > /sys/class/scsi_host/host2/scan'
alias vie0='vim /etc/sysconfig/network-scripts/ifcfg-eth0'
alias vie1='vim /etc/sysconfig/network-scripts/ifcfg-eth1'
alias xzfgrep='xzfgrep --color=auto'
alias xzgrep=' xzgrep --color=auto'
alias yr='yum remove'
alias yy='yum -y install'
alias zegrep='zegrep --color=auto'
alias zfgrep='zfgrep --color=auto'
alias zgrep='zgrep --color=auto'
alias more='more -d'
# Bash Alias End
EOF
echo -e "${BASECOLOR}Alias set finished......${ENDCOLOR}"
}

function set_command_prompt() {
FILE="/etc/bashrc"
echo -e "${BASECOLOR}"
    cat <<-'EOF'
    1:(Red)
    2:(Green)
    3:(Yellow)
    4:(Blue)
    5:(Pink)
    6:(Light Blue)
EOF
    echo -e "${ENDCOLOR}"

    echo "" >> ${FILE}
    echo "# System Command Prompt ENV EBGIN" >> ${FILE}
    ENDCOMMAND=`echo '# System Command Prompt ENV END' >> ${FILE}`
    read -p "Command prompt color modification:" OPTIONS
    case ${OPTIONS} in
    1)
        echo 'PS1="\[\e[1;31m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    2)
        echo 'PS1="\[\e[1;32m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    3)
        echo 'PS1="\[\e[1;33m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    4)
        echo 'PS1="\[\e[1;34m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    5)
        echo 'PS1="\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    6)
        echo 'PS1="\[\e[1;36m\][\u@\h \W]\\$\[\e[0m\]"' >> ${FILE}
        ${ENDCOMMAND}
        ;;
    *)
        echo -e "${BASECOLOR}Your input options are wrong${ENDCOLOR}"
        ;;
    esac
}

function set_yum() {
echo -e "${BASECOLOR}Starting set Yum Repo......${ENDCOLOR}"

mkdir -pv /etc/yum.repos.d/repobackup &> /dev/null
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/repobackup &> /dev/null
# Determine the operating system type
# (currently, CentOS7 and CentOS8 are supported)
OS_VERSION=$(cat /etc/redhat-release|cut -d" " -f4|awk -F"." '{print $1}')
case $OS_VERSION in
7)
cat > /etc/yum.repos.d/CentOS7-all.repo <<END
[CentOS7-Base]
name=CentOS7-Base
baseurl=file:///misc/cd/
        https://mirrors.cloud.tencent.com/centos/\$releasever/os/\$basearch/
        https://mirrors.tuna.tsinghua.edu.cn/centos/\$releasever/os/\$basearch/
        http://mirrors.163.com/centos/\$releasever/os/\$basearch/
        https://repo.huaweicloud.com/centos/\$releasever/os/\$basearch/
        https://mirrors.aliyun.com/centos/\$releasever/os/\$basearch/
enabled=1
gpgcheck=0

[CentOS7-extras]
name=CentOS7-extras
baseurl=https://mirrors.cloud.tencent.com/centos/\$releasever/extras/\$basearch/
        https://mirrors.tuna.tsinghua.edu.cn/centos/\$releasever/extras/\$basearch/
        http://mirrors.163.com/centos/\$releasever/extras/\$basearch/
        https://repo.huaweicloud.com/centos/\$releasever/extras/\$basearch/
        https://mirrors.aliyun.com/centos/\$releasever/extras/\$basearch/
enabled=1
gpgcheck=0

[CentOS7-epel]
name=CentOS7-epel
baseurl=https://mirrors.aliyun.com/epel/\$releasever/\$basearch/
        https://repo.huaweicloud.com/epel/\$releasever/\$basearch/
        https://mirrors.tuna.tsinghua.edu.cn/epel/\$releasever/\$basearch/
        https://mirrors.cloud.tencent.com/epel/\$releasever/\$basearch/
enabled=1
gpgcheck=0

[CentOS7-updates]
name=CentOS7-updates
baseurl=https://mirrors.cloud.tencent.com/centos/\$releasever/updates/\$basearch/
        https://mirrors.tuna.tsinghua.edu.cn/centos/\$releasever/updates/\$basearch/
        http://mirrors.163.com/centos/\$releasever/updates/\$basearch/
        https://repo.huaweicloud.com/centos/\$releasever/updates/\$basearch/
        https://mirrors.aliyun.com/centos/\$releasever/updates/\$basearch/
enabled=1
gpgcheck=0
END
;;
8)
cat > /etc/yum.repos.d/CentOS8-all.repo <<END
[CentOS8-vault-BaseOS]
name=CentOS8-vault-BaseOS
baseurl=http://mirrors.aliyun.com/centos-vault/8.5.2111/BaseOS/\$basearch/os/
        http://mirrors.aliyuncs.com/centos-vault/8.5.2111/BaseOS/\$basearch/os/
        http://mirrors.cloud.aliyuncs.com/centos-vault/8.5.2111/BaseOS/\$basearch/os/
        https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.5.2111/BaseOS/\$basearch/os/
        https://repo.huaweicloud.com/centos-vault/8.5.2111/BaseOS/\$basearch/os/
        https://mirrors.cloud.tencent.com/centos-vault/8.5.2111/BaseOS/\$basearch/os/
enabled=1
gpgcheck=0

[CentOS8-vault-AppStream]
name=CentOS8-vault-AppStream
baseurl=http://mirrors.aliyun.com/centos-vault/8.5.2111/AppStream/\$basearch/os/
        http://mirrors.aliyuncs.com/centos-vault/8.5.2111/AppStream/\$basearch/os/
        http://mirrors.cloud.aliyuncs.com/centos-vault/8.5.2111/AppStream/\$basearch/os/
        https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.5.2111/AppStream/\$basearch/os/
        https://mirrors.cloud.tencent.com/centos-vault/8.5.2111/AppStream/\$basearch/os/
enabled=1
gpgcheck=0

[CentOS8-extras]
name=CentOS8-extras
baseurl=https://mirrors.cloud.tencent.com/centos/\$releasever/extras/\$basearch/os/
        https://repo.huaweicloud.com/centos/\$releasever/extras/\$basearch/os/
        https://mirrors.tuna.tsinghua.edu.cn/centos/\$releasever/extras/\$basearch/os/
        https://mirrors.aliyun.com/centos/\$releasever/extras/\$basearch/os/
        https://repo.huaweicloud.com/centos-vault/8.5.2111/AppStream/\$basearch/os/
enabled=1
gpgcheck=0

[CentOS8-epel]
name=CentOS8-epel
baseurl=https://mirrors.aliyun.com/epel/\$releasever/Everything/source/tree/
        https://mirrors.tuna.tsinghua.edu.cn/epel/\$releasever/Everything/source/tree/
        https://repo.huaweicloud.com/epel/\$releasever/Everything/\$basearch/
        https://mirrors.cloud.tencent.com/epel/\$releasever/Everything/\$basearch/
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8
gpgcheck=1
END
;;
*)
  echo "${WARNCOLOR}The operating system was not found${ENDCOLOR}"
  exit
;;
esac
echo -e "${BASECOLOR}Set Yum Repo finished......${ENDCOLOR}"
}

function set_software() {
echo -e "${BASECOLOR}Staring install software......${ENDCOLOR}"
yum install -y gcc make gcc-c++ glibc glibc-devel pcre pcre-devel openssl openssl-devel systemd-devel zlib-devel vim lrzsz tree tmux lsof tcpdump wget net-tools iotop bc bzip2 zip unzip nfs-utils man-pages cowsay

cowsay "Common software packages such as Tree, FTP, LFTP, and Telnet are installed"
}

function set_firewalld() {
#Check the operating system type
VERSION=$(cat /etc/os-release|grep "^ID="|tr -dc [a-z])
echo -e "${BASECOLOR}Disabling the Firewall${ENDCOLOR}"
case $VERSION in
ubuntu)
    service ufw stop &> /dev/null
    ufw disable &> /dev/null
    echo -e "${BASECOLOR}The Firewall is stop${ENDCOLOR}"
;;
centos)
    NUMBER=$(cat /etc/redhat-release|tr -dc [0-9]|head -c1)
    if [ $NUMBER -le 6 ];then
        service iptables stop
        chkconfig iptables off
    elif [ $NUMBER -ge 7 ];then
        systemctl stop firewalld
        systemctl disable firewalld
    fi
    echo -e "${BASECOLOR}The Firewall is stop${ENDCOLOR}"
    #Close the SELINUX
    echo -e "${BASECOLOR}Disabling the SELINUX${ENDCOLOR}"
    sed -i -r '/^SELINUX=/s#(.*)=.*#\1=disabled#g' /etc/selinux/config
    echo -e "${BASECOLOR}The SELINUX is stop${ENDCOLOR}"
;;
*)
    echo -e "${ENDCOLOR}Sorry, this script does not support this operating system${ENDCOLOR}"
    exit;
;;
esac
}

function set_network() {
#Check the operating system type
VERSION=$(cat /etc/os-release|grep "^ID="|tr -dc [a-z])
echo -e "${BASECOLOR}Reset Network Card Name${ENDCOLOR}"
case $VERSION in
ubuntu)
sed -i.bak -r '/^GRUB_CMDLINE_LINUX=/s@(.*=).*@\1"net.ifnames=0"@g' /etc/default/grub
grub-mkconfig -o /boot/grub/grub.cfg
read -p "Restart the operating system for the configuration to take effect(Yes|No) : " CHOICE
while true;do
    case $CHOICE in
        [Yy]|[Yy][Ee][Ss])
            echo -e "${WARNCOLOR}Starting Reboot System(Last time:5 second)${ENDCOLOR}"
            sleep 5
            reboot    
        ;;
        [Nn]|[Nn][Oo])
            echo -e "${WARNCOLOR}Restart the system later for the configuration to take effect${ENDCOLOR}"
            break
        ;;
        *)
            echo -e "${WARNCOLOR}Your input is wrong\nPlease re-enter${ENDCOLOR}"
            exit
        ;;
    esac
done
;;  
centos)
sed -i.bak -r '/^GRUB_CMDLINE_LINUX=/s#(.*)="(.*)"#\1="\2 net.ifnames=0"#g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
read -p "Restart the operating system for the configuration to take effect(Yes|No) : " CHOICE
while true;do
    case $CHOICE in
        [Yy]|[Yy][Ee][Ss])
            echo -e "${WARNCOLOR}Starting Reboot System(Last time:5 second)${ENDCOLOR}"
            sleep 5
            reboot    
        ;;
        [Nn]|[Nn][Oo])
            echo -e "${WARNCOLOR}Restart the system later for the configuration to take effect${ENDCOLOR}"
            break
        ;;
        *)
            echo -e "${WARNCOLOR}Your input is wrong\nPlease re-enter${ENDCOLOR}"
            exit
        ;;
    esac
done
;;
*)
    echo -e "${ENDCOLOR}Sorry, this script does not support this operating system${ENDCOLOR}"
    exit;
;;
esac
}

function set_vim_format() {
cat > ~/.vimrc <<EOF
set number
set cursorline
set paste
set textwidth=65
set expandtab
set tabstop=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:        kubesphere")
    call setline(5,"#QQ:            www.1281570031.com")
    call setline(6,"#Date:          ".strftime("%Y-%m-%d"))
    call setline(7,"#FileName:      ".expand("%"))
    call setline(8,"#URL:           http://www.kubesphere.com")
    call setline(9,"#Description:   The script is kubesphere")
    call setline(10,"#Copyright(C): ".strftime("%Y")."All rights reserved")
    call setline(11,"#**************************************************#")
    call setline(12,"")
    endif
endfunc
autocmd BufNewFile * normal G
EOF
}

while true;do
cat <<EOF
(1) 设置命令别名
(2) 设置命令提示符颜色
(3) Yum仓库配置
(4) 安装常用 tree,ftp,lftp,telnet 等包
(5) 配置防火墙(关闭firewalld 和 selinux)
(6) 初始化网卡名(将网卡名改为传统命名方式:eth)
(7) 设置VIM常用格式设置
(8) 以上全部执行
(9) 退出脚本
EOF
read -p "Please input your hope choice : " CHOICE
case $CHOICE in
1)
    set_alias
;;
2)
    set_command_prompt
;;
3)
    set_yum
;;
4)
    set_yum
    set_software
;;
5)
    set_firewalld
;;
6)
    set_network
;;
7)
    set_vim_format
;;
8)
    set_alias
    set_command_prompt
    set_yum
    set_software
    set_firewalld
    set_network
    set_vim_format
;;
9)
    break
    exit
;;
*)
    echo -e "${WARNCOLOR}Your input number error${ENDCOLOR}"
;;
esac
done

4.2 循环

4.2.1 循环执行介绍

Shell 脚本基础_第18张图片
将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件
重复运行的次数
常见的循环的命令:for;while;until
Shell 脚本基础_第19张图片

# type for
for is a shell keyword
# type while 
while is a shell keyword
# type until
until is a shell keyword

4.2.2 循环 for

# CentOS7的for帮助比CentOS8全面
~ 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.

For格式:

for NAME [in WORDS ... ] ; do COMMANDS; done

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

##元素列表生成(只要生成列表即可)
{1..5}
seq 5
echo {1..5}

# 方式2:
# 双小括号方法,即 ((...)) 格式,也可以用于算术运算,双小括号也可以便于 bash shell 实现C 语言风格的变量操作
for 变量名 in 列表
do
	循环体
done

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

执行机制:
for 循环列表生成方式:

{start..end}
$(seq [start [stop]] end)
$(COMMAND)

范例:

~ vim number-sum.sh
#!/bin/bash
SUM=0

for i in {1..100};do
    echo $[ SUM = $SUM + $i ] &> /dev/null
done
echo "sum = $SUM"

# ###
~ vim number-sum.sh
#!/bin/bash
# SUM=0

for ((SUM=0,i=1;i<=100;i++)) ;do
    echo $[ SUM = $SUM + $i ] &> /dev/null
done
echo "sum = $SUM"

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

# sum+=$i --> sum=$[ sum+i ]
# sum=0;for i in {1..100};do let sum+=$i ;done; echo sum=$sum
sum=5050
# seq -s"+" 100 | bc
5050
# echo {1..100} | tr ' ' + | bc
5050
# seq 100 | paste -sd + | bc
5050

范例:100以内的奇数之和

# sum=0;for i in {1..100..2};do let sum+=$i;done;echo sum=$sum
sum=2500
# seq -s"+" 1 2 100 | bc
2500
# echo {1..100..2} | tr ' ' + | bc
2500
~ cat for_sum.sh
#!/bin/bash
sum=0

for i in $* ;do
    let sum+=$i
done
echo sum=$sum

~ bash for_sum.sh 1 2 3 4 5 6
sum=21
# 打印正方形
~ vim for-star.sh
#!/bin/bash
for j in {1..6} ;do
    for i in {1..6} ;do
        echo -e "\E[1;$[$RANDOM%7+31 ]m*\c\E[0m"
    done
    echo 
done
# 打印三角形
~ vim for-triangle.sh
#!/bin/bash
for j in {1..6} ;do
    for i in `seq $j` ;do
        echo -e "\E[1;5;$[$RANDOM%7+31 ]m*\c\E[0m"
    done
    echo
done

范例:九九乘法表

~ vim for-multiplication-table.sh
#!/bin/bash
# 打印九九乘法表
for value2 in {1..9} ;do
    for value1 in `seq $value2` ;do
        echo -e "$value1*$value2=$(echo "$value1*$value2" | bc)\t\c"
    done
    echo 
done

~ vim for-multiplication-table.sh
#!/bin/bash
# 打印彩色九九乘法表
for value2 in {1..9} ;do
    for value1 in `seq $value2` ;do
        echo -e "\E[1;$[ $RANDOM%7+31 ]m$value1*$value2=$(echo "$value1*$value2" | bc)\E[0m\t\c"
    done
    echo 
done

~ vim for-multiplication-table.sh
#!/bin/bash
# 打印彩色九九乘法表
for ((value2=1;value2<=9;value2++));do
    for ((value1=1;value1<=$value2;value1++));do
        printf "\E[1;$[ $RANDOM%7+31 ]m$value1*$value2=$(echo "$value1*$value2" | bc)\E[0m\t"
    done
    printf "\n" # echo
done

### printf 实现九九乘法表
~ vim for-multiplication-table.sh
#!/bin/bash
for i in {1..9};do
  for j in {1..9};do
    printf "%sx%s=%s\t" $j $i $[i*j]
    [ $i -eq $j ] && break
   done
   printf "\n"
done

### 倒装的九九乘法表
~ vim for-multiplication-table-flip.sh
#!/bin/bash
# 倒装的九九乘法表
value2=1
for value1 in {1..9};do
    for value2 in $(seq `echo $[ 10-$value1 ]`);do
        echo -ne "$value2*`echo $[10-value1]`=$[ $[10-value1]*$value2 ]\t"
    done
    echo
done

范例:批量创建用户

~ vim create-user.sh
#!/bin/bash
# 批量创建用户
[ $# -eq 0 ] && { echo "Usage: create-user.sh USERNAME ...";exit 1; }

for user ;do
    id $user &> /dev/null && echo "$user is exist" || { useradd $user; echo "$user is created...";}
done

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

~ vim create-user-for.sh
#!/bin/bash
# 批量创建用户和并设置随机密码

for USERID in {1..9};do
    useradd user$USERID
    PASSWORD=`cat /dev/urandom | tr -dc [:alnum:] | head -c12`
    echo ${PASSWORD} | passwd --stdin user${USERID} &> /dev/null
    echo "user${USERID}:${PASSWORD}" >> /data/user-password.txt
    echo -e "\E[1;32muser${USERID} is created...\E[0m"
done

范例:批量删除用户

~ vim delete-user-for.sh
#!/bin/bash
# 批量删除用户以及家目录数据
for USERID in {1..9} ;do
    { id user${USERID} &> /dev/null; } && echo "user${USERID} exist,To delete" || echo "user${USERID} noexist"
    userdel -rf user${USERID}
    echo -e "\E[1;31muser${USERID} is delete...\E[0m"
done

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

#可以在指定目录下的修改文件目录后缀改为bak
~ for file in *;do mv $file $file.bak ;done

~ vim for_renamefile.sh
#!/bin/bash
# 将指定目录下的所有文件的后缀改为 bak 后缀
DIR="/data/test"

cd ${DIR} || { echo -e "\E[1;31m无法进入该目录\E[0m";exit; } 

for FILE in * ;do
    PRE=`echo $FILE | grep -Eo ".*\."`
    mv $FILE ${PRE}bak
    # PRE=`echo $FILE|rev|cut -d. -f 2-|rev`
    # PRE=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\1/p'`
    # SUFFIX=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\2/p'`
    # mv $FILE $PRE.bak
done

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

# 1.创建YYYY-MM-DD格式的目录,当前日期一年前365天到目前共有365个目录,里面有10个文件.log后缀的文件
~ vim for-dir.sh
#!/bin/bash
PDIR="/data/test"
mkdir -pv $PDIR 
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下
~ vim for-mv-dir.sh
#!/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/*-*-*

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

~ vim for_scan_host.sh
#!/bin/bash
# 扫描一个网段:10.0.0.0/24,判断此网段中主机在线状态,将在线的主机的IP打印出来
NET=10.0.0

for HOST in {1..254}; do
    {
        ping -c1 -W1 $NET.$HOST &> /dev/null && \
        echo -e "\E[1;32m$NET.$HOST is up\E[0m" | tee -a host_list.log || \
        echo -e "\E[1;31m$NET.$HOST is down\E[0m"
    }
done
wait

格式2:
双小括号方法,即((…))格式,也可以用于算术运算,双小((括号方法也可以便于 bash Shell 实现C语言风格的变量操作
for(( i=1;i<=10;i++ ))
Shell 脚本基础_第20张图片
范例:九九乘法表

~ vim for-multiplication-table.sh
#!/bin/bash
# 打印九九乘法表
for (( value2=1;value2<=9;value2++ )) ;do
    for (( value1=1;value1<=value2;value1++ )) ;do
        echo -e "$value1*$value2=$(echo "$value1*$value2" | bc)\t\c"
    done
    echo 
done

~ vim for-multiplication-table.sh
#!/bin/bash
# 打印彩色九九乘法表
for (( value2=1;value2<=9;value2++ )) ;do
    for (( value1=1;value1<=value2;value1++ )) ;do
        echo -e "\E[1;$[ $RANDOM%7+31 ]m$value1*$value2=$(echo "$value1*$value2" | bc)\E[0m\t\c"
    done
    echo 
done

### printf 实现九九乘法表
~ vim for-multiplication-table.sh
#!/bin/bash
for (( i=1;i<=9;i++ ));do
  for (( j=1;j<i;j++ ));do
    printf "%sx%s=%s\t" $j $i $[i*j]
    [ $i -eq $j ] && break
   done
   printf "\n"
done

### 倒装的九九乘法表
~ vim for-multiplication-table-flip.sh
#!/bin/bash
# 倒装的九九乘法表
value2=1
for value1 in {1..9};do
    for value2 in $(seq `echo $[ 10-$value1 ]`);do
        echo -ne "$value2*`echo $[10-value1]`=$[ $[10-value1]*$value2 ]\t"
    done
    echo
done

~ vim for-multiplication-table-flip.sh
#!/bin/bash
# 倒装的九九乘法表
for (( value1=1;value1<=9;value1++ ));do
  for (( value2=1;value2<=$[ 10-value1 ];value2++ ));do
    echo -ne "$value2*`echo $[10-value1]`=$[ $[10-value1]*$value2 ]\t"
  done
  echo
done

范例:字符串还原

#编写脚本,后续六个字符串,efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63 是通过对随机数变量RANDOM随机执行命令:echo $RANDOM| md5sum |cut -c1-10后的结果,请破解这些字符串对应的RANDOM值
~ vim for_random.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
ENDCOLOR="\E[0m"

for (( i=0 ; i<=32767 ; i++ )) ;do
KEY=$(echo $i | md5sum | cut -c 1-10)
case $KEY in
efbaf275cd)
    ${BASECOLOR}"The $KEY 对应的RANDOM值 : $i"${ENDCOLOR}
    let i++
;;
4be9c40b8b)
    ${BASECOLOR}"The $KEY 对应的RANDOM值 : $i"${ENDCOLOR}
    let i++
;;
44b2395c46)
    ${BASECOLOR}"The $KEY 对应的RANDOM值 : $i"${ENDCOLOR}
    let i++
;;
f8c8873ce0)
    ${BASECOLOR}"The $KEY 对应的RANDOM值 : $i"${ENDCOLOR}
    let i++
;;
b902c16c8b)
    ${BASECOLOR}"The $KEY 对应的RANDOM值 : $i"${ENDCOLOR}
    let i++
;;
ad865d2f63)
    ${BASECOLOR}"The $KEY 对应的RANDOM值 : $i"${ENDCOLOR}
    let i++
;;
*)
    let i++
;;
esac
done
4.2.2.1 练习
# 1.判断 /var/ 目录下所有文件的类型
# 2.添加10个用户user1-user10,密码为8位随机的字符
# 3./etc/rc.d/rc3.d 目录下分别有多个以 K 开头和以 S 开头的文件;分别读取每个文件,以 K 开头的输出为文件加 stop,以 S 开头的输出为文件名加 start,如 K34filename stop S66filename start
# 4.编写脚本,提示输入正整数n的值,计算1+2+……+n的总和
# 5.计算100以内所有能被3整除之和
# 6.编写脚本,提示输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态
# 7.打印九九乘法表
# 8.在/testdir 目录下创建 10个html文件,文件名格式为数字N(从1到10)加随机8个字母,如1AbCdeFgH.html
# 9.打印等腰三角形
# 10.猴子第一天摘下若干个桃子,当即吃了一半,还不够,有多吃了一个,第二天早上又将剩下的桃子吃掉一半,又多了一个,以后每天早上都吃了前一天剩下的一半零一个,到了第10天早上想再吃时,就只剩一个桃子,求第一天共摘了多少?
# 1.判断 /var/ 目录下所有文件的类型
~ vim check_varfile.sh
#!/bin/bash
# Determine the types of all files in the /var/ directory

# SHELL ENV
BASECOLOR="\E[1;36m"
WARNCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"

VARLIST=`find /var -maxdepth 1 -mindepth 1`

for FILE in ${VARLIST} ;do
    if [ -b $FILE ] ;then
        echo -e "${BASECOLOR}${FILE} is block special.${ENDCOLOR}"
    elif [ -c $FILE ] ;then
        echo -e "${BASECOLOR}${FILE} is character special.${ENDCOLOR}"
    elif [ -L $FILE ] ;then
        echo -e "${BASECOLOR}${FILE} is symbolic link.${ENDCOLOR}"
    elif [ -p $FILE ] ;then
        echo -e "${BASECOLOR}${FILE} is pipe.${ENDCOLOR}"
    elif [ -S $FILE ] ;then
        echo -e "${BASECOLOR}${FILE} is socket.${ENDCOLOR}"
    elif [ -f $FILE ] ;then
        echo -e "${BASECOLOR}${FILE} is regular file.${ENDCOLOR}"
    elif [ -d $FILE ] ;then
        echo -e "${BASECOLOR}${FILE} is directory.${ENDCOLOR}"
    else
        echo -e "${WARNCOLOR}${FILE} is other file.${ENDCOLOR}"
    fi
done

# 2.添加10个用户user1-user10,密码为8位随机的字符
~ vim create_user.sh
#!/bin/bash
# Create Ten User

# SHELL ENV
BASECOLOR="\E[1;36m"
ENDCOLOR="\E[0m"

# Cycle
for num in {1..10} ;do
    id user$num &> /dev/null && { echo "The user${num} is existed";exit; } || { echo "The user${num} is create"; }
    useradd user${num}
    PASSWORD=`cat /dev/random | tr -dc [[:alnum:]] | head -c8`
    # CentOS
    echo $PASSWORD | passwd --stdin user${num} &> /dev/null
    echo "user${num}:${PASSWORD}" >> /data/user-password.txt
done

# 3./etc/rc.d/rc3.d 目录下分别有多个以 K 开头和以 S 开头的文件;分别读取每个文件,以 K 开头的输出为文件加 stop,以 S 开头的输出为文件名加 start,如 K34filename stop S66filename start
~ vim for_rc_d.sh
#!/bin/bash
set -ue

read -p "Please input Your rc.d DIR Path:" DIR

if [ -d $DIR ] ;then
        # if [[ $DIR =~ (^(/etc/)|(rc.d/rc[0-9].d/)(rc[0-9].d)$) ]] ;then
        if [[ $DIR == /etc/rc* ]] ;then
                cd $DIR
                for FILE in * ;do
                        if [[ $FILE =~ (^S.*$) ]] ;then
                                echo "$FILE start "
                        elif [[ $FILE =~ (^K.*$) ]] ;then
                                echo "$FILE stop "
                        fi
                done
        else
                echo -e "Input Error"
        fi
else
        echo -e "Input Error"
fi

~ vim relist_rc.sh
#!/bin/bash
# Each file is read with stop for output starting with K and
# start for filename for output starting with S

# SHELL ENV
BASECOLOR="\E[1;36m"
ENDCOLOR="\E[0m"
DIR="/etc/rc.d/rc3.d"

cd $DIR
FILENAME=`find -maxdepth 1 -mindepth 1 | cut -d"/" -f2`

for name in ${FILENAME} ;do
    if [[ ${name} =~ ^K.* ]] ;then
        echo "${name} stop"
    elif [[ ${name} =~ ^S.* ]] ;then
        echo "${name} start"
    fi
done

# 4.编写脚本,提示输入正整数n的值,计算1+2+……+n的总和
~ vim input_int_number.sh
#!/bin/bash
# Enter the value of a positive integer,Calculate the total

# SHELL ENV
BASECOLOR="\E[1;36m"
ENDCOLOR="\E[0m"

read -p "Please input integer number(Calculate the total):" NUMBER
if [ ${NUMBER} -gt 0 ] && [[ ${NUMBER} =~ ^([0-9]+)$ ]] ;then
    echo -e "${BASECOLOR}Calculate the total:$( echo `seq -s'+' ${NUMBER} | bc` )${ENDCOLOR}"
else
    echo -e "\E[1;31mError.Please input integer number\E[0m"
fi

# 5.计算100以内所有能被3整除之和
~ vim 100%3.sh
#!/bin/bash
# Calculate the sum of all divisible by 3 up to 100

# SHELL ENV
BASECOLOR="\E[1;46m"
WARNINGCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"
SUM=0

for number in {1..100} ;do
    NUM=`echo "$number%3" | bc`
    [ $NUM -eq 0 ] &&  SUM=$[ $number+$SUM ]
done
echo -e "${BASECOLOR}The sum(100%3):${SUM}${ENDCOLOR}"

# 6.编写脚本,提示输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态
~ vim hostping_read.sh
#!/bin/bash
# Accepts the IPv4 address of a host as a parameter to test whether the host is reachable

# set -ue
# SHELL ENV
BASECOLOR="\E[1;46m"
WARNINGCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"
# IP=${1}
# [ $# -eq 0 ] && { echo "Usage: $0 ";exit 10; }
read -p "Please input ipv4 address:" IP

if [[ ${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])$ ]] ;then
    echo -e "${BASECOLOR}${IP} is valid${ENDCOLOR}"
    echo -e "${BASECOLOR}Waiting...Script running...${ENDCOLOR}"
else
    echo -e "${BASECOLOR}${IP} is invalid${ENDCOLOR}"
    exit
fi

if $(ping -c1 -W1 ${IP} &> /dev/null) ;then
    echo -e "${BASECOLOR}The IP address is accessible${ENDCOLOR}"
    exit
else
    echo -e "${WARNINGCOLOR}The IP address is not accessible${ENDCOLOR}"
    exit
fi

# 7.打印九九乘法表
~ vim for-multiplication-table.sh
#!/bin/bash
# Print the multiplication table

# SHELL ENV
RANDOMCOLOR="\E[1;$[ `echo "$RANDOM%7+31"|bc` ]m"
ENDCOLOR="\E[0m"

# Judge
for VALUE1 in `seq 9` ;do
    for VALUE2 in `seq $VALUE1` ;do
        echo -ne "\E[1;$[ `echo "$RANDOM%7+31"|bc` ]m${VALUE1}*${VALUE2}=$(echo "${VALUE1}*${VALUE2}"|bc)${ENDCOLOR}\t\c"
    done
    printf "\n"
done

# 8./testdir 目录下创建 10个html文件,文件名格式为数字N(110)加随机8个字母,如1AbCdeFgH.html
~ vim create_testhtml.sh
#!/bin/bash
# Create 10 HTML files in the /testdir directory

# SHELL ENV
BASECOLOR="\E[1;36m"
ENDCOLOR="\E[0m"
#RANDOMFILE=`cat /dev/random | tr -dc "[[:alpha:]]" | head -c8`
DIR="/testdir"
mkdir -p $DIR; cd $DIR
echo -e "${BASECOLOR}Create file starting...waiting...${ENDCOLOR}"
for FILE in {1..10} ;do
    touch ${FILE}$(cat /dev/random | tr -dc "[[:alpha:]]" | head -c8).html
done
echo -e "${BASECOLOR}Create file ending...${ENDCOLOR}"

# 9.打印等腰三角形
~ vim triangle.sh
#!/bin/bash
read -p "please input line number: " LINE
for((i=1;i<=LINE;i++));do
        for((j=1;j<=LINE-i;j++));do
                echo -e " \c"
        done
        for((k=1;k<=i*2-1;k++));do
                echo -e "\e[$[RANDOM%7+31]m*\c\e[0m"
        done
        echo
done

# 10.猴子第一天摘下若干个桃子,当即吃了一半,还不够,有多吃了一个,
# 第二天早上又将剩下的桃子吃掉一半,又多了一个,
# 以后每天早上都吃了前一天剩下的一半零一个,
# 到了第10天早上想再吃时,就只剩一个桃子,求第一天共摘了多少?
~ vim playgame.sh
#!/bin/bash
for((i=9,sum=0;i>=1;i--))
do
		sum=$[$sum*2+2]
done
echo sum=$sum

4.2.3 循环 while

格式:

while DONDITION;do
	循环体
done

Shell 脚本基础_第21张图片
说明:
DONDITION:循环控制条件;进入循环之前,先做一次判断,每一次循环之后会再次做判断;条件为“true”,则执行一次循环,直到条件测试状态为“false”终止循环,因此,DONDITION 一般应该有循环控制变量;而此变量的值会在循环体不断的被修正。
进入条件:DONDITION 为 true
退出条件:DONDITION 为 false
范例:while 的帮助用法

➜  ~ help while
while: while COMMANDS; do COMMANDS; done
    Execute commands as long as a test succeeds.

    Expand and execute COMMANDS as long as the final command in the
    `while' COMMANDS has an exit status of zero.

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

无限循环

while true;do
  循环体
done

while : ;do
  循环体
done

范例:

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

范例:邮件发送磁盘告警消息

#主机需要安装邮件服务
~ yum install -y postfix mailx

~ cat > .mailrc <<-'EOF'
# 从哪发送邮件
set [email protected]
# 使用哪种邮件服务器进行发送
set smtp=smtp.qq.com
# 使用哪种身份进行发送
set [email protected]
# 该该身份的验证码
set smtp-autp-password=yxmomqzszmblbcad
# 使用的登录方式
set smtp-auto=login
set ssl-verify=ignore
EOF

~ vim while_diskcheck.sh
#!/bin/bash
#Script for checking disk utilization

WARNING=80
while : ;do
    USE=$(df | sed -nr '/^\/dev/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" 1281570031@qq.com
    fi
    sleep 10
done

范例:防止Dos攻击的脚本

~ vim check-link.sh
#!/bin/bash
#A script to prevent Dos attacks

WARNING=10
touch deny_hosts.txt

while true;do
    ss -nt | sed -nr '1!s#.* ([0-9]+):[0-9]+ *#\1#p' | sort | uniq -c | sort | while read count ip;do
        if [ $count -gt $WARNING ];then
            echo $ip is deny
            grep -q "$ip" deny_hosts.txt || { echo $ip >> deny_hosts.txt; iptables -A INPUT -s $ip -j REJECT; }
        fi
    done
    sleep 10
done
4.2.3.1 练习

用 while 实现以下练习

# 1.编写脚本,求 100 以内的正奇数之和。
# 2.编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各是多少。
# 3.编写脚本,打印九九乘法表
# 4.编写脚本,利用变量RANDOM生成10个随机数字,输出这10个数字,并显示其中的最大值和最小值
# 5.编写脚本,实现打印国际象棋棋盘
# 6.编写脚本,后续六个字符串,efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63 是通过对随机数变量RANDOM随机执行命令:echo $RANDOM| md5sum |cut -c1-10后的结果,请破解这些字符串对应的RANDOM值
# 1.编写脚本,求 100 以内的正奇数之和。
~ vim while-odd-number.sh
#!/bin/bash
#Find the sum of the positive odd numbers up to 100.

#SHELL ENV
BASECOLOR="\E[1;32m"
ENDCOLOR="\E[0m"
declare -i ODD_SUM=0
ODD_NUM=1

while (( ${ODD_NUM}<=100 )); do
    if [ $[$ODD_NUM%2] -ne 0 ]; then
        ODD_SUM=$[ ODD_SUM+ODD_NUM ]
    fi
    #ODD_NUM=$[ ODD_NUM+1 ]
    let ODD_NUM++
    #echo ${ODD_NUM}
done
echo -e "${BASECOLOR}100 ODD_SUM is ${ODD_SUM}${ENDCOLOR}"
~ chmod +x while-odd-number.sh
~ bash while-odd-number.sh
100 ODD_SUM is 2500

# 2.编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各是多少。
~ vim while-networkcheck.sh
#!/bin/bash
#Enter a network address,
#for example, 192.168.0.0. Determine the online
#status of hosts in the entered network segment and
#count the number of online and offline hosts respectively.

#SHELL ENV
BASECOLOR="\E[1;32m"
WARNCOLOR="\E[1;31m"
ENDCOLOE="\E[0m"
UP_IP=0
DOWN_IP=0
HOST_1=0
HOST_2=0
HOST_3=0

function END()
{
echo -e "${BASECOLOR}The UP_IP Sum is ${1}${ENDCOLOE}"
echo -e "${WARNCOLOR}The UP_IP Sum is ${2}${ENDCOLOE}"
}

read -p "Please input network address(网络地址) : " NET
[[ $NET =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-9])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]] && \
echo "$NET is valid network address" || \
{ echo "$NET is unvalid network address";exit; }
#Take network head address
NET_HEAD=$(echo ${NET%%.*})
#echo "${NET_HEAD}"

while true;do
    if [ ${NET_HEAD} -ge 1 -a ${NET_HEAD} -le 126 ];then
        echo -e "${BASECOLOR}${NET} is Class A address${ENDCOLOE}"
        NET_NET=$(echo ${NET:0:3})
        while [ $HOST_1 -le 255 ];do
            while [ $HOST_2 -le 255 ];do
                while [ $HOST_3 -le 254];do
                    if ping -c1 -W1 ${NET_NET}.${HOST_1}.${HOST_2}.${HOST_3} &> /dev/null;then
                        echo -e "${BASECOLOR}The ${NET_NET}.${HOST_1}.${HOST_2}.${HOST_3} is up${ENDCOLOE}"
                        let UP_IP++
                    else
                        echo -e "${BASECOLOR}The ${NET_NET}.${HOST_1}.${HOST_2}.${HOST_3} is down${ENDCOLOE}"
                        let DOWN_IP++
                    fi
                    let HOST_3++
                done
                let HOST_2++
            done
            let HOST_1++
        done
        END ${UP_IP} ${DOWN_IP}
        break
    elif [ ${NET_HEAD} -ge 128 -a ${NET_HEAD} -le 191 ];then
        echo -e "${BASECOLOR}${NET} is Class B address${ENDCOLOE}"
        NET_NET=$(echo ${NET:0:6})
        while [ $HOST_2 -le 255 ];do
            while [ $HOST_3 -le 254 ];do
                if ping -c1 -W1 ${NET_NET}.${HOST_2}.${HOST_3} &> /dev/null;then
                    echo -e "${BASECOLOR}The ${NET_NET}.${HOST_2}.${HOST_3} is up${ENDCOLOE}"
                    let UP_IP++
                else
                    echo -e "${ENDCOLOE}The ${NET_NET}.${HOST_2}.${HOST_3} is down${ENDCOLOE}"
                    let DOWN_IP++
                fi
                let HOST_3++
            done
            let HOST_2++
        done
        END ${UP_IP} ${DOWN_IP}
        break
    elif [ ${NET_HEAD} -ge 192 -a ${NET_HEAD} -le 223 ];then
        echo -e "${BASECOLOR}${NET} is Class C address${ENDCOLOE}"
        NET_NET=$(echo ${NET%.*})
        while [ $HOST_3 -le 254 ];do
            if ping -c1 -W1 ${NET_NET}.${HOST_3} &> /dev/null;then
                echo -e "${BASECOLOR}The ${NET_NET}.${HOST_3} is up${ENDCOLOE}"
                let UP_IP++
            else
                echo -e "${ENDCOLOE}The ${NET_NET}.${HOST_3} is down${ENDCOLOE}"
                let DOWN_IP++
            fi
            HOST_3=$[HOST_3+1]
        done
        END ${UP_IP} ${DOWN_IP}
        break
    else
        echo -e "${WARNCOLOR}${NET} cannot be used as an address condition for judgment${ENDCOLOE}"
        break
    fi
done

~ vim while-networkcheck.sh
#!/bin/bash
IP=192.168.0

i=1
UP=0
DOWN=0
while [ $i -le 254 ] ;do
  if ping -c1 -W1 ${IP}.$i &> /dev/null;then
    echo -e "\e[1;32m${IP}.${i} is up.\e[0m"
    let UP++
  else
    echo -e "\e[1;31m${IP}.${i} is down.\e[0m"
    let DOWN++
  fi
  let i++
done
echo -e "\e[1;34mUP is $UP.\e[0m"  
echo -e "\e[1;35mDOWN is $DOWN.\e[0m"

# 3.编写脚本,打印九九乘法表
~ vim while-multiplication-table.sh
#!/bin/bash
# 打印九九乘法表

#SHELL ENV
BASECOLOR="\E[1;32m"
ENDCOLOR="\E[0m"
declare -i value1=1 value2=1
#declare -i SUM=0

while (( $value1<=9 ));do
    value2=1
    while (( $value2<=$value1 ));do
        echo -e "${BASECOLOR}${value1}*${value2}=$(echo $value1*$value2|bc)${ENDCOLOR}\t\c"
        let value2++
    done
    let value1++
    echo
done

# 4.编写脚本,利用变量RANDOM生成10个随机数字,输出这10个数字,并显示其中的最大值和最小值
~ vim while-random.sh
#!/bin/bash
#Use variable RANDOM to generate 10 RANDOM numbers,
#print the 10 numbers, and display the maximum and minimum values among them

declare -i min max
i=1

while (( i<=10 ));do
    num=$(echo $RANDOM)
    all+="${num} "
    [ $i -eq 1 ] && min=$num && max=$num && let i++ && continue
    [ $num -gt $max ] && max=$num ; let i++ && continue
    [ $num -lt $min ] && min=$num ; let i++ && continue || { exit; }
done

#字符串追加
echo "All numbers are ${all}"
echo "Max is $max"
echo "Min is $min"

~ vim while-random.sh
i=1
while true;do
  NUM=$RANDOM
  if [ "$i" -eq "1" ];then
    MAX=$NUM
    MIN=$NUM
  else  
    if [ "$MAX" -lt "${num[$i]}" ];then
      MAX=${num[$i]}
    elif [ "$MIN" -gt "${num[$i]}" ];then
      MIN=${num[$i]}
    else
      true
    fi
  fi
  let i++
done
echo "num is : ${num[@]}"
echo "最大值MAX:$MAX 最小值MIN:$MIN"

# 5.编写脚本,实现打印国际象棋棋盘
~ vim while-chessboard.sh
#!/bin/bash

#set chess cell's width
read -p "Please set the chess cell's width( two space width as unit ):" width
if [[ $width =~ "^[0-9]+$" ]];then
    echo "wrong width setting, check your input and try again."
    exit
fi
let width=$width*2

#choose player's board cell color
player="player1"
PS3="Which color do you want to set for $player :"
select choice in red green yellow blue purple cyan white;do
    case $REPLY in
    [1-7])
    if [[ $player == player2 ]];then
        declare -i color2=$REPLY
        break
    else
        declare -i color1=$REPLY
    fi
    player="player2"
    PS3="Which color do you want to set for $player :"
    ;;
    *)
    ;;
    esac
done
if (( color1==color2 ));then
    echo "two player must choose different color, check your choice and try again."
    exit
fi

#print the chess board
for (( i=0; i<4; i++ )); do
    for (( j=0; j<$width/2; j++ ));do
        for (( k=0; k<4; k++ ));do
            echo -e "\e[4${color1}m$(printf %${width}s)\e[0m\c"
            echo -e "\e[4${color2}m$(printf %${width}s)\e[0m\c"
    done
    echo
done
    for (( j=0; j<$width/2; j++ ));do
        for (( k=0; k<4; k++ ));do
            echo -e "\e[4${color2}m$(printf %${width}s)\e[0m\c"
            echo -e "\e[4${color1}m$(printf %${width}s)\e[0m\c"
        done
    echo
    done
done

# 6.编写脚本,后续六个字符串,efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63是通过对随机数变量RANDOM随机执行命令:echo $RANDOM| md5sum |cut -c1-10后的结果,请破解这些字符串对应的RANDOM值
~ vim while_random.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
ENDCOLOR="\E[0m"

num=1
while true ; do
RAN=$(echo $num | md5sum | cut -c 1-10)
case $RAN in
efbaf275cd)
    ${BASECOLOR}"The efbaf275cd 对应的RANDOM值 : $num"${ENDCOLOR}
    let num++
;;
4be9c40b8b)
    ${BASECOLOR}"The 4be9c40b8b 对应的RANDOM值 : $num"${ENDCOLOR}
    let num++
;;
44b2395c46)
    ${BASECOLOR}"The 44b2395c46 对应的RANDOM值 : $num"${ENDCOLOR}
    let num++
;;
f8c8873ce0)
    ${BASECOLOR}"The f8c8873ce0 对应的RANDOM值 : $num"${ENDCOLOR}
    let num++
;;
b902c16c8b)
    ${BASECOLOR}"The b902c16c8b 对应的RANDOM值 : $num"${ENDCOLOR}
    let num++
;;
ad865d2f63)
    ${BASECOLOR}"The ad865d2f63 对应的RANDOM值 : $num"${ENDCOLOR}
    let num++
;;
*)
    let num++
;;
esac
done

~ vim while_read_random.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
ENDCOLOR="\E[0m"

echo -e "efbaf275cd\n4be9c40b8b\n44b2395c46\nf8c8873ce0\nb902c16c8b\nad865d2f63" | while read RAD ;do
    NUM=1
    while true ;do
        KEY=$(echo $NUM | md5sum | cut -c 1-10)
        if [ "$KEY" = "$RAD" ] ;then
            ${BASECOLOR}"The $RAD 对应的RANDOM值 : $NUM"${ENDCOLOR}
            let NUM++
            break
        else 
            let NUM++
        fi
    done
done

4.2.4 循环 until

格式:

until DONDITION;do
	循环体
done

范例:查看循环 until 帮助

~ help until

Shell 脚本基础_第22张图片
说明:
进入条件:DONDITION 为 false
退出条件:DONDITION 为 true
无限循环

until false;do
	循环体
done

范例:

~ sum=0;i=1;until ((i>100));do sum=$[sum+i];let i++;done;echo $sum
5050

4.2.5 循环控制语句 continue

continue 退出本轮循环
continue [N]:提前结束第N层循环,而直接进入下一轮判断:最内层为第 1 层;
continue [N]:N代表退出的第几层循环。例如:continue 2 就代表退出最内层外的一层的循环。
例如:当有同学上课期间身体不佳,上课上到一半,就要结束当天的课程,提前回家休息(结束当前循环);在家里休息一晚上后,就恢复完毕,第二天可以继续上课了(继续循环)。
格式:

while CONDITION1 ;do
	CMD1
	...
	if CONDITION2 ;then
		continue
	fi
	CMDn
	...
done

范例:计算100以内的偶数的总和(2+4+6+…+98)

~ seq -s "+" 2 2 100 | bc
2550
~ echo {0..100..2} | tr " " + | bc
2550

#使用while循环计算100以内偶数之和
~ vim continue-while-evensum.sh
#!/bin/bash
#Calculate the sum of even numbers up to 100 (2+4+6+... + 98).
sum=0
i=1
#while [ $i -le 100 ];do
while (( $i <= 100 ));do
    if [ $[ $i%2 ] -eq 1 ];then
        i=$[i+1]
        continue
    fi
    sum=$[ $sum+$i ]
    i=$[i+1]
done
echo "Sum of even numbers up to 100: $sum"

#使用for循环计算100以内偶数之和
~ vim continue-for-evensum.sh
#!/bin/bash
#Calculate the sum of even numbers up to 100 (2+4+6+... + 98).

for (( i=1,sum=0;i<=100;i++ ));do
    if [ $[ $i%2 ] -eq 1 ];then
        continue
    fi
    sum=$[ $sum+$i ]
done
echo "Sum of even numbers up to 100: $sum"

范例:

~ vim continue-for.sh
#!/bin/bash

for (( i=0;i<=10;i++ ));do
    if [ $i -eq 5 ];then
        continue
    fi
    echo i=$i
done

范例:退出第n层循环

~ cat continue-for.sh
#!/bin/bash
for (( j=0;j<=10;j++ ));do
    for (( i=0;i<=10;i++ ));do
        if [ $i -eq 5 ];then
            continue 2
        fi
        echo i=$i---$j
    done
    echo "----------"
done

4.2.6 循环控制语句 break

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

while CONDITION1 ;do
	CMD1
	...
	if CONDITION2 ;then
		break
	fi
	CMDn
	...
done

范例:

~ vim break-for.sh
#!/bin/bash

for (( i=0;i<=10;i++ ));do
    for (( j=0;j<=10;j++ ));do
        [ $j -eq 5 ] && break
        echo $j
    done
    echo -e "-------"
done

~ vim break-for.sh
#!/bin/bash

for (( i=0;i<=10;i++ ));do
    if [ $i -eq 5 ];then
        break
    fi
    echo i=$i
done

范例:退出第n层循环

~ cat break-for.sh
#!/bin/bash
for (( j=0;j<=10;j++ ));do
    for (( i=0;i<=10;i++ ));do
        if [ $i -eq 5 ];then
            break
        fi
        echo i=$i---$j
    done
    echo "----------"
done

~ cat break-for.sh
#!/bin/bash
for (( j=0;j<=10;j++ ));do
    for (( i=0;i<=10;i++ ));do
        if [ $i -eq 5 ];then
            break 2
        fi
        echo i=$i---$j
    done
    echo "----------"
done

4.2.7 循环控制语句 shift 命令

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

#!/bin/bash
# Name:doit.sh
# Purpose: shift throught command line argument
# Usage: doit.sh [args] 
while [ $You can't use 'marco parameter character #' in math mode# -gt 0] # (( $# > 0))
do 
	echo $*
	shift
done

./doit.sh a b c d e f g h


~ cat shift-for-test.sh
#!/bin/bash
#Test the Shift loop control statement script
echo $*
shift
echo "shift"
echo $*
shift
echo "shift"
echo $*
#执行结果
~ bash shift-for-test.sh a b c d e
a b c d e
shift
b c d e
shift
c d e

范例:

~ vim shift.sh
#!/bin/bash
#Step through all the positional parameters
until [ -z "$1" ]
do
  echo "$1"
  shift
done
echo

~ ./shift.sh a b c d e f g h

范例:批量创建用户

~ vim shift-batch-useradd.sh
#!/bin/bash
#Create users in batches using Shift
#Shell ENV
BASECOLOR="\E[1;32m"
ENDCOLOR="\E[0m"
WARNCOLOR="\E[1;5;31m"

#Determines whether the position parameter is zero
if [ $# -eq 0 ];then
    echo "Usage: `basename $0` user1 user2 user3 ..."
    exit
fi

#The loop body
while [ "$1" ] ;do
    if id $1 &> /dev/null ; then
        echo -e "${WARNCOLOR}$1 is exist...${ENDCOLOR}"
    else
        useradd $1
        echo -e "${BASECOLOR}$1 is created...${ENDCOLOR}"
    fi
    shift
done
echo -e "${BASECOLOR}All user is created${ENDCOLOR}"

~ bash shift-batch-useradd.sh
Usage: shift-batch-useradd.sh user1 user2 user3 ...
~ bash shift-batch-useradd.sh xiaoliang xiaoming
xiaoliang is created...
xiaoming is exist...
All user is created

~ vim for-batch-useradd.sh
#!/bin/bash
#Create users in batches using Shift
#Shell ENV
BASECOLOR="\E[1;32m"
ENDCOLOR="\E[0m"
WARNCOLOR="\E[1;5;31m"

#Determines whether the position parameter is zero
if [ $# -eq 0 ];then
    echo "Usage: `basename $0` user1 user2 user3 ..."
    exit
fi

#The loop body
for user in $* ;do
    if id $user &> /dev/null ; then
        echo -e "${WARNCOLOR}$user is exist...${ENDCOLOR}"
    else
        useradd $user
        echo -e "${BASECOLOR}$user is created...${ENDCOLOR}"
    fi
done
echo -e "${BASECOLOR}All user is created${ENDCOLOR}"
4.2.7.1 练习
# 1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并退出脚本
# 2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出
# 3、用文件名做为参数,统计所有参数文件的总行数
# 4、用二个以上的数字为参数,显示其中的最大值和最小值
# 1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并退出脚本
➜  ~ cat while_login.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
ENDCOLOR="\E[0m"

while true ; do
    RESULT=`w | { grep -q "hacker"; echo $?; }`
    if [ $RESULT -eq 0 ] ;then
        who | awk 'BEGIN{print "User   Login time"}/hacker/{print $1","$3}' >> /var/log/login.log
        cowsay "用户hacker已经登录该系统,登录日志在/var/log/login.log"
        break
    else
        w ; cowsay "休息3秒钟"
        sleep 3
    fi
done

# 2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出
➜  ~ vim shift_guess.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
ENDCOLOR="\E[0m"
declare -i GUESS_NUM=$(echo "$RANDOM%9+1"|bc)

#Determines whether the position parameter is zero
if [ $# -eq 0 ];then
    echo "Usage: `basename $0` number1 number2 number3 [...]"
    exit
fi

while [ "$1" ] ;do
    if [ "${1}" -lt ${GUESS_NUM} ] ;then
        ${BASECOLOR}"数字猜的偏小"${ENDCOLOR}
    elif [ "${1}" -gt ${GUESS_NUM} ] ;then
        ${BASECOLOR}"数字猜的偏大"${ENDCOLOR}
    else
        ${BASECOLOR}"猜中大奖数字:${GUESS_NUM}"${ENDCOLOR}
        ${BASECOLOR}"数字猜中啦,恭喜你脱离苦海"${ENDCOLOR}
        break
    fi
    shift
done

➜  ~ vim while_guess.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
ENDCOLOR="\E[0m"
declare -i GUESS_NUM=$(echo "$RANDOM%9+1"|bc)

while true ; do
    read -p "请输入一个10之内的数字进行比较:" NUM
    if [ "${NUM}" -lt ${GUESS_NUM} ] ;then
        ${BASECOLOR}"数字猜的偏小"${ENDCOLOR}
    elif [ "${NUM}" -gt ${GUESS_NUM} ] ;then
        ${BASECOLOR}"数字猜的偏大"${ENDCOLOR}
    else
        ${BASECOLOR}"猜中大奖数字:${GUESS_NUM}"${ENDCOLOR}
        ${BASECOLOR}"数字猜中啦,恭喜你脱离苦海"${ENDCOLOR}
        break
    fi
done

# 3、用文件名做为参数,统计所有参数文件的总行数
➜  ~ vim shift_filename.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
ENDCOLOR="\E[0m"
declare -i SUM=0

#Determines whether the position parameter is zero
if [ $# -eq 0 ];then
    echo "Usage: `basename $0` Filename1 Filename2 Filename3 [...]"
    exit
fi

#The loop boby
while [ "$1" ] ;do
    if [ -f $1 ] ;then
        LINE=`wc -l ${1} | awk '{print $1}'`
        SUM=$[ $SUM+$LINE ]
    else
        echo -e "The Filename is not exists"
    fi
    shift
done
${BASECOLOR}The parameter Filename line is ${SUM}${ENDCOLOR}

# 4、用二个以上的数字为参数,显示其中的最大值和最小值
➜  ~ vim shift_max_min.sh
#!/bin/bash

#Determines whether the position parameter is zero
if [ $# -eq 0 ];then
    echo "Usage: `basename $0` number1 number2 number3 [...]"
    exit
fi

while [ "$1" ] ;do
    echo $1 >> ./num.txt
    shift
done

#echo "MAX:`cat ./num.txt|sort -nr|head -1`,MIN:`cat ./num.txt|sort -nr|tail -n1`"
cowsay "数值的最大值为:`cat ./num.txt|sort -nr|head -n1`"
cowsay "数值的最小值为:`cat ./num.txt|sort -nr|tail -n1`"
rm -f ./num.txt

4.2.8 while 特殊用法 while read

while read循环的特殊用法,遍历文件或者文本的每一行;实现文本的逐行处理。
支持标准输入,就可以使用管道。一旦涉及到脚本中的逐行处理就要使用 while read
格式:

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

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

#管道前后会开启子进程
~ echo kubesphere | read X ; echo $X

~ echo kubesphere | while read X ; do echo $X ;done
kubesphere
~ echo kubesphere | { read X ; echo $X; }
kubesphere
~ echo kubesphere | ( read X ; echo $X )
kubesphere
~ echo kubesphere kubernetes kubeedge | while read X Y Z ; do echo $X $Y $Z ;done
kubesphere kubernetes kubeedge

~ while read line ;do echo $line ;done
~ while read line ;do echo $line >> /data/while-read.txt ;done
#标准输入重定向实现逐行处理
~ while read line ;do echo $line ;done < /etc/issue
#管道实现实现逐行处理
~ cat /etc/issue | while read line ;do echo $line ;done

范例:分区利用率处理

#获取分区和各自分区的利用率
df | sed -nr '/^\/dev/s#([^[:space:]]+) .* ([0-9]+)%.*#\1 \2#p'

~ vim while-read-diskcheck.sh
#!/bin/bash
#SHELL ENV
WARNING=80

#The loop body
while true; do
    df | sed -nr '/^\/dev/s#([^[:space:]]+) .* ([0-9]+)%.*#\1 \2#p' | while read DEVICE USE ;do
        if [ $USE -ge $WARNING ] ;then
            echo "$DEVICE will be full,Use: $USE!" | mail -s "Disk Warning" 1281570031@qq.com
        fi
    done
    sleep 10
done

~ vim while-read-diskcheck.sh
#!/bin/bash
#SHELL ENV
WARNING=80
MAIL="[email protected]"

df | sed -nr '/^\/dev/s#([^[:space:]]+) .* ([0-9]+)%.*#\1 \2#p' | while read DEVICE USE ;do
  if [ $USE -ge $WARNING ] ;then
            echo "$DEVICE will be full,Use: $USE!" | mail -s "Disk Warning" $MAIL
  fi
done

~ while-read-checkdos.sh
#!/bin/bash
#SHELL ENV
MAX=3

lastb | sed -nr '/ssh:/s@.* ([0-9.]{1,3}{3}[0-9]{1,3}) .*@\1@p' | sort | uniq -c | while read count ip ;do
  if [ $count -gt $MAX ];then
    iptables -A INPUT -s $ip -j REJECT
  fi
done

范例:考试成绩

#准备工作
~ tee > score.txt <<EOF
xiaoming:88
xiaohong:90
xiaolan:70
xiaoqiang:92
EOF

~ vim while-read-score.sh
#!/bin/bash

cat score.txt | tr : ' ' | while read name score ;do
    if [ $score -ge 90 ] ;then
        echo "$name is Good"
    else
        echo "$name is Low"
    fi
done
~ bash while-read-score.sh
xiaoming is Low
xiaohong is Good
xiaolan is Low
xiaoqiang is Good

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

#方法一:
~ vim while-read-passwd.sh
#!/bin/bash
#View the user name and UserID of UserShell type in /sbin/nologin

cat /etc/passwd | cut -d":" -f1,3,7 | tr : " " | while read USER UserID UserShell;do
    if [ $UserShell = '/sbin/nologin' ];then
        echo "User: $USER , UserID: $UserID"
    fi
done

#方法二:
~ vim while-read-passwd.sh
#!/bin/bash
#View the user name and UserID of UserShell type in /sbin/nologin

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

4.2.9 循环和菜单 select

格式:

#查看select帮助用法
~ help select 

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

说明:
提示符基本概念

#PS1 提示符:命令提示符
~ echo $PS1
\[\e[1;31m\][\u@\h \W]\$\[\e[0m\]

#PS2 提示符:cat <
~ echo $PS2
>
~ PS2=###
~ cat <<EOF
###a
###b
###c
EOF

#PS3 提示符:select 循环中的提示符

范例:运维菜单

~ vim select-linux-test.sh
#!/bin/bash

PS3="请选择功能 (1-5) :"
select menu in 备份数据库 清理日志 升级软件 降级软件 删库跑路;do
    case $REPLY in
    1)
        echo $menu
    ;;
    2)
        echo $menu
    ;;
    3)
        echo $menu
    ;;
    4)
        echo $menu
    ;;
    5)
        echo $menu
        break
    ;;
    *)
        echo "请输入功能 (1-5)"
    ;;
    esac
done

~ vim select-linux-test.sh
#!/bin/bash

name="
备份数据库
清理日志
升级软件
降级软件
删库跑路
"

PS3="请选择功能 (1-5) :"
select menu in $name;do
    case $REPLY in
    1)
        echo $menu
    ;;
    2)
        echo $menu
    ;;
    3)
        echo $menu
    ;;
    4)
        echo $menu
    ;;
    5)
        echo $menu
        break
    ;;
    *)
        echo "请输入功能 (1-5)"
    ;;
    esac
done

范例:菜单系统

~ vim menu.sh
#!/bin/bash
# 设置变量
SUM=0
COLOR='echo -e \033[1;31m'
COLOR_2='echo -e \033[1;32m'
COLOREND='\033[0m'

while true ;do

cat <<EOF
1)鲍鱼
2)满汉全席
3)龙虾
4)螃蟹
5)燕窝
6)退出
EOF
read -p "请输入你要吃的菜单:" OPTIONS
case $OPTIONS in
1|5)
        ${COLOR}这菜的价格为: \$100${COLOREND}
        SUM=$[ SUM+100 ]
        ;;
3|4)
        ${COLOR}这菜的价格为: \$50${COLOREND}
        SUM=$[ SUM+50 ]
        ;;
2)
        ${COLOR}这菜的价格为: \$1000${COLOREND}
        SUM=$[ SUM+1000 ]
        ;;
6)
        ${COLOR_2}你点的菜总价为:${SUM}${COLOREND}
        break
        ;;
*)
        ${COLOR}你的输入有误,请重新输入菜品${COLOREND}
        ;;
esac
${COLOR_2}你点的菜总价为:${SUM}${COLOREND}
done

# 测试
~ menu.sh

5 函数 Function

5.1 函数介绍

函数 function 是由若干条 shell 命令组成的语句块,实现代码重用和模块化编程。
它与 shell 程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是 shell 程序的一部分。
函数和 shell 程序比较相似,区别在于

5.2 管理函数

函数由两部分组成:函数名和函数体
帮助查看:help function

~ help function
function: function name { COMMANDS ; } or name () { COMMANDS ; }
    Define shell function.

    Create a shell function named NAME.  When invoked as a simple command,
    NAME runs COMMANDs in the calling shell's context.  When NAME is invoked,
    the arguments are passed to the function as $1...$n, and the function's
    name is in $FUNCNAME.

    Exit Status:
    Returns success unless NAME is readonly.
~ type function
function is a shell keyword

5.2.1 定义函数

格式:

# 语法一:
func_name (){
	...函数体...
}

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

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

范例:

~ testfunc1() { 
echo testfunc1;
echo functions is excuted; 
}

范例:

#根据操作系统的类型安装httpd
~ vim function-installed-httpd.sh
#!/bin/bash
function os_type(){
#Check the operating system type
if grep -i -q ubuntu /etc/os-release ;then
    echo "ubuntu"
elif grep -i -q centos /etc/os-release ;then
    echo "centos"
else
    echo "Sorry, this script does not support this operating system"
fi
}

if [ `os_type` = centos ] ;then
    yum install -y httpd
elif [ `os_type` = ubuntu ] ;then
    apt install -y apache2
else
    echo "Sorry, this script does not support this operating system"
fi

#定义color.sh
~ vim color.sh
function color()
{
  RES_COL=60;
  MOVE_TO_COL="echo -en \\033[${RES_COL}G";
  SETCOLOR_SUCCESS="echo -en \\033[1;32m";
  SETCOLOR_FAILURE="echo -en \\033[1;31m";
  SETCOLOR_WARNING="echo -en \\033[1;33m";
  SETCOLOR_NORMAL="echo -en \E[0m";
  echo -n "$1" && $MOVE_TO_COL;
  echo -n "[";
  if [ $2 = "success" -o $2 = "0" ] ;then
    ${SETCOLOR_SUCCESS};
    echo -n $"  OK  ";
  elif [ $2 = "failure" -o $2 = "1" ] ;then
    ${SETCOLOR_FAILURE};
    echo -n $"FAILED";
  else
    ${SETCOLOR_WARNING};
    echo -n $"WARNING";
  fi
  ${SETCOLOR_NORMAL};
  echo -n "]";
  echo
}
#[ $# -eq 0 ] && echo "Usage: `basename $0` {success|failure|warning}"
#color $1 $2

5.2.2 查看函数

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

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

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

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

5.2.3 删除函数

格式:
unset func_name

5.3 函数调用

函数的调用方式:
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码。
函数的生命周期:被调用时创建,返回时终止。

5.3.1 交互式环境调用函数

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

➜  ~ function dir()
function> {
function> ls -l
function> }
➜  ~ dir

范例:实现判断CentOS的主版本

➜  ~ function centos_version()
function> {
function> sed -nr 's#^.* +([0-9]+)\..*#\1#p' /etc/redhat-release
function> }
➜  ~ centos_version
7

5.3.2 在脚本中定义及使用函数

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

➜  ~ vim function-test.sh
#!/bin/bash
#name: function-test.sh
function hello()
{
    echo "Hello there today is `date +%F`"
}
echo "Now going is the function hello"
hello
echo "Back from the function"

➜  ~ chmod +x function-test.sh
➜  ~ ./function-test.sh
Now going is the function hello
Hello there today is 2022-06-14
Back from the function

范例:系统初始化脚本

➜  ~ vim function-reset-centos.sh
#!/bin/bash
#name:function-reset.sh

function disable_selinux()
{
    sed -i.bak 's#SELINUX=enforcing#SELINUX=disabled#ig' /etc/selinux/config
    echo "SELinux已经禁用,重新启动才可生效"
}
function disable_firewalld()
{
    systemctl disable --now firewalld &> /dev/null
    echo "防火墙已经禁用"
}
function set_ps1()
{
    echo 'PS1="\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]"' > /etc/profile.d/reset_ps1.sh
    echo "提示符已经修改成功,请重新登录生效"
}
function set_eth()
{
    sed -i.bak -r '/^GRUB_CMDLINE_LINUX=/s#(.*)="(.*)"#\1="\2 net.ifnames=0"#g' /etc/default/grub
    grub2-mkconfig -o /boot/grub2/grub.cfg
    echo "网卡名称已经修改成功,重新启动才可生效"
}

PS3="请选择相应的编号 (1-6) "
MENU='
禁用SELinux
关闭防火墙
修改提示符
修改网卡名
以上全实现
退出脚本
'
select CHOICE in $MENU ;do
case $REPLY in
1)
    disable_selinux
;;
2)
    disable_firewalld
;;
3)
    set_ps1
;;
4)
    set_eth
;;
5)
    disable_selinux
    disable_firewalld
    set_ps1
    set_eth
;;
6)
    exit
;;
*)
    echo "你的输入有误"
esac
done

5.3.3 使用函数文件

可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入 shell ,再进行调用函数
文件名可以任意选取,但是最好与相关任务有某种联系,例如:functions
一旦函数文件载入 shell,就可以再命令行或者脚本中调用函数。可以使用 declare -f 或者 set 命令查看所有定义的函数,其输出列表包括已经载入 shell 的所有函数
若要改动函数,首先用 unset 命令从 shell 中删除函数。改动完毕后,再重新载入此文件。
实现函数文件的过程:
1.创建函数文件,只存放函数的定义
2.在shell 脚本或者交互式 shell 中调用函数的文件,格式如下:
. filename 或者 source filename

#该文件专门存放函数
~ vim functions
RED="echo -e \E[1;31m";
GREEN="echo -e \E[1;32m";
END="\E[0m";

function os_type()
{
#Check the operating system type
if grep -i -q ubuntu /etc/os-release ;then
    echo "ubuntu"
elif grep -i -q centos /etc/os-release ;then
    echo "centos"
else
    echo "Sorry, this script does not support this operating system"
fi
}

function font_color()
{
    RED="echo -e \E[31m"
    GREEN="echo -e \E[32m"
    END="\E[0m"
}

function is_ipaddr()
{
    [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && \
    echo -e "$1 is valid IP!" || \
    { echo -e "$1 is not valid IP!";return 1; }
}

#调用函数文件
~ . functions
~ os_type
~ font_color
~ ${RED}kubephere${END}
kubephere
~ is_ipaddr 223.5.5.5

#脚本中调用函数文件
~ vim function-installed-httpd.sh
#!/bin/bash
#使用绝对路径或者是相对路径
. /root/shell/functions

if [ `os_type` = centos ] ;then
    yum install -y httpd
    ${GREEN}Install httpd finished${END}
elif [ `os_type` = ubuntu ] ;then
    apt install -y apache2
    ${GREEN}Install apache2 finished${END}
else
    ${RED}Sorry, this script does not support this operating system${END}
fi

范例:调用系统的 action 函数

~ declare -f | grep action
~ . /etc/init.d/functions
~ declare -f | grep action

# 用于提示,并不执行
~ action "Stopping sshd:"
Stopping sshd:                                             [  OK  ]
~ action "Stopping sshd:" false
Stopping sshd:                                             [FAILED]
~ action "rm -rf /*"
rm -rf /*                                                  [  OK  ]

5.4 函数返回值

函数的执行结构返回值:
函数的退出状态码:

5.5 环境函数

类似于环境变量,也可以环境函数,使子进程也可以使用父进程定义的函数
定义环境函数:
export -f function_name
declare -xf function_name
查看环境函数:
export -f
declare -xf

5.6 函数参数

函数可以接受参数:
$1,$2,…… 对应第1个、第2个等参数,shift [n]换位置
$0:命令本身,包括路径
$:传递给脚本的所有参数,全部参数合成为一个字符串
$@:传递给脚本的所有参数,每个参数为独立字符串
KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲:传递给脚本的参数的个数 注意…@ $
只在被双引号包起来的时候才会有差异

5.7 函数变量

变量作用域:
注意:
在函数中定义本地变量的方法(必须在函数体内部使用,在函数体外使用会报错)
local NAME=VALUE

#默认函数体内部的变量会影响函数体外部的变量
➜  ~ name=wang;echo $name
wang
➜  ~ function func1(){name=wang;echo $name}
➜  ~ func1
wang
➜  ~ echo $name
wang

#尽可能在函数体内部使用本地变量,否则会产生互相的干扰
➜  ~ name=wang;echo $name
wang
➜  ~ function func1(){ local name=wang;echo $name; }
➜  ~ func1
wang
➜  ~ echo $name
wang

5.8 函数递归

函数递归:函数之间或者间接调用自身,注意递归层数,可以会陷入死循环。
函数递归特点:
范例:无出口函数的递归函数调用

➜  ~ function func(){ let i++;echo $i;echo "run func";func; }
➜  ~ func
func: maximum nested function level reached

递归示例:
阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘( factorial )是所有小于以及等于该数的正整数的积,并且有 0 的阶乘为 1,自然数 n 的阶乘写作 n!
n!=1 ⨉ 2 ⨉ 3 ⨉ … ⨉ n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)! ⨉ n
n!=n(n-1)(n-2)…1
n(n-1)! = n(n-1)(n-2)!
例如:3! = (3-1=2)! ⨉ 3 == 6 -> 2! = (2-1=1)! ⨉ 2 == 2 -> 1! == 1
范例:fact.sh

➜  ~ vim fact.sh
#!/bin/bash
#The loop body
function fact()
{
    if [ $1 -eq 0 -o $1 -eq 1 ] ;then
        echo 1
    else
        echo $[ $1*$(fact $[ $1-1 ]) ]
    fi
}
fact $1

➜  ~ bash -x fact.sh 2
2
➜  ~ bash -x fact.sh 3
6
➜  ~ bash -x fact.sh 4
24

范例:测试递归的嵌套深度

➜  ~ function func()
function> {
function> let i++
function> echo i=$i
function> test
function> }
➜  ~ func
i=1001

fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序,由于程序是后台递归的,如果没有任何的限制,这会导致这个简单的程序迅速耗尽里面的所有资源(会导致内存极速用光)
参考:https://en.wikipedia.org/wiki/Fork.bomb
函数实现:

~ cat fork.sh
#!/bin/bash
# &后台执行
:(){ :|:& };:
#bomb() { bomb | bomb& } ;bomb

脚本实现

~ cat bomb.sh
#!/bin/bash
./$0 | ./$0&
~ chmod +x bomb.sh
~ bash bomb.sh

#Windows fork炸弹
%0|%0

@echo off
:start
start "Forkbomb" /high %0
goto start

Shell 脚本基础_第23张图片

5.9 练习

1. 编写函数,实现OS的版本判断
2. 编写函数,实现取出当前系统eth0的IP地址
3. 编写函数,实现打印绿色OK和红色FAILED
4. 编写函数,实现判断是否无位置参数,如无参数,提示错误
5. 编写函数,实现两个数字做为参数,返回最大值
6. 编写服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数:start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”
考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”
考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start
考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is
running...”,如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is
stopped...”
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理
说明:SCRIPT_NAME为当前脚本名

7. 编写脚本/root/bin/copycmd.sh
(1) 提示用户输入一个可执行命令名称
(2) 获取此命令所依赖到的所有库文件列表
(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下
如:/bin/bash ==> /mnt/sysroot/bin/bash
/usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd
(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
(5) 每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出

8. 斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2),利用函数,求n阶斐波那契数列
9. 汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤
1. 编写函数,实现OS的版本判断
➜  ~ vim function_typeos.sh
#!/bin/bash
#SHELL ENV
BASECOLOR="\E[1;32m"
WARNCOLOR="\E[1;31m"
ENDCOLOR="\E[0m"

function type_os()
{
. /etc/os-release
if [ $ID = "centos" ] ;then
echo -e "${BASECOLOR}The operating system is CentOS${ENDCOLOR}"
elif [ $ID = "ubuntu" ] ;then
echo -e "${BASECOLOR}The operating system is Ubuntu${ENDCOLOR}"
elif [ $ID = "rocky" ] ;then
echo -e "${BASECOLOR}The operating system is Rocky${ENDCOLOR}"
else
echo -e "${WARNCOLOR}Other operating system${ENDCOLOR}"
fi
}

type_os

2. 编写函数,实现取出当前系统eth0的IP地址
➜  ~ vim function_eth0ip.sh
#!/bin/bash

function get_eth0_ip()
{
IP=$(ifconfig eth0 | awk '/\/{print $2}')
echo "The eth0 IP address is $IP"
}

get_eth0_ip

3. 编写函数,实现打印绿色OK和红色FAILED
➜  ~ vim function_color.sh
function color()
{
  RES_COL=60;
  MOVE_TO_COL="echo -en \\033[${RES_COL}G";
  SETCOLOR_SUCCESS="echo -en \\033[1;32m";
  SETCOLOR_FAILURE="echo -en \\033[1;31m";
  SETCOLOR_WARNING="echo -en \\033[1;33m";
  SETCOLOR_NORMAL="echo -en \E[0m";
  echo -n "$1" && $MOVE_TO_COL;
  echo -n "[";
  if [ $2 = "success" -o $2 = "0" ] ;then
      ${SETCOLOR_SUCCESS};
      echo -n $"  OK  ";
  elif [ $2 = "failure" -o $2 = "1" ] ;then
      ${SETCOLOR_FAILURE};
      echo -n $"FAILED";
  else
      ${SETCOLOR_WARNING};
      echo -n $"WARNING";
  fi
  ${SETCOLOR_NORMAL};
  echo -n "]";
  echo
}

color "执行成功" 0
color "执行失败" 1

4. 编写函数,实现判断是否无位置参数,如无参数,提示错误
➜  ~ vim function_args.sh
#!/bin/bash

function judge_args()
{
    if [ $# -eq 0 ] ;then
        echo -e "\E[1;31mUsage: $(basename $0) arg1 arg2 [...]\E[0m"
    else
        echo -e "\E[1;32mThe Args are $*\E[0m"
    fi
}

judge_args $*

5. 编写函数,实现两个数字做为参数,返回最大值
➜  ~ vim function_max.sh
#!/bin/bash
#SHELL ENV
WARNCOLOR="echo -e \E[1;31m"
ENDCOLOR="\E[0m"

function judge_max()
{
    [ $# -eq 0 -o $# -ne 2 ] && { ${WARNCOLOR}Usage: $(basename $0) number1 number2${ENDCOLOR}; exit; }
    if [ $1 -gt $2 ] ;then
        echo "The Max number is $1"
    elif [ $1 -lt $2 ] ;then
        echo "The Max number is $2"
    else
        echo "$1 equal $2"
    fi
}

judge_max $1 $2

6. 编写服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数:start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”
考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”
考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start
考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running...”,
如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is stopped...”
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理
说明:SCRIPT_NAME为当前脚本名
➜  ~ vim testsrv.sh
#!/bin/bash

#SHELL ENV
BASECOLOR="echo -e \E[1;32m"
WARNCOLOR="echo -e \E[1;31m"
ENDCOLOR="\E[0m"
DIR="/var/lock/subsys/"
SCRIPT_NAME="testsrv.sh"

. /etc/init.d/functions
{ cd $(dirname $0);cp testsrv.sh /etc/init.d/; }

function start()
{
    if [ $(status) == 1 ];then
        ${BASECOLOR}The Program already running${ENDCOLOR}
    else
        { cd $(dirname $0);cp ${SCRIPT_NAME} ${DIR}; }
        action "Start Success..." true
    fi
}

function stop()
{
    if [ $(status) == 1 ];then
        rm -rf ${DIR}${SCRIPT_NAME}
        action "Stop Success..." true
    else
        ${WARNCOLOR}The Program is not running${ENDCOLOR}
    fi
}

function restart()
{
    if [ $(status) == 1 ];then
        stop
    fi
    start
}

function status()
{
    if [ -a ${DIR}${SCRIPT_NAME} ] ;then
        echo "1"
    else
        echo "0"
    fi
}

name="
start
stop
restart
status
"

PS3="请选择功能(1-4) : "
select menu in $name ;do
case $REPLY in
1)
    start
;;
2)
    stop
;;
3)
    restart
;;
4)
    if [ $(status) == 0 ];then
        action "The program was stoped" false
    else
        action "The program was started" true
    fi
;;
*)
    ${WARNCOLOR}Please enter the correct function number${ENDCOLOR}
;;

esac
done

7. 编写脚本/root/bin/copycmd.sh
(1) 提示用户输入一个可执行命令名称
(2) 获取此命令所依赖到的所有库文件列表
(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下
如:/bin/bash ==> /mnt/sysroot/bin/bash
/usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd
(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出
#!/bin/bash
DIR="/mnt/sysroot"
[ ! -d $DIR ] && mkdir $DIR

bincopy(){
		CMD_PATH=`which --skip-alias $command`
		BIN_DIR=`dirname $CMD_PATH`
        [ -d ${DIR}${BIN_DIR} ] || mkdir -p ${DIR}${BIN_DIR}
        [ -f ${DIR}${CMD_PATH} ] || cp $CMD_PATH ${DIR}
		echo "copy $CMD_PATH ========> $DIR$BIN_DIR"
    return 0
}
 
libcopy() {
    LIB_LIST=$(ldd `which --skip-alias $command` | grep -Eo '/[^[:space:]]+')
    for loop in $LIB_LIST;do
        LID_DIR=`dirname $loop`
        [ -d ${DIR}${LID_DIR} ] || mkdir -p  ${DIR}${LID_DIR}
        [ -f ${DIR}${loop} ] || cp $loop ${DIR}${LID_DIR}
	    echo "copy $loop ========> $DIR$LID_DIR"
    done
}
 
while read -p "Please input a command or quit: " command;do
    if [ $command == "quit" ];then
        break
    elif [ -z $command ];then
        echo "please input again!"
        continue
    else
        which $command &>/dev/null
        if [ $? -ne 0 ];then
            echo "command is not exit!"
            continue
        else
            if bincopy ;then
            libcopy
            fi
        fi
    fi
done

8. 斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2),利用函数,求n阶斐波那契数列
~ vim function-fibonacci.sh
func_Fibonacci(){
    tmp=$1
    first=1
    declare -i i=0
    second=1
    last=1
    if [ $tmp -eq 1 -o $tmp -eq 2 ] ;then
        echo "F($tmp)=1"
    elif [ $tmp -eq 0 ];then
        echo  "F($tmp)=0"
    else
        for i in `seq 3 $tmp`;do
            let last=$first+$second
            let first=$second
            let second=$last
        done
        echo  "F($tmp)=$last"
    fi
}   

trap 'echo Ctrl+C\\ is not work ,Please type q or quit to exit' 1 2 3 15
while : ;do
    read -p "please input a number(0-92):" num
    if [[ $num =~ ^[Qq]([Uu][Ii][Tt])?$ ]] ;then
        break 
    elif [[ $num =~ ^[0-9]+$ ]] && [ $num -le 92 ] ;then
        func_Fibonacci $num
    else
        echo "Error:please enter a positive number less than 92!"
    fi
    unset num
done 

9. 汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤
~ vim function-hanoi.sh
#!/bin/bash
function Hanoi(){
    local tmp=$1
    if [ $tmp -eq 1 ] ;then
        let i++
        echo "step:$i move $2--->$4"      #move A to C
    else
        Hanoi $[$1-1] $2 $4 $3 # move the n-1 plate to B
        let i++
        echo "step:$i move $2--->$4" #move the bigest  plate to C
        Hanoi $[$1-1] $3 $2 $4  #move the n-1 plate to C
    fi
}

trap 'echo Ctrl+C is not work ,Please type q or quit to exit' 2
while : ;do
    read -p "please input a number:" tmp
    declare -i i=0
    if [[ $tmp =~ ^[Qq]([Uu][Ii][Tt])?$ ]] ;then
        break 
    elif [[ $tmp =~ ^[0-9]+$ ]] ;then
        Hanoi $tmp A B C
    else
        echo "Error:please input a positive number!"
    fi
    unset tmp
done

6 其他脚本相关工具

6.1 信号捕捉 trap

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

-l 让shell打印一个命令名称和其相对应的编号的列表
-p 打印与每一个信号有关联的命令的列表
缺省 每个接收到的sigspec信号都将会被重置为它们进入shell时的值
-f 阻止中断信号
#查看信号列表
trap -l
2) SIGINT :是Ctrl+C的按键信号
3)SIGQUIT:是Ctrl+\的按键信号

### 信号的值可以写成三种格式:
## 2(数字法表示) | SIGINT(全称) | INT(简称)
#进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '触发指令' 信号

#忽略信号的操作
trap '' 信号

#恢复原信号的操作
trap '-' 信号

#列出自定义信号操作
trap -p

#当前脚本退出时,执行finish函数(重要)
trap finish EXIT

范例:

#!/bin/bash
#自定义指令信号
trap "echo 'Please Ctrl+C or Ctlr+\'" SIGINT SIGQUIT
trap -p
for ((i=0;i<=10;i++))
do
  sleep 1
  echo $i
done
#忽略原信号操作
trap '' SIGINT
trap -p
for ((i=11;i<=20;i++))
do
  sleep 1
  echo $i
done
#恢复原信号操作
trap '-' SIGINT
trap -p
for ((i=21;i<=30;i++))
do
  sleep 1
  echo $i
done

范例:当脚本正常或者异常退出时,也会执行 finish 函数

~ vim trap-exit.sh
#!/bin/bash
function finish()
{
  echo finish is at `date +%F_%T` | tee -a /root/finish.log
}
#捕获退出就执行finish函数
trap finish exit

while true ;do
  echo running
  sleep 1
done

6.2 创建临时文件 mktemp

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

mktemp [OPTIONS]... [TEMPLATE]

说明:TEMPLATE: filenameXXX,X至少要出现三个,三个以上随机字符

常用选项:

-d 										  #创建临时目录
-p DIR或者--tmpdir=DIR	#指明临时文件所存放目录位置

范例:

~ mktemp
/tmp/tmp.lOLpKJ8nrB

~ mktemp /tmp/testXXX
#-p 指定的现有目录位置
~ tmpdir=`mktemp -d /tmp/testdirXXX`
#--tmpdir 指定的现有目录位置
~ mktemp --tmpdir=tmpdir testXXXXXX
~ mktemp -d tmpdirXXX -p /usr/local/src

范例:实现文件垃圾箱功能

#方法1:脚本实现
~ vim /data/shell/mktemp-rm.sh
#!/bin/bash

DIR=$(mktemp -d /tmp/trash-$(date +%F_%T)XXX)
mv $* $DIR
echo $* is move to $DIR

~ chmod +x /data/shell/mktemp-rm.sh
~ alias rm=/data/shell/mktemp-rm.sh
~ rm finish.log
finish.log is move to /tmp/trash-2022-06-14_17:35:45jX0

#方法2:函数实现
~ function rm() { local trash=$(mktemp -d /tmp/trash-$(date +%F_%T)XXX);mv $* $trash; }

6.3 安装复制文件 install

install 功能相当于 cp,chmod,chown,chgrp,mkdir等相关工具的集合
install 命令格式:

Usage: install [OPTION]... [-T] SOURCE DEST
  or:  install [OPTION]... SOURCE... DIRECTORY
  or:  install [OPTION]... -t DIRECTORY SOURCE...
  or:  install [OPTION]... -d DIRECTORY...

选项:

-m MODE			权限,默认755
-o OWNER		用户
-g GROUP		用户组
-d DIRNAME 	目录
-S					设置目标文件的后缀
-D					创建指定文件路径中不存在的目录

范例:

~ install -m 700 -o wang -g admins srcfile dstfile
#-d:相当于mkdir -p
~ install -m 700 -d /testdir/installdir

~ install -m 600 -o wang -g bin reset.sh /data/set.sh
~ ls -l /data/set.sh

~ install -m 700 -o kubesphere -g daemon -d /data/testdir
~ ls -ld /data/testdir

6.4 交互式转换批处理工具 expect

expect是由Don Libes基于TCL ( Tool Command Language ) 语言开发的,主要应用于自动化交互式操作的场景,借助 expect 处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务懦执行相同操作的环境中,可以大大提高系统管理人员的工作效率。可以代替所有的交互式操作。(例如:bc,fdisk,ssh等)。当然,expect 主要用于配合 ssh 和 scp 两个命令。
范例:安装expect 及mkpasswd工具

➜  ~ yum install -y expect
➜  ~ rpm -ql expect | head
/usr/bin/autoexpect
/usr/bin/dislocate
/usr/bin/expect
/usr/bin/ftp-rfc
/usr/bin/kibitz
/usr/bin/lpunlock
/usr/bin/mkpasswd
/usr/bin/passmass
/usr/bin/rftp
/usr/bin/rlogin-cwd

➜  ~ mkpasswd
gBOlu87i<
➜  ~ mkpasswd -l 15 -d 3 -C 5
@k5FpJ9XRsPaj1i

expect 语法:

expect [选项] [ -c cmds ]  [[ -[flb] ] cmdfile ] [ args ]

常见选项:

-c:从命令行执行expect脚本,默认expect是交互地执行的
-d:可以调试信息

示例:

expect -c 'expect "\n" { send "pressed enter\n" }'
expect -d ssh.exp

expect 中的相关命令
expect 最常用的语法:(TCL语言:模式-动作)
单一分支模式语法:

#直接执行expect则是交互式
~ expect
#一次性的。
expect1.1> expect "hi" {send "You said hi\n"}
hi
You said hi
expect1.2> expect "hi" {send "You said hi\n"}
123hihello
You said hi

匹配到 hi 后,会输出"You said hi",并换行
多分支模式语法:

~ expect
expect1.1> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send "Good bye\n" }
hehe
Hehe yourself
expect1.2> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send "Good bye\n" }
bye
Good bye
expect1.3> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" } "bye" { send "Good bye\n" }
hi
You said hi

匹配 hi,hello,bye 任意字符串时,执行相应输出。等同如下:

expect {
  "hi" { send "You said hi\n" }
  "hehe" { send "Hehe yourself\n" }
  "bye" { send "Good bye\n" }
}

~ expect1.1> expect {
+>  "hi" { send "You said hi\n" }
+>  "hehe" { send "Hehe yourself\n" }
+>  "bye" { send "Good bye\n" }
+> }
~ bye
Good bye
expect1.2>

范例1:非交互式复制文件

~ vim expect-scp
#!/usr/bin/expect
spawn scp /etc/redhat-release 10.0.0.170:/data
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "magedu\n" }
}
expect eof

范例2:自动登录(登录后交互式)

~ vim expect-ssh
#!/usr/bin/expect
spawn ssh 10.0.0.170
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "magedu\n" }
}
interact

范例3:expect 变量

~ vim expect-set-ssh
#!/usr/bin/expect
set ip 10.0.0.170
set user root
set password magedu
set timeout 10

spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "$password\n" }
}
interact

范例4:expect 位置参数

~ vim expect-arg
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]

spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "$password\n" }
}
interact

~ chmod +x expect-args
~ ./expect-args 10.0.0.170 root magedu
spawn ssh [email protected]
[email protected]'s password:
Last login: Tue Jun 14 21:42:04 2022 from 10.0.0.160
[root@kubesphere-mysql57 ~]#logout
Connection to 10.0.0.170 closed.

范例5:expect 执行多条命令

~ vim expect-command
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]

spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo Admin@h3c | passwd --stdin haha\n"}
send "exit\n"
expect eof

~ ./expect-command 10.0.0.170 root magedu

范例6:shell 脚本调用 expect

~ vim shell-expect.sh
#!/bin/bash
#Shell ENV
ip=${1}
user=${2}
password=${3}

expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n"}
expect "]#" { send "echo Admin@h3c | passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF

~ chmod +x bash-expect.sh
~ ./bash-expect.sh 10.0.0.170 root magedu

范例7:shell脚本利用循环调用 expect 在 CentOS 和Ubuntu上批量创建用户

~ vim export-ubuntu-centos-useradd.sh
#!/bin/bash
#SHELL ENV
net=10.0.0
user=root
password=magedu
IPLIST="
51
52
53
"

for ID in $IPLIST ;do
ip=$net.$ID

expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "$password\m" }
}
expect "#" { send "useradd test\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

范例8:修改配置文件 SELinux

~ vim expect-selinux.sh
#!/bin/bash
#SHELL ENV
net=10.0.0
user=root
password=magedu
IPLIST="
51
52
53
"

for ID in $IPLIST ;do
ip=$net.$ID

expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "$password\m" }
}
expect "#" { send "sed -i 's/^SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config\n" }
expect "#" { send "setenforce 0\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

范例9:在指定主机执行批量操作

~ vim expect-scp.sh
#!/bin/bash

net=10.0.0
user=root
password=magedu

for i in {51..53} ;do
ip=$net.$i
expect <<EOF
set timeout 20
spawn scp /etc/selinux/config $user@$ip:/data
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "$password\n" }
}
spawn ssh $user@$ip
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "$password\n" }
}
expect "root" { send "useradd kubesphere\n" }
expect "root" { send "echo Admin@h3c | passwd --stdin kubesphere\n" }
expect "root" { send "exit\n" }
expect eof
EOF

done

~ chmod +x expect-scp.sh
~ ./expect-scp.sh

7 数组

7.1 数组介绍

Shell 脚本基础_第24张图片
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引(或 下标)
Shell 脚本基础_第25张图片

7.2 声明数组

#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
注意:两者不可相互转换。一旦定义就不能进行重新声明,除非删除后重新定义。

7.3 数组赋值

数组元素的赋值
(1)一次只能赋值一个元素

ARRAY_NAME[INDEX]=VALUE

范例:

weekdays[0]="Sunday"
weekdays[4]="Thursday"

(2)一次赋值全部元素

ARRAY_NAME=("VAL1" "VAL2" "VAL3" ... "VAL#")

范例:

title=("CEO" "CTO" "COO")
num=({1..10})
alpha=({a..g})
file=( *.sh )

(3)只赋值特定元素

ARRAY_NAME=([0]="VAL1" [1]="VAL2" [2]="VAL3" ... [N]="VAL#")

(4)交互式数组值对赋值

read -a ARRAY

范例:

~ declare -A course
~ declare -a course
bash: declare: course: cannot convert associative to indexed array

~ file=( *.sh )
~ declare -A file
bash: declare: file: cannot convert indexed to associative array

#使用read交互式赋值
~ read -a name
wang wang shi wu
~ echo ${name[*]}
wang wang shi wu

范例:

~ i=a
~ j=1
~ declare -A arr
~ arr[$i$j]=kubernetes
~ j=2
~ arr[$i$j]=kubesphere

~ echo ${arr[a1]}
kubernetes
~ echo ${arr[a2]}
kubesphere
~ echo ${arr[*]}
kubesphere kubernetes

7.4 显示所有数组

显示所有数组:

declare -a

范例:显示所有数组

~ declare -a
declare -a BASH_ARGC='()'
declare -a BASH_ARGV='()'
declare -a BASH_LINENO='()'
declare -a BASH_SOURCE='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -a DIRSTACK='()'
declare -a FUNCNAME='()'
declare -a GROUPS='()'
declare -a PIPESTATUS='([0]="0")'

7.5 引用数组

引用数组元素

${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素。默认是0号元素

范例:

~ declare -a job=([0]="CEO" [1]="CTO" [2]="COO")
~ declare -a | grep job
declare -a job='([0]="CEO" [1]="CTO" [2]="COO")'
~ echo ${job[0]}
CEO
~ echo ${job[1]}
CTO
~ echo ${job[2]}
COO
~ echo ${job[3]}

引用数组所有元素

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

范例:

~ echo ${job[*]}
CEO CTO COO
~ echo ${job[@]}
CEO CTO COO

数组的长度,即数组中的元素的个数

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

范例:

~ echo ${#job[*]}
3
~ echo ${#job[@]}
3

7.6 删除数组

删除数组中的某个元素,会导致稀疏格式(编号索引(或下标)不连续)

unset ARRAY_NAME[INDEX]

范例:

~ declare -a job=([0]="CEO" [1]="CTO" [2]="COO")
~ echo ${job[*]}
CEO CTO COO
~ echo ${#job[*]}
3
#删除数组中的某个元素
~ unset job[0]
~ echo ${job[*]}
CTO COO

删除整个数组

unset ARRAY_NAME

范例:

~ unset job
~ echo ${job[*]}

~ declare -a | grep job

7.7 数组数据处理

数组的切片:

${ARRAY_NAME:offset:number}
offset	:#要跳过的元素个数
number	:#要取出的元素个数

#取偏移量之后的所有元素
{ARRAY_NAME[0]:offset}

范例:

~ declare -a num=({1..10})
~ echo ${num[*]}
1 2 3 4 5 6 7 8 9 10
~ echo ${num[*]:3:4}
4 5 6 7
~ echo ${num[*]:3}
4 5 6 7 8 9 10

向数组中追加元素(对稀疏格式追加元素不友好):

ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRYA[@]}]=value

范例:

~ num[${#num[*]}]=11
或
~ declare num[${#num[*]}]=11

~ echo ${#num[*]}
1 2 3 4 5 6 7 8 9 10 11
~ echo ${#num[*]}
11

7.8 关联数组

declare -A ARRAY_NAME
ARRAY_NAME=([IDX_NAME1]="val1" [IDX_NAME2]="val2" ... [IDX_NAMEN]="valn")

注意:关联数组必须先声明再进行调用
范例:

#默认使用的数值数组
~ name[ceo]="kubesphere"
~ name[cto]="kubernetes"
~ name[coo]="kubeedge"
~ echo ${name[ceo]}
kubeedge
~ echo ${name[cto]}
kubeedge
~ echo ${name[coo]}
kubeedge
~ declare -A name
bash: declare: name: cannot convert indexed to associative array
~ unset name
#定义关联数组
~ declare -A name=([ceo]="kubesphere" [cto]="kubernetes" [coo]="kubeedge")
~ declare -A | grep name
declare -A name='([ceo]="kubesphere" [coo]="kubeedge" [cto]="kubernetes" )'
~ echo ${name[ceo]}
kubesphere
~ echo ${name[coo]}
kubeedge
~ echo ${name[cto]}
kubernetes

#关联数组必须先声明
~ declare -A family
~ family[name]="wangshi"
~ family[address]="guangdong"
~ family[number]="3"
~ fmaily[user]="wang"

~ declare -A family=([name]="wangshi" [address]="guangdong" [number]="3")
~ declare -A | grep family
declare -A family='([number]="3" [name]="wangshi" [address]="guangdong" )'

#引用关联数组
~ echo ${family[name]}
wangshi
~ echo ${family[address]}
guangdong

范例:关联数组

~ declare -A student=([name1]="lijun" \
[name2]="ziqing" \
[age1]="18" \
[age2]="16" \
[gender1]="m" [city1]="nanjing" \
[gender2]="f" [city2]="anhui" [gender2]="m" \
[name10]="alice" [name3]="tom")

~ for i in {1..10};do echo student[name$i]=${student[name$i]};done
student[name1]=lijun
student[name2]=ziqing
student[name3]=tom
student[name4]=
student[name5]=
student[name6]=
student[name7]=
student[name8]=
student[name9]=
student[name10]=alice

7.9 范例

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

~ vim array-random-maxmin.sh
#!/bin/bash
#Generate 10 random numbers and store them in an array,
#and find out their maximum and minimum values
#声明两个整数
declare -i min max
declare -a nums

for ((i=0;i<=10;i++));do
    nums[$i]=$RANDOM
    [ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]} && continue
    [ ${nums[$i]} -gt $max ] && max=${nums[$i]} && continue
    [ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done

echo "All numbers are ${nums[*]}"
echo "Max is $max"
echo "Min is $min"

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

~ vim array-log.sh
#!/bin/bash

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

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

7.10 练习

#1、输入若干个数值存入数组中,采用冒泡排序算法进行升序或者降序排序
#2、将下图所示,实现转置矩阵 matrix.sh
1 2 3				1 4 7
4 5 6 --->  2 5 8
7 8 9				3 6 9
#3、打印杨辉三角形
#1、输入若干个数值存入数组中,采用冒泡排序算法进行升序或者降序排序

#2、将下图所示,实现转置矩阵 matrix.sh
1 2 3				1 4 7
4 5 6 --->  2 5 8
7 8 9				3 6 9

#3、打印杨辉三角形
#!/bin/bash
yanghui(){
    tmp=$1
    i=0
    while [ $i -ne `expr $tmp + 1` ]  
    do
        if [ $i -eq $tmp ]||[ $i -eq 0 ]
        then
            echo -n 1  
        else
            echo -n $(expr $2 + $3)  
            shift
        fi
        echo -n " "  
        i=`expr $i + 1`
    done
}
if [ $# -ne 1 ]
then
   read -p  "enter the Max number:"  COUNT
else
    COUNT=$1
fi
i=0
while [ $i -ne  $COUNT ]  
do
    tmp=(`yanghui $i ${tmp[*]}`)
    echo ${tmp[*]}  
    i=`expr $i + 1`
done

8 字符串处理

8.1 字符串切片

8.1.1 基于偏移量取字符串

#返回字符串变量var的字符的个数长度,一个汉字算一个字符
${#var}

#返回字符串变量var中从第offset个字符串(不包括第offset个字符)的字符开始,到最后的部分,offset的取值再0到${#var}-1之间(base4.2后,允许为负值)
${var:offset}

#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var:offset:number}

#取字符串的最右侧几个字符,注意:冒号后必须有一空白字符
${var: -length}

#从最左侧跳过offset字符,一直向右取到距离最右侧length个字符之前的内容,即 : 掐头去尾
${var:offset:-length}

#先从最右侧向左取到length个字符开始,再向右侧取到举例最右侧offset个字符之间的内容,注意:-length前空格,并且length必须大于offset
${var: -length:-offset}

#先从最右侧向左取到length个字符开始,再向右侧取到举例最左侧offset个字符之间的内容,注意:-length前空格,其中offset值可以大于length但是不能小于0
${var: -length:offset}

范例:

~ echo {a..z}|tr -d " "
abcdefghijklmnopqrstuvwxyz
~ str=$(echo {a..z}|tr -d " ")
~ echo ${str}
abcdefghijklmnopqrstuvwxyz
~ echo ${#str}
26
~ name="青云"
~ echo ${name}
青云
~ echo ${#name}
2

~ echo ${str:3}
~ echo ${str:3:5}
~ echo ${str: -3}
~ echo ${str:3:-3}
~ echo ${str: -6:-2}

###
~ str="abcdef我你他"
~ echo ${#str}
9
~ echo ${str:2}
cdef我你他
~ echo ${str:2:3}
cde
~ echo ${str:-3}
abcdef我你他
~ echo ${str: -3}
我你他
~ echo ${str:2:-3}
cdef
#无用的写法
~ echo ${str:-2:-3}
abcdef我你他
~ echo ${str: -2:-3}
bash: -3: substring expression < 0
#正确取字符串
~ echo ${str: -3:-2}
我
~ echo ${str:-3:-2}
abcdef我你他
~ echo ${str: -5:-2}
ef我

8.1.2 基于模式取子串

#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word,删除字符串开头至第一次出现 word 字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由 word 指定的字符之间的所有内容,即贪婪模式,以最后一个 word 为界删左留右
${var##*word}
范例:

~ url=http://www.kubesphere.com/index.html
~ echo ${url#*/}
/www.kubesphere.com/index.html
~ echo ${url#*//}
www.kubesphere.com/index.html
~ echo ${url##*/}
index.html

~ file="var/log/message"
~ echo ${file#*/}
log/message
~ echo ${file##*/}
message

#其中word可以是指定的任意字符,自右而左,查找var变量所存储的字符串中,第一次出现的word ,删除字符串开头至第一次出现 word 字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删右留左
${var%word*}
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由 word 指定的字符之间的所有内容,即贪婪模式,以最后一个 word 为界删右留左
${var%%word*}
范例:

~ file="var/log/message"
~ echo ${file%/*}
var/log
~ echo ${file%%/*}
var

范例:

~ url=http://www.kubesphere.com:8080/index.html
~ echo ${url%:*}
http://www.kubesphere.com
~ echo ${url%%:*}
http

8.2 查找替换

#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之。(懒惰模式)
${var/pattern/substr}
#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之。(贪婪模式)
${var//pattern/substr}
#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}
#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}
范例:查找替换

~ getent passwd root
root:x:0:0:root:/root:/bin/zsh
~ line=$(getent passwd root)
~ echo $line
root:x:0:0:root:/root:/bin/zsh

~ echo ${line/root/rooter}
rooter:x:0:0:root:/root:/bin/zsh
~ echo ${line//root/rooter}
rooter:x:0:0:rooter:/rooter:/bin/zsh
~ echo ${line/#root/rooter}
rooter:x:0:0:root:/root:/bin/zsh
~ echo ${line/%zsh/rooter}
root:x:0:0:root:/root:/bin/rooter

8.3 查找并删除

#删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}
#删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}
#删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}
#删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}
范例:查找并删除

~ getent passwd root
root:x:0:0:root:/root:/bin/zsh
~ line=$(getent passwd root)
~ echo $line
root:x:0:0:root:/root:/bin/zsh

~ echo ${line/root}
:x:0:0:root:/root:/bin/zsh
~ echo ${line//root}
:x:0:0::/:/bin/zsh
~ echo ${line/#root}
:x:0:0:root:/root:/bin/zsh
~ echo ${line/%zsh}
root:x:0:0:root:/root:/bin/

8.4 字符大小写转换

#把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,}
范例:字符大小写转换

~ line=$(getent password root)
~ echo $line
root:x:0:0:root:/root:/bin/zsh
#字符转换为大写
~ echo ${line^^}
ROOT:X:0:0:ROOT:/ROOT:/BIN/ZSH

~ line2=$(echo ${line^^})
~ echo ${line2}
ROOT:X:0:0:ROOT:/ROOT:/BIN/ZSH
#字符转换为小写
~ echo ${line2,,}
root:x:0:0:root:/root:/bin/zsh

~ url=http://www.kubesphere.com:8080/index.html
~ echo ${url}
http://www.kubesphere.com:8080/index.html
~ url=$(echo ${url^^})
~ echo ${url}
HTTP://WWW.KUBESPHERE.COM:8080/INDEX.HTML

~ url=$(echo ${url,,})
~ echo ${url}
http://www.kubesphere.com:8080/index.html

9 高级变量

9.1 高级变量赋值

变量配置方式 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

范例:高级变量赋值

~ unset str;var=${str-kubesphere};echo ${var}
kubesphere
~ str='';var=${str-kubesphere};echo ${var}

~ str="wang";var=${str-kubesphere};echo ${var}
wang

~ title=ceo;name=${title-kubernets};echo ${name}
ceo
~ title='';name=${title-kubernets};echo ${name}

~ unset title;name=${title-kubernets};echo ${name}
kubernets
~ unset str;var=${str:-kubesphere};echo ${var}
kubesphere
~ str='';var=${str:-kubesphere};echo ${var}
kubesphere
~ str="wang";var=${str-kubesphere};echo ${var}
wang

~ unset kube;var=${kube:-kubesphere};echo $var
kubesphere
~ kube="";var=${kube:-kubesphere};echo $var
kubesphere
~ kube="kubernetes";var=${kube:-kubesphere};echo $var
kubernetes

9.2 高级变量用法 - 有类型变量

Shell变是一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的。
declare [选项] 变量名
选项:
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数,相当于export
-l 声明变量为小写字母 declare -l var=UPPER
-u 声明变量为大写字母 declare -u var=lower
-n 给变量提供nameref属性,使其成为对另一个变量的引用
范例:

~ declare -l var=KUBESPHERE
~ echo ${var}
kubesphere

~ declare -u var=kubesphere
~ echo ${var}
KUBESPHERE

9.3 变量间接引用

9.3.1 eval 命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变呈该命令对变量进行两次扫描。是Linux系统的内置命令。
Bash Shell 脚本在执行的时候只会进行一次扫描处理。简单的理解就是在原有的变量引用前面再加一个变量引用。例如:
范例:

~ CMD=whoami
~ echo $CMD
whoami
~ eval echo $CMD
whoami
~ eval $CMD
root

~ n=10;echo {1..$n}
{1..10}
~ n=10;eval echo {1..$n}
1 2 3 4 5 6 7 8 9 10

~ n=5;for i in $(eval echo {1..$n}) ;do echo i=$i;done
i=1
i=2
i=3
i=4
i=5

~ i=a
~ j=1
~ $i$j="hello"
bash: a1=hello: command not found
~ eval $i$j="hello"
~ echo $i$j
a1
~ echo ${a1}
hello

#数组中的下标可以使用变量进行替换
~ i=1 ; j=1 ; arr[$i$j]=x ; echo ${arr[11]};
x
~ i=1 ; j=2 ; arr[$i$j]=y ; echo ${arr[12]};
y

9.3.2 间接变量引用

主要用于复杂的逻辑处理,逻辑处理不复杂的脚本建议不要使用该间接变量引用。
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为。
Bash Shell 默认只处理一次变量赋值。简单理解就是在原有变量引用前继续套用一个变量引用

variable1=variable2
variable2=value

示例:
i=1
$1=wang

bash Shell 提供了两种格式实现间接变量引用

#方法1:
#变量赋值
eval tempvar=\$$variable1
#显示值
eval echo \$$variable1
eval echo '$'$variable1

#方法2:
#变量赋值
tempvar=${!variable1}
#显示值
echo ${!variable1}

范例:

#$$表示当前shell 的进程编号
echo $$

~ ceo=name
~ name=wang
~ echo $ceo
name
~ echo $$ceo
25585ceo
~ echo \$$ceo
$name
或者
~ echo '$'$ceo
$name

~ eval echo '$'$ceo
wang
~ eval echo \$$ceo
wang
~ echo ${!ceo}
wang

范例:

~ N1=N2
~ N2=wangjun
~ eval NAME=\$$N1
~ echo $NAME
wangjun

~ NAME=${!N1}
~ echo $NAME
wangjun

范例:批量创建用户

~ vim create-eval-useradd.sh
#!/bin/bash

n=$#
[ $n -eq 0 ] && { echo "Usgae: `basename $0` username..."; exit 2; }

#eval echo {1..$n}中的eval用来将$n转变为数值后进行{1..n}
for i in $(eval echo {1..$n}) ;do
  #echo $i;user=${i}
  #user=$i 若直接引用$i,那么i的值就直接是{1..n}的数值
  #user=${!i}-->echo '$'${i} 变量引用,即将${i}转变为数值后,前面有个$,即又转变为$1-->则是位置参数
  user=${!i}	#user=$(eval echo \$${i})类似
  id $user &> /dev/null && \
  echo "$user is exist" || \
  { useradd $user; echo "$user is created"; }
done

~ chmod +x create-eval-useradd.sh
~ ./create-eval-useradd.sh kubesphere kubernetes kubeedge

9.3.3 变量引用 reference

主要用于复杂的逻辑处理,目前并不需要使用到变量引用,了解即可。

~ cat reference-test.sh
#!/bin/bash
ceo=wangjun
title=ceo

declare -n ref=$title #变量的引用
#-R 判断变量是否被引用
[ -R ref ] && echo "reference"	#[ -R var ] 是bash4.4的新特性:help test 可以帮助手册可以查看到
echo $ref
ceo=kubernetes
echo $ref

~ bash reference-test.sh
reference
wangjun
kubernetes
~ bash -x reference-test.sh

你可能感兴趣的:(学习笔记,linux,运维)