面向过程语言
面向对象语言
计算机:运行二进制指令
编程语言:人与计算机之间交互的语言。分为两种:低级语言和高级语言
低级编程语言:
机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大,难懂、难写
汇编:用一些助记符号替代机器指令,称为汇编语言
如:ADDAB 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中
汇编语言写好的程序需要汇编程序转换成机器指令
汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言
高级编程语言:
三种处理逻辑
shell脚本编程:是基于过程式、解释执行的语言
编程语言的基本结构:
shell脚本:包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
第一步:使用文本编辑器来创建文本文件
第二步:加执行权限
第三步:运行脚本
autocmd BufNewFile *.sh exec ":cal SetTitle()"
func SetTitle()
if expand("%:e") == 'sh'
call setline(1,"#!/bin/bash")
call setline(2,"#")
call setline(3,"#*****************************************************")
call setline(4,"#Author: minNexus")
call setline(5,"#QQ: 1299575088")
call setline(6,"#Date: ".strftime("%Y-%m-%d"))
call setline(7,"#FileName: .expand("%"))
call setline(8,"#URL: https://blog.csdn.net/qq_24472227?type=blog")
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
第一行一般为调用使用的语言
程序名,避免更改文件名为无法找到正确的文件
版本号
更改后的时间
作者相关信息
该程序的作用,及注意事项
最后是各版本的更新简要说明
[root@CentOS8 ~]#vim hello.sh
输入echo hello world
[root@CentOS8 ~]#./hello.sh 执行hello.sh
hello world
语法错误:导致后续命令不再执行
命令错误:后续命令仍然执行
逻辑错误
检测脚本中的语法错误,bash后加-n,提示出错行不一定准确,不执行
bash -n /path/to/some_script
一定程度上检查逻辑出错 与 命令出错,调试并执行,加-x,逐行执行并显示
bash -x /path/to/some_script
注意:
变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据
类型:
不同的变量存放的数据不同,决定了以下
变量数据类型:
静态和动态语言
强类型和弱类型语言
如:以下python代码
print('magedu'+ 10) 提示出错,不会自动转换类型
print('magedu'+str(10)) 结果为magedu10,需要显示转换类型
弱类型语言:语言的运行时会隐式类型转换。无须指定类型,默认均为字符型,参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
如: bash ,php,javascript
不能使程序中的保留字:如: if,for
只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线“.",和主机名相反
见名知义,用英文单词命名,并体现出实际作用,不要用简写,如: ATM
统一命名规则:驼峰命名法studentname,大驼峰StudentName ,小驼峰studentName
变量的生效范围等标准划分变量类型
变量赋值:
name='value'
value可以是以下多种形式
COMMAND
或者 name=$(COMMAND)注意:变量赋值临时生效,终端退出将自动删除,不过为了安全起见最好使用完手动删除。脚本中的变量也随脚本结束自动删除
变量引用:
$name
${name}
弱引用和强引用
"$name" 双引号,弱引用,其中的变量引用会被替换为变量值
'$name' 单引号,强引用,其中的变量引用不会被替换为变量值,而保持原字符串
显示已定义的所有变量
set
删除变量
unset name
例:
1:
[root@CentOS8 ~]#ls
f1.txt f2.txt f3.txt issue.bak passwd scripts test.img
[root@CentOS8 ~]#FILE=* 将当前文件夹所有文件名保存至变量FILE中
[root@CentOS8 ~]#echo $FILE
f1.txt f2.txt f3.txt issue.bak passwd scripts test.img
2:
[root@CentOS8 ~]#FILE="i am $HOSTNAME" 将hostname的变量名存入变量FILE中
[root@CentOS8 ~]#echo $FILE
i am CentOS8.Joyce.person1
3:
[root@CentOS8 ~]#seq 10
1
2
3
4
5
6
7
8
9
10
[root@CentOS8 ~]#NUM=`seq 10`
[root@CentOS8 ~]#echo $NUM
1 2 3 4 5 6 7 8 9 10
[root@CentOS8 ~]#echo "$NUM" 加双引号保留多行格式
1
2
3
4
5
6
7
8
9
10
环境变量:可以使子进程(包括子子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
一旦子进程修改从父进程继承的变量,新的值将传递给子子进程
一般只在系统配置文件中使用,在脚本中较少使用
声明环境变量 并赋值
export name=VALUE
declare -x name=VALUE
或分2步
name=VALUE
export name
变量引用
$name
${name}
例:
[root@CentOS8 ~]#NAME=joyce
[root@CentOS8 ~]#AGE=21
[root@CentOS8 ~]#echo $NAME$AGE
joyce21
[root@CentOS8 ~]#echo $NAME_$AGE
21
[root@CentOS8 ~]#echo ${NAME}_$AGE
joyce_21
显示所有环境变量
env
printenv
export
declare -x
删除变量:
unset name
bash内建的环境变量
PATH
SHELL
USER
UID
HOME
PWD
SHLVL 当前所在shell的深度,使用bash进入下一shell(开子进程)后,深度+1,最低是1
LANG 当前设置语言与编码格式
MAIL 邮箱位置
HOSTNAME
HISTSIZE
_ 下划线,表示前一命令的最后一个参数,前面记得加$
脚本:显示系统信息
RED="\E[1;31m"
GREEN="\E[1;32m"
END="\E[0m"
echo -e "\E[1;32m--------------------------------Host systeminfo------------------------------------$END"
echo -e "HOSTNAME: $RED`hostname`$END"
echo -e "IPADDR: $RED` ifconfig ens160|grep -Eo '([0-9]{1,3}\.){3}[0-9][1,3]' |head -n1`$END"
echo -e "OSVERSION: $RED`cat /etc/redhat-release`$END"
echo -e "KERNEL: $RED`uname -r`$END"
echo -e "CPU: $RED`lscpu|grep 'Model name'|tr -s ' '|cut -d : -f2`$END"
echo -e "MEMORY: $RED`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
echo -e "DISK: $RED`lsblk |grep '^sd' |tr -s ' ' |cut -d " " -f4`$END"
echo -e "\E[1;32m-----------------------------------------------------------------------------------$END"
只读变量:只能声明定义,但后续不能修改和删除,即常量,退出终端自动删除
声明只读变量: 加-r
readonly name
declare -r name
查看只读变量:
readonly [-p]
declare -r
位置变量:在bash shell中内置的变量,在脚本代码中调用通过命令行传递给脚本的参数
$1,$2,... 对应第1个、第2个等参数,shift [n]换位置
$0 命令本身,包括路径,想只显示名字可以写`basename $0`
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
注意: $@ $ 只在被双引号包起来的时候才会有差异*
清空所有位置变量
set --
例:
[root@CentOS8 ~]#cat arg.sh 查看arg.sh文件
#!/bin/bash
#
#*****************************************************************************
#Author: minNexus
#QQ: 1299575088
#Date: 2023-07-25
#FileName: arg.sh
#URL: https://blog.csdn.net/qq_24472227?type=blog
#Description: The test script
#Copyright (C): 2023 All rights reserved
#****************************************************************************
echo "lst arg is $l"
echo "2st arg is $2"
echo "3st arg is $3"
echo "10st arg is ${l0}"
echo "1lst arg is ${11}"
echo "The number of arg is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"
[root@CentOS8 ~]#bash arg.sh {a..z} 运行脚本,加上a..z参数
lst arg is
2st arg is b
3st arg is c
10st arg is
1lst arg is k
The number of arg is 26
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
All args are a b c d e f g h i j k l m n o p q r s t u v w x y z
The scriptname is arg.sh
进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功或失败,$?取值范例 0-255
例:
[root@CentOS8 ~]#curl www.google.com &> /dev/null
[root@CentOS8 ~]#echo $?
7
用户可以在脚本中使用以下命令自定义退出状态码
exit [n]
注意:
展开命令执行顺序:
把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明 ~
命令替换$()和``
再次把命令行分成命令词
展开文件通配* ? [abc]等等
准备I/0重导向 <、>
运行命令
防止扩展:
反斜线(\)会使随后的字符按原意解释
加引号来防止扩展:
单引号('')-防止所有扩展
双引号 ("")也可防止扩展,但是以下情况例外: $(美元符号)
变量扩展:
`` 反引号,命令替换
\ 反斜线,禁止单个字符扩展
! 叹号,历史命令替换
**set命令:**可以用来定制shell环境
$- 变量
h hashal,打开洗项后,Shell会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h洗项关闭
i interactive-comments,包含这个选项说明当前的 shel 是一个交式的 shel。所谓的交式shell,在脚本中i选项是关闭的
m monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等
B braceexpand,大括号{}扩展,set +B关闭{}的功能
H history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!"返回上最近的一个历史命令,“!n”返回第 n个历史命令
set 命今实现脚本安全 推荐打开-eu
-u 在扩展一个没有设置的变量时,显示错误信息,等同set -o nounset,避免出现删除整个目录
-e 如果一个命令返回一个非0退出状态值(失败)就退出,等同set -o errexit
-o option 显示,打开或者关闭选项
显示选项 set-o
打开选项 set -o选项
关闭选项 set +o选项
-x 当执行命令时,打印命令及其参数,类似bash -x
格式
printf "指定的格式" "文本1" "文本2"...
常用格式替换符
%s 字符串
%f 浮点格式
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
%c ASCII字符,即显示对应参数的第一个字符
%d,%i 十进制整数
%o 八进制值
%u 不带正负号的十进制值
%x 十六进制值 (a-f)
%X 十六进制值 (A-F)
%%
说明:%s中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,- 表示左对齐
常用转义字符:
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ 表示\本身
例:
[root@CentOS8 ~]#printf "(%.3f)\n" 1 2 3
(1.000)
(2.000)
(3.000)
[root@CentOS8 ~]#printf "%s\n%s" 1 2 3 4;echo
1
23
4
shell 支持算术运算,但只支持整数,不支持小数
bash中的算术运算:
+
-
*
/
% 取模,即取余数,%5的结果在0-4
** 乘方
乘法符号有些场景中需要转义
实现算术运算:
let var=算术表达式
var=$[算术表达式]
var=$((算术表达式))
var=$(expr argl arg2 arg3 ...) 注意:expr里*可能被当作通配符,乘法要用\*
declare -i var = 数值
echo'算术表达式'|bc
例:
[root@CentOS8 ~]#echo -e "\E[1;$[RANDOM%7+31]mhello\e[0m" 打印随机颜色的hello,颜色编号31-37
hello
[root@CentOS8 ~]#expr 99 \* 3
297
[root@CentOS8 ~]#echo "scale=3;3/7"|bc
.428
内建的随机数生成器变量
$RANDOM 取值范围 0-32767
增强型赋值:
+= i+=10 相当于 i=i+10
-= i-=j 相当于 i=i-j
*=
/=
%=
++ i++,++i 相当于 i=i+1
-- i--,--i 相当于 i=i-1
true false
1 0
与:& 与0为0,与1保留
1与1=1
1与0=0
0与1=0
0与0=0
或:| 同0为0
1或1=1
1或0=1
0或1=1
0或0=0
非:!
!1 =0 ! true
!0 =1 ! false
异或:^ 异或的两个值,相同为,假0;不同为真,非0。
1^1=0
1^0=1
0^1=1
0^0=0
两个数字异或,将结果与任意一个数字异或,结果是另一数字
12^8=4
8^4=12
12^4=8
例:
[root@CentOS8 ~]#i=i=10;j=20;echo i=$[i^(i^j)] j=$[j^(i^j)] 使用异或交换i和j的值
i=20 j=10
[root@CentOS8 ~]#i=10;j=20;i=$[i^j];j=$[i^j];i=$[i^j];echo i=$i,j=$j
i=20,j=10
短路运算
短路与
CMD1 短路与 CMD2
短路或
CMD1 短路或CMD2
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程,实现评估布尔声明,以便用在条件性环境下进行执行
条件测试命令
test EXPRESSION 等价于 [ EXPRESSION ] 推荐使用[ ]
[[ EXPRESSION ]]
注意:EXPRESSION前后必须有空白字符
-v VAR 变量VAR是否设置,即是否存在
-R VAR 变量VAR是否设置并引用
示例:判断NAME变量是否定义
[root@CentOS8 ~]#[ -v name ] 或 test -v NAME
[root@CentOS8 ~]#echo $?
1
-eq 是否等于
-ne 是否不等于
-gt 是否大于
-ge 是否大于等于
-lt 是否小于
-le 是否小于等于
例:
[root@CentOS8 ~]#x=33
[root@CentOS8 ~]#y=44
[root@CentOS8 ~]#[ $x -gt $y ] 必须假$
[root@CentOS8 ~]#echo $?
1
[root@CentOS8 ~]#[ $x -lt $y ]
[root@CentOS8 ~]#echo $?
0
-z "STRING" 字符串是否为空,未定义或空为真,不空为假
-n "STRING" 字符串是否不空,不空为真,空为假
= 是否等于
!= 是否不等于
ascii码是否大于ascii码
< 是否小于
== 左侧字符串是否和右侧的PATTERN相同
注意:此表达式用于[[ ]]中,PATTERN为通配符
=~ 左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配
注意: 此表达式用于[[ ]]中;扩展的正则表达式
[root@CentOS8 ~]#name="hello babe"
[root@CentOS8 ~]#[ $name ]
-bash: [: hello: unary operator expected
[root@CentOS8 ~]#[ "$name" ]
[root@CentOS8 ~]#echo $?
0
例:
[root@CentOS8 ~]#title1=ceo
[root@CentOS8 ~]#title1=cto
[root@CentOS8 ~]#[ $title1 = $title2 ] 字符串之间必须加空格 ,不加空格则成赋值
[root@CentOS8 ~]#echo $?
1
2通配符
[root@CentOS8 ~]#FILE=test.log
[root@CentOS8 ~]#[[ "$FILE == *.log" ]]
[root@CentOS8 ~]#echo $?
0
3正则表达式
[root@CentOS8 ~]#FILE=test.log
[root@CentOS8 ~]#[[ "$FILE" =~ \.log$ ]]
[root@CentOS8 ~]#echo $?
0
[root@CentOS8 ~]#IP=1.2.3.444 判断ip地址是否合法(在255.255.255.255之间)
[root@CentOS8 ~]#[[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
[root@CentOS8 ~]#echo $?
1
[root@CentOS8 ~]#IP=1.2.3.4
[root@CentOS8 ~]#echo $?
0
存在性测试
-a FILE 同 -e
-e FILE 文件存在性测试,存在为真,否则为假
-b FILE 是否存在且为块设备文件
-C FILE 是否存在且为字符设备文件
-d FILE 是否存在且为目录文件
-f FILE 是否存在且为普通文件,判断软链接则判断软链接指定的文件
-h FILE 或 -L FILE: 存在且为符号链接文件
-p FILE 是否存在且为命名管道文件
-S FILE 是否存在且为套接字文件
例:
[root@CentOS8 ~]#[[ -c /dev/zero ]] 判断是否为字符文件
[root@CentOS8 ~]#echo $?
0
文件权限测试.
-r FILE 是否存在且可读
-W FILE 是否存在且可写
-X FILE 是否存在且可执行
-u FILE 是否存在且拥有suid权限
-g FILE 是否存在且拥有sgid权限
-k FILE 是否存在且拥有sticky权限
例:
[root@CentOS8 ~]# [[ -x CHOOK_RABBIT.sh ]] 判断是否有执行权限
[root@CentOS8 ~]#echo $?
0
[root@CentOS8 ~]#chmod 0 CHOOK_RABBIT.sh 去除所有权限
[root@CentOS8 ~]# [[ -x CHOOK_RABBIT.sh ]] 判断是否有执行权限
[root@CentOS8 ~]#echo $?
1
[root@CentOS8 ~]# [[ -r CHOOK_RABBIT.sh ]] 判断是否有可读权限,因为我们是root,所有仍然可以读
[root@CentOS8 ~]#echo $?
0
[root@CentOS8 ~]# [[ -w CHOOK_RABBIT.sh ]] 判断是否有写入权限,因为我们是root,所有仍然可以写
[root@CentOS8 ~]#echo $?
0
文件属性测试
-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
(CMD1;CMD2;...) 都可以批量执行多个命令
{ CMD1;CMD2;...; }最后加;,{ }内前后加空格
()会开启子shell,继承父进程的变量。list中变量赋值及内部命令执行后,不影响后续的环境。帮助参看:man bash 搜索(list)
{;}不会启子shell,在当前shell中运行,会影响当前shell环境,帮助参看:man bash 搜索{list;}
例:
[root@CentOS8 ~]#name=babe;(echo $name;name=hello;echo $name);echo $name 使用()
babe
hello
babe
[root@CentOS8 ~]#name=babe;{ echo $name;name=hello;echo $name; };echo $name 使用{}
babe
hello
hello
EXPRESSION1 -a EXPRESSION2 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
EXPRESSION1 -o EXPRESSION2 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
[ ! EXPRESSION ] 或 ![ exp ] 取反
说明: -a和-0 需要使用测试命令进行,[[]]不支持
COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN
如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2
COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE
如果COMMAND1 成功,将不执行COMMAND2否则,将执行COMMAND2
!COMMAND 非,取反
例:
[root@CentOS8 ~]#NAME=joycee;id $NAME &> /dev/null && echo "$NAME is exist" || echo "$NAME is not exist"
joycee is not exist
[root@CentOS8 ~]#NANAME=joycee;id $NAME &> /dev/null && echo "$NAME is exist" || (useradd $NAME;echo $NAME is created)
joycee is created
注意:&& 如果和 || 混用,则&& 要放前,|| 放后
使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量
格式
read [options] [name ...]
常见选项:
-p 指定要显示的提示
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d '字符',输入结束符
-t N TIMEOUT为N秒
[root@CentOS8 ~]#read
joyce
[root@CentOS8 ~]#echo $REPLY
joyce
[root@CentOS8 ~]#read NAME TITLE
joyce babe
[root@CentOS8 ~]#echo $NAME $TITLE
joyce babe
[root@CentOS8 ~]#read -p "are u ok?" ANSWER
are u ok?not bad
[root@CentOS8 ~]#echo $ANSWER
not bad
[root@CentOS8 ~]#echo 1 2 > test.txt 重定向
[root@CentOS8 ~]#read a b < test.txt;echo $a; echo $b
1
2
注意:
[root@CentOS8 ~]#echo 1 2 | read a b | echo $a $b
1 2
[root@CentOS8 ~]#echo 1 2 | (read a b; echo $a $b)
1 2
[root@CentOS8 ~]#echo 1 2 | read a b; echo $a $b 管道里,如果不加括号或|,则中间和后面是两个独立的shell,因此二者的ab不等
1 2
bash shell的配置文件很多,可以分成下面类别
全局配置
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置:
~/.bash_profile
~/.bashrc
su - UserName
切换用户配置文件执行顺序:
/etc/profile --> /etc/profile.d/*,sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
su UserName
图形界面下打开的终端
执行脚本
任何其它的bash实例
执行顺序:
/etc/profile.d/*.sh --> /etc/bashrc -->~/.bashrc
profile类和bashrc类
profile类为交互式登录的shell提供配置
全局: /etc/profile,/etc/profile.d/*.sh
个人:~/.bash_profile
作用:
bashrc类:为非交互式和交互式登录的shell提供配置
全局: /etc/bashrc
个人:~/.bashrc
作用:
修改profile和bashrc文件后有生效两种方法
保存在~/.bash_logout文件中 (用户),在退出登录shell时运行
功能
条件判断:
if
case
&& ||
格式:
if CMDs; then CMDs;[ elif CMDs; then CMDs; ]... [ else CMDs; ] fi
单分支:
if 判断条件;then
条件为真的分支代码
fi
双分支:
if 判断条件; then
条件为真的分支代码
e1se
条件为假的分支代码
fi
多分支:
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
...
e1se
以上条件都为假的分支代码
fi
说明:
格式:
case WORD in [PATTERN [| PATTERN]...) CMDs ;;]...esac
此处PATTERN指通配符的模式
case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
默认分支
;;
esac
case支持glob风格的通配符:
* 任意长度任意字符
? 任意单个字符
[] 指定范围内的任意单个字符
| 或,如 a或b
将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件
重复运行次数
常见的循环的命令:for,while,until
for NAME [in WORDS ...] ; do COMMANDS; done
(WORD之间使用空白符(空格、Tab键、回车)分割)
或
for 变量名 in 列表;do
循环体
done
或
for 变量名 in 列表
do
循环体
done
执行机制:依次将列表中的元素赋值给“变量名”, 每次赋值后即执行一次循环体;直到列表中的元素耗尽,循环结束
for循环列表生成方式:
{start..end}
$(seq [start [step]] end)
$(COMMAND)
死循环:
for((;;)) ; do CMDs;done
例:
[root@CentOS8 ~]#for i in {001..8..2};do echo $i; done
001
003
005
007
[root@CentOS8 ~]#for i in `seq 5`;do echo $i; done
1
2
3
4
5
[root@CentOS8 ~]#for i in $(seq 5);do echo $i; done
1
2
3
4
5
例:计算1+2+3+…+100的和
[root@CentOS8 ~]#seq -s+ 100|bc
5050
或
[root@CentOS8 ~]#sum=0;for i in {1..100};do let sum+=i;done;echo $sum
5050
例:
[root@CentOS8 scripts]#cat sum.sh
#!/bin/bash
sum=0
for i in $@ ;do
let sum+=i
done
echo $sum
[root@CentOS8 scripts]#chmod +x sum.sh
[root@CentOS8 scripts]#./sum.sh 1 3 4 5 6
19
例:九九乘法表的实现
[root@CentOS8 scripts]#cat ./9x9.sh
#!/bin/bash
for j in {1..9};do
for i in `seq $j`;do
echo -e "${i}x$j=$((i*j))\t\c"
done
echo
done
[root@CentOS8 scripts]#./9x9.sh
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
1x4=4 2x4=8 3x4=12 4x4=16
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
面试题:要求将目录YYYY-MM-DD/中所有文件,移动到YYYY-MM/DD/下
#1 yyyy-mm-dd10.sh ,准备数据
#创建YYYY-MM-DD,当前日期一年前365天到目前共365个目录,里面有10个文件,$RANDOM.Tog
[root@centos8 ~]#cat for_dir20.sh
#!/bin/bash
for i in {1..365};do
DIR=`date -d "-$i day" +%F`
mkdir /data/test/$DIR
cd /data/test/$DIR
for n in {1..10};do
touch $RANDOM.log
done
done
#2 移动到YYYY-MM/DD/下
[root@centos8 ~]#cat for_mv20.sh
#!/bin/bash
#
DIR=/data/test
cd $DIR
for DIR in *;do
YYYY_MM=`echo $DIR |cut -d"-" -f1,2` #将年月和日期拆分成2部分,这里是年月
DD=`echo $DIR | cut -d"-" -f3` #这里是日期
[ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/null
mv $DIR/* $YYYY_MM/$DD
done
面试题:扫描一个网段:10.0.0.0/24,判断此网段中主机在线状态,将在线的主机的IP打印出来
NET=10.0.0
for ID in {1..254};do
{
ping -c1 -W1 $NET.$ID &> /dev/null && echo $NET.$ID is up || echo $NET.$ID is down
}& #实现并行
done
wait #默认并行不会自动退出,加上wait使其自动执行下一个命令
双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的变量操作
|=10;((l++))
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done
说明:
例:实现1累加到100
for ((sum=0;i<=100;i++));do
let sum+=i
done
echo $sum
例:九九乘法表的实现_2
for ((i=1;i<10;i++));do
for((j=1;j<=i;j++));do
echo -e "${j}x$i=$((j*i))\t\c"
done
echo
done
例:实现国际象棋的棋盘(较垃圾)
for ((i=1;i<=8;i++));do
if((i%2));then
for((x=0;x<4;x++));do
for ((j=0;j<8;j++));do
if ((j%2));then
echo -e '\E[47m \E[0m\c' ;
else
echo -e '\E[40m \E[0m\c' ;
fi
done
echo
done
else
for((x=0;x<4;x++));do
for ((j=0;j<8;j++));do
if ((j%2));then
echo -e '\E[40m \E[0m\c' ;
else
echo -e '\E[47m \E[0m\c' ;
fi
done
echo
done
fi
done
例:实现三角形
for((i=1;i<=10;i++));do
#for((z=0;z<=10-i;z++));do
for((z=10;z-i>0;z--));do
echo -e ' \c';
done
for((j=1;j<=2*i-1;j++));do
echo -e '*\c'
done
echo
done
read -p "请输入三角形的行数:" line
for((i=1;i<=$line;i++));do
#for((z=0;z<=10-i;z++));do
for((z=$line;z-i>0;z--));do
echo -e ' \c';
done
for((j=1;j<=2*i-1;j++));do
echo -e '*\c'
done
echo
done
格式:
while COMMANDs; do COMMANDS; done
while CONDITION; do
循环体
done
说明:
CONDITION:循环控制条件,进入循环之前,先做一次判断,每一次循环之后会再次做判断;条件为“true",则执行一次循环,直到条件测试状态为“false"终止循环,因此:CONDTION一般应该有循环控制变量,而此变量的值会在循环体不断地被修正
死循环:
#方法1
while true; do
循环体
done
#方法2
while :; do
循环体
done
范例:使用while实现磁盘报警
WARNING=10 #阈值
while : ;do
USE=`df | sed -rn '/^\/dev\/sd/s#.* ([0-9]+)%.*#\1#p' |sort -nr | head -n1`
if [ $USE -gt $WARNING ] ; then
echo DISK will be full from `hostname -I` | mail -s "Disk Warning" 1299575088@qq.com
fi
sleep 10 #10秒循环一次
done
例:死循环
[root@CentOS8 scripts]#while :;do echo ok ;sleep 1 ;done
ok
ok
ok
ok
ok
格式:
until COMMANDS; do COMMANDS; done
until CONDITION; do
循环体
done
说明:
死循环
until false; do
循环体
done
continue[N]:提前结束第N层的本轮循环,而直接进入下一轮判断,最内层为第1层
格式:
while CONDITION1; do
CMD1
...
if CONDITION2; then
continue
CMDn
...
done
例:
#1
for((i=0;i<10;i++));do
for((j=0;j<10;j++));do
[ $j -eq 5 ] && continue 1 #1可以不写,代表终止该层循环的本次循环
echo $j
done
echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
0
1
2
3
4
6
7
8
9
-----------------
#2
for((i=0;i<10;i++));do
for((j=0;j<10;j++));do
[ $j -eq 5 ] && continue 2 #2代表终止外层循环的本次循环,使外层循环进入下次循环
echo $j
done
echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
break [N]:提前结束第N层整个循环,最内层为第1层
格式:
while CONDITION1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
例:
#1
for((i=0;i<10;i++));do
for((j=0;j<10;j++));do
[ $j -eq 5 ] && break 1 #1可以不写,表示结束内层循环,使外层循环进入下一轮
echo $j
done
echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
0
1
2
3
4
-----------------
#2
for((i=0;i<10;i++));do
for((j=0;j<10;j++));do
[ $j -eq 5 ] && break 2 #2表示结束外层循环,即结束所有循环
echo $j
done
echo -----------------
done
[root@CentOS8 scripts]#bash continue_for_1.sh
0
1
2
3
4
例:菜品选择
sum=0
COLOR1='echo -e \033[1;31m'
COLOR2='echo -e \033[1;32m'
END="\033[0m"
while :;do
echo -e "\033[33;1m\c"
cat <<-EOF
1)米
2)面
3)粉
4)汤
5)谷
6)肉
EOF
echo -e "\033[0m"
read -p "请选择菜品:" MENU
case $MENU in
1|4)
$COLOR1'菜价:$10'$END
let sum+=10
;;
3|5)
$COLOR1'菜价:$20'$END
let sum+=20
;;
2)
$COLOR1'菜价:$888'$END
let sum+=888
;;
6)
$COLOR2"你点的菜品总价是:$sum"$END
break
;;
*)
echo "输入错误!"
;;
esac
$COLOR2"你点的菜品总价是:$sum"$END
done
shift[n]用于将参量列表list 左移指定次数,缺省为左移一次。
参量列表list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift
例:shift批量创建用户
PASS=110420
while [ "$1" ];do
useradd $1 && echo $1 is created || echo $1 is exists
echo $PASS | passwd --stdin $1
shift #shift 1 一次只跳过1个,2则跳过2个
done
[root@CentOS8 scripts]#bash shift_user.sh Tom Alice
Tom is created
Changing password for user Tom.
passwd: all authentication tokens updated successfully.
Alice is created
Changing password for user Alice.
passwd: all authentication tokens updated successfully.
例:判断某个ip访问的次数
[root@CentOS8 data]#sed -En '/^ESTAB/s#.*[: ]([^:]+):[0-9]+ $#\1#p' ss.log | sort |uniq -c
1 10.0.0.1
200 10.0.0.8
while循环的特殊用法,遍历文件或文本的每一行,即逐行处理
支持stdin
格式
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
例:
[root@centos8 ~]#echo joyce | read X ; echo $X # 管道|开启子进程
[rootcentos8 ~]#echo joyce | while read X ; do echo $X;done
joyce
[root@centos8 ~]#echo joyce | { read X ; echo $X; }
joyce
[root@centos8 ~]#echo joyce | ( read X ; echo $X )
joyce
[root@centos8 ~]#echo joyce wang zhang | ( read X Y Z; echo $X $Y $Z )
joyce wang zhang
[root@centos8 ~]#echo joyce wang zhang | while read X Y Z; do echo $X $Y $Z;done
joyce wang zhang
例:使用while read实现磁盘报警
WARNING=10
MAIL=1299575088@qq.com
df |sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p" |while read DEVICE USE;do
if [ $USE -gt $WARNING ] ;then
echo "$DEVICE will be full,use:$USE" | mail -s "DISK WARNING" $MAIL
fi
done
例:查看/sbin/nologin的shell类型的用户名和UID
while read line ;do
if [[ "$line" =~ /sbin/nologin$ ]] ; then
echo $line | cut -d: -f1,3
fi
done < /etc/passwd
例:存放大量单词文件
[root@CentOS8 scripts]#wc -l /usr/share/dict/linux.words
479828 /usr/share/dict/linux.words
格式:
select NAME [in WORDS ... ;] do COMMANDS; done
select variable in list ;do
循环体命令
done
说明
函数由两部分组成:函数名和函数体
帮助参看:help function
function name { COMMANDS ; } or name () { COMMANDS ; }
#语法一:
func_name () {
...函数体...
}
#语法二:
function func_name {
...函数体...
}
#语法三:
function func_name () {
...函数体...
}
例:创建disable_firewall_selinux函数以关闭firewalld和selinux
[root@centos8_3 ~]#systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2023-08-12 22:57:44 CST; 6min ago
[root@centos8_3 ~]#getenforce
Enforcing
# 函数主体:
[root@centos8_3 ~]#disable_firewall_selinux () {
> systemctl stop firewalld
> systemctl disable firewalld
> sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
> setenforce 0
> }
[root@centos8_3 ~]#disable_firewall_selinux
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@centos8_3 ~]#systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
Active: inactive (dead)
[root@centos8_3 ~]#getenforce
Permissive
注意:函数操作后确实修改了文件,因此重启后仍然保存。但是函数本身一次性使用,重启后消失,因此想要保存函数需要写入到文件
例:分装函数与调用于2个文件,可直接调用函数
#funtions文件,保存函数主体
[root@centos8_3 scripts]#cat functions
#函数1:disable_firewall_selinux()
disable_firewall_selinux () {
systemctl stop firewalld
systemctl disable firewalld
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0
}
#函数2:yum_repo()
yum_repo() {
cd /etc/yum.repos.d/
mkdir backup
mv *.repo backup
cat > base.repo <<EOF
[BaseOS]
name=aliyun BaseOS
baseurl = https://mirrors.aliyun.com/centos/8/BaseOS/x86_64/os/
https://repo.huaweicloud.com/centos/8-stream/BaseOS/x86_64/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
[AppStream]
name=aliyun AppStream
baseurl = https://mirrors.aliyun.com/centos/8/AppStream/x86_64/os/
https://repo.huaweicloud.com/centos/8-stream/AppStream/x86_64/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
[epel]
name=Extra Packages for Enterprise Linux \$releasever - \$basearch
# It is much more secure to use the metalink, but if you wish to use a local mirror
# place it's address here.
#baseurl=https://download.example/pub/epel/\$releasever/Everything/\$basearch
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-\$releasever&arch=\$basearch&infra=\$infra&content=\$contentdir
enabled=1
gpgcheck=1
countme=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8
[epel-debuginfo]
name=Extra Packages for Enterprise Linux \$releasever - \$basearch - Debug
# It is much more secure to use the metalink, but if you wish to use a local mirror
# place it's address here.
#baseurl=https://download.example/pub/epel/\$releasever/Everything/\$basearch/debug
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-debug-\$releasever&arch=\$basearch&infra=\$infra&content=\$contentdir
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8
gpgcheck=1
[epel-source]
name=Extra Packages for Enterprise Linux \$releasever - \$basearch - Source
# It is much more secure to use the metalink, but if you wish to use a local mirror
# place it's address here.
#baseurl=https://download.example/pub/epel/\$releasever/Everything/SRPMS
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-source-\$releasever&arch=\$basearch&infra=\$infra&content=\$contentdir
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-8
gpgcheck=1
EOF
}
#函数3:install_package()
install_package() {
packages="
vim
tree
autofs
net-tools
apache
httpd
"
for i in $packages;do
rpm -q $i &> /dev/null || yum -q -y install $i
done
}
#disable_firewall_selinux.sh文件,调用disable_firewall_selinux函数
[root@centos8_3 scripts]#cat disable_firewall_selinux.sh
#!/bin/bash
. functions #引用functions文件
disable_firewall_selinux
#yum_repo.sh文件,调用yum_repo函数
[root@centos8_3 scripts]#cat yum_repo.sh
#!/bin/bash
. functions
yum_repo
#install_package.sh文件,调用install_package函数
[root@centos8_3 scripts]#cat install_package.sh
#!/bin/bash
. functions
install_package
#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
格式:
unset func_name
函数的调用方式
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止。
不过调用函数时,如果函数内外有同名变量,调用函数后会修改函数内外的变量,类似于全局变量,如果要使函数只修改函数内部的变量而不影响函数外的同名变量,可以在函数内声明时加上local,使其成为本地变量,如local NAME=1
交互式环境下定义和使用函数
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使用其函数名即可
例:调用系统函数
[root@CentOS8 ~]#. /etc/init.d/functions
[root@CentOS8 ~]#action "you an me"
you an me [ OK ]
[root@CentOS8 ~]#action "you an medsadsad" false
you an medsadsad [FAILED]
默认情况下,我们使用exit 100,会返回100,但用在函数里,会使整个脚本退出而不执行下面的代码。
因此使用return #返回值,可只退出当前函数而不退出脚本,不过只在函数中使用
函数的执行结果返回值
函数的退出状态码
默认取决于函数中执行的最后一条命令的退出状态码
自定义退出状态码,其格式为
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回
类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
定义环境函数:
export -f function_name
declare -xf function_name
查看环境函数:
export -f
declare -xf
函数可以接受参数:
变量作用域:
注意:
在函数中定义本地变量的方法
local NAME=VALUE
函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环
递归示例:阶乘
阶乘是基斯顿·卡曼于1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!
n!=1x2x3x…xn
阶乘亦可以递归方式定义: 0!=1,n!=(n-1)!xn
n!=n(n-1)(n-2)…1
n(n-1)!=n(n-1)(n-2)!
例:递归实现阶乘
[root@CentOS8 functions]#cat fact.sh
fact() {
if [ $1 -eq 1 ];then
echo 1
else
echo $[`fact $[$1-1]`*$1]
fi
}
fact $1
[root@CentOS8 functions]#bash fact.sh 5
120
fork炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
参考:https://en.wikipedia.org/wiki/Fork_bomb
函数实现
:(){ :|:& };: #无基例
bomb () {} bomb | bomb & }; bomb
脚本实现
cat Bomb.sh
#!/bin/bash
./$0|./$0&
OOM:内存溢出
trap ‘触发指令’ 信号
进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap" 信号
忽略信号的操作
trap ‘-’ 信号
恢复原信号的操作
trap -p
列出自定义信号操作
trap finish EXIT
当脚本退出时,执行finish函数
例:
[root@CentOS8 functions]#cat signal_trap1.sh
trap 'echo "Press ctrl+c"' int quit #将int 和quit信号改为echo "Press ctrl+c"
trap -p
for((i=1;i<=10;i++))
do
sleep 1
echo $i
done
trap '' int #将int信号改为空,即忽略
trap -p
for((i=11;i<21;i++ ))
do
sleep 1
echo $i
done
trap '-' int #恢复int信号,接下来遇到int信号将执行ctrl+c
trap -p
for(( i=21;i<31;i++))
do
sleep 1
echo $i
done
[root@CentOS8 functions]#bash signal_trap1.sh
trap -- 'echo "Press ctrl+c"' SIGINT
trap -- 'echo "Press ctrl+c"' SIGQUIT
1
^CPress ctrl+c
2
^CPress ctrl+c
3
^CPress ctrl+c
4
^CPress ctrl+c
5
^CPress ctrl+c
6
^CPress ctrl+c
7
^CPress ctrl+c
8
^CPress ctrl+c
9
^CPress ctrl+c
10
trap -- '' SIGINT
trap -- 'echo "Press ctrl+c"' SIGQUIT
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^[[A11
^[[A12
^[[A^[[A^[[A^C13
^C^C^C^C^C^C^C^C^C^C^C14
15
16
17
18
19
20
trap -- 'echo "Press ctrl+c"' SIGQUIT
21
22
23
24
25
26
^C
mktemp命令用于创建并显示临时文件,可避免冲突
格式
mktemp [OPTION]... [TEMPLATE]
说明:TEMPLATE:filenameXXX,X至少要出现三个
常见选项:
-d 创建临时目录
-p DIR或--tmpdir=DIR 指明临时文件所存放目录位置
例:
#默认放在tmp目录下
[root@CentOS8 functions]#mktemp
/tmp/tmp.B9CHlgxwCL
[root@CentOS8 functions]#mktemp
/tmp/tmp.ur2MYKCZhU
[root@CentOS8 ~]#mktemp -d tmpdirXXXX #在当前目录下生成临时目录
tmpdirqWDe
[root@CentOS8 ~]#ll tmpdirqWDe -d
drwx------ 2 root root 6 Aug 13 14:51 tmpdirqWDe
例:实现rm的进阶写法
[root@CentOS8 scripts]#cat rm2.sh
DIR=`mktemp -d /tmp/trash-$(date +%F_%H-%M-%S)XXXXX`
mv $* $DIR
echo $* is move to $DIR
[root@CentOS8 scripts]#bash rm2.sh 123 321
123 321 is move to /tmp/trash-2023-08-13_14-59-58BDIIO
[root@CentOS8 scripts]#ls /tmp/trash-2023-08-13_14-59-58BDIIO/
123 321
[root@CentOS8 scripts]#alias rm=/data/scripts/rm2.sh
[root@CentOS8 scripts]#touch 1
[root@CentOS8 scripts]#chmod +x rm2.sh
[root@CentOS8 scripts]#rm 1
1 is move to /tmp/trash-2023-08-13_15-05-24kBeQl
功能相当于cp、chmod、chown、chgrp等相关工具的集合
install命令格式
install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
insta11 [OPTION]... -d DIRECTORY...创建空目录
选项:
-m MODE,默认755 #改权限
-o OWNER #改所有者
-g GROUP #改所属组
-d DIRNAME
例:
[root@CentOS8 ~]#ll cal.txt
-rw-r--r-- 1 root root 0 Jul 28 19:29 cal.txt
[root@CentOS8 ~]#install -m 666 -o joyce -g bin cal.txt /data/test.txt
[root@CentOS8 ~]#ll /data/test.txt
-rw-rw-rw- 1 joyce bin 0 Aug 13 15:12 /data/test.txt
#创建空文件夹
[root@CentOS8 ~]#install -m 700 -o joyce -g daemon -d /data/testdir/
[root@CentOS8 ~]#ll -d /data/testdir/
drwx------ 2 joyce daemon 6 Aug 13 15:13 /data/testdir/
expect 是由 Don Libes 基于 TcI ( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助 expect处理交互的命令,可以将交互过程如:ssh登录,tp登录等写在一个脚本上,使之自动化完成尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率
expect 语法:
expect [选项] [ -c cmds ] [ [ -[fIb] ] cmdfile ] [ args ]
常见选项:
-c 从命令行执行expect脚本,默认expect是交互地执行的
-d 可以输出输出调试信息
示例:
expect -c 'expect "\n" {send "pressed enter\n"}'
expect -d ssh.exp
expect中相关命令
spawn 启动新的进程
expect 从进程接收字符串
send 用于向进程发送字符串
interact 允许用户交互
exp_continue 匹配多个字符串在执行动作后加此命令
expect最常用的语法 ( tcl语言:模式-动作 )
单一分支模式语法:只生效一次,遇到hi输出后即结束
[root@centos8 test]#expect
expect1.1> expect "hi" {send "You said hi\n"}
hahahixixi
You said hi
例:非交互式发送文件至第二个主机,自动输入yes和密码
#!/usr/bin/expect #注意,不是bash
spawn scp /etc/fstab 10.0.0.7:/data
expect {
"yes/no" { send "yes\n",exp_continue }
"password" { send "110420\n" }
}
expect eof
例:非交互式自动登录至第二个ip主机,自动输入yes和密码
[root@centos8 scripts]#cat expect2
#!/usr/bin/expect
spawn ssh 10.0.0.209
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "110420\n" }
}
interact
[root@CentOS8 scripts]#chmod +x expect1.sh
[root@CentOS8 scripts]#./expect1.sh
spawn ssh 10.0.0.209
[email protected]'s password:
hello Joyce!
Nna
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Sun Aug 13 15:48:51 2023 from 10.0.0.201
[root@centos8_2 ~]#hostname
centos8_2
例:带有参数的自动登录其他ip主机的脚本,自动输入yes和密码
[root@CentOS8 scripts]#cat ./expect2.sh
#!/usr/bin/expect
set ip 10.0.0.209
set user root
set password 110420
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact #代表可以进行交互
[root@CentOS8 scripts]#chmod +x expect2.sh
[root@CentOS8 scripts]#./expect2.sh
spawn ssh [email protected]
[email protected]'s password:
hello Joyce!
Nna
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Sun Aug 13 15:49:06 2023 from 10.0.0.201
例:使用位置参数实现自动登录其他ip的主机
[root@CentOS8 scripts]#cat ./expect3.sh
#!/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
[root@CentOS8 scripts]#chmod +x expect3.sh
[root@centos8 scripts]#./expect3 10.0.0.209 root 110420
spawn ssh roota10.0.0.7
roota10.0.0.209's password:
Last login: wed Apr 29 15:34:14 2020 from 10.0.0.8
例:远程登陆主机并创建账号设置密码,完了退出
#!usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password"{ send"$password\n" }
}
# ]#即登陆后输入内容前面的标识符
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo 110420 |passwd --stdin haha\n" }
send "exit\n"
expect eof
例:在bash脚本里调用expect以远程登录主机并创建账号设置密码
[root@CentOS8 scripts]#cat ./expect5.sh
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password"{ send"$password\n" }
}
# ]#即登陆后输入内容前面的标识符
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo 110420 |passwd --stdin haha\n" }
send "exit\n"
expect eof
EOF
[root@CentOS8 scripts]#bash expect5.sh 10.0.0.209 root 110420
例:批量登录不同主机并创建用户设置密码
#!/bin/bash
NET=10.0.0
user=root
password=110420
for ID in 209 210;do #循环遍历不同IP的主机
ip=$NET.$ID
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "#" { send "useradd test\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done
例:批量登录不同主机并修改selinux为disabled
#!/bin/bash
NET=10.0.0
user=root
password=110420
for ID in 209 210;do #循环遍历不同IP的主机
ip=$NET.$ID
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "#" { send "sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/confiig\n" }
expect "#" { send "setenforce 0\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
注意:两者不可相互转换
数组元素的赋值
一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
例:
[root@CentOS8 ~]#title[0]=ceo
[root@CentOS8 ~]#echo ${title}
ceo
一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3"...)
例:
title=("ceo""coo""cto")
num=({0..10})
alpha=({a..g})
file=( *.sh )
只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
交互式数组值对赋值
read -a ARRAY
[root@CentOS8 ~]#read -a number
1 3 5 7 9
[root@CentOS8 ~]#echo ${number[*]}
1 3 5 7 9
[root@CentOS8 ~]#echo ${number[2]}
5
[root@CentOS8 ~]#echo ${number}
1
显示所有数组
declare -a
引用数组元素
${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素
引用数组所有元素
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
例:
[root@CentOS8 ~]#NUM=({1..5})
[root@CentOS8 ~]#echo ${NUM[3]}
4
[root@CentOS8 ~]#echo ${NUM[2]}
3
[root@CentOS8 ~]#echo ${NUM[*]}
1 2 3 4 5
[root@CentOS8 ~]#echo ${NUM[@]}
1 2 3 4 5
例:
[root@CentOS8 ~]#read -a number
1 3 5 7 9
[root@CentOS8 ~]#echo ${number[*]}
1 3 5 7 9
[root@CentOS8 ~]#echo ${number[2]}
5
[root@CentOS8 ~]#echo ${number}
1
删除数组中的某元素,会导致稀疏格式
unset ARRAY[INDEX]
例:
[root@centos8 ~]#echo $title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo $ftitle[*]]
ceo cto
删除整个数组
unset ARRAY
例:
[root@centos8 ~]#unset title
[root@centos8 ~]#echo ${title[*]}
数组切片
${ARRAY[@]:offset:number}
offset #要跳过的元素个数
number #要取出的元素个数
{ARRAY[@]:offset} #取偏移量之后的所有元素
例:
[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos8 ~]#echo ${num[*]:6}
6 7 8 9 10
向数组中追加元素:
ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value
例:
[root@CentOS8 ~]#num=({0..10})
[root@CentOS8 ~]#echo ${num[2]}
2
[root@CentOS8 ~]#echo ${num[*]}
0 1 2 3 4 5 6 7 8 9 10
[root@CentOS8 ~]#num[11]=11
[root@CentOS8 ~]#echo ${num[*]}
0 1 2 3 4 5 6 7 8 9 10 11
[root@CentOS8 ~]#echo ${#num[*]}
12
[root@CentOS8 ~]#num[${#num[*]}]=12
[root@CentOS8 ~]#echo ${#num[*]}
13
[root@CentOS8 ~]#echo ${num[*]}
0 1 2 3 4 5 6 7 8 9 10 11 12
declare -A ARRAY_NAME
ARRAY_NAME=([idx_namel]='val1' [idx_name2]='val2'...
注意:关联数组必须先声明再调用
例:
[root@CentOS8 ~]#declare -A name
[root@CentOS8 ~]#name[first]=Tom
[root@CentOS8 ~]#name[second]=Jack
[root@CentOS8 ~]#name[third]=Case
[root@CentOS8 ~]#echo ${name[first]}
Tom
[root@CentOS8 ~]#echo ${name[*]}
Case Jack Tom
[root@CentOS8 ~]#echo ${name[third]}
Case
例:生成包含10个随机数的数组,并显示其中的最大值MAX和最小值MIN
[root@CentOS8 test]#cat random1.sh
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo min is $min
[root@CentOS8 test]#bash random1.sh
All numbers are 23203 6083 14719 18022 31925 25880 9039 10908 30266 23092
Max is 31925
min is 6083
基于偏移量取字符串
#返回字符串变量var的长度
${#var}
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0到${#var}-1 之间(bash4.2后,允许为负值)
${var:offset]
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var:offset:number}
#取字符串的最右侧几个字符,取字符串的最右侧几个字符,注意: 冒号后必须有一空白字符
${var: -length}
#从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var:offset:-length}
#先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意: -length前空格
${var: -length:-offset}
例:
[root@CentOS8 test]#str=123abc哇哦
[root@CentOS8 test]#echo ${#str}
8
[root@CentOS8 test]#echo ${str:5}
c哇哦
[root@CentOS8 test]#echo ${str:5:2}
c哇
[root@CentOS8 test]#echo ${str: -2}
哇哦
[root@CentOS8 test]#echo ${str:3:-2}
abc
[root@CentOS8 test]#echo ${str: -3:-1} #取倒数3个和倒数1个之间的2个
c哇
基于模式取子串
从左往右:
#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word,删除字符串开头至第一次出现word字符串 (含) 之间的所有字符
${var#*word}:
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
${var##*word}:
例:
[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file#*/}
log/messages
[root@centos8 ~]#echo ${file##*/}
messages
从右往左:
#其中word可以是指定的任意字符,功能,自右而左,查找var变量所存储的字符串中,第一次出现的worc,删除字符串最后一个字符向左至第一次出现word字符串 (含) 之间的所有字符
${var%word*}
#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
${var%%word*}
例:
[root@centos8 ~]#file="/var/log/messages"
[root@centos8 ~]#echo ${file%/*}
var/log
[root@centos8 ~]#echo ${file%%/*}
var
#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var/pattern/substr}
#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}
#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}
#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}
#删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}
#删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}
#删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}
#删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}
#把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,,}
例:
[root@CentOS8 test]#echo $str
123abc哇哦
[root@CentOS8 test]#echo ${str^^}
123ABC哇哦
变量配置方法 | 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=sstr |
var=${str:?expr} | expr输出至stderr | expr输出至stderr | var=sstr |
例:
[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title-oops}
[root@centos8 ~]#echo $name
ceo
[root@centos8 ~]#title=
[root@centos8 ~]#name=${title-oops}
[root@centos8 ~]#echo $name
[root@centos8 ~]#unset title
[rootacentos8 ~]#name=$ftitle-oops}
[root@centos8 ~]#echo $name
oops
Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的
declare [选项] 变量名
选项:
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数,相当于export
-l 声明变量为小写字母 declare -l var=UPPER
-u 声明变量为大写字母 declare -u var=lower
eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描,因此后面必须跟命令
例1:
[root@CentOS8 test]#CMD=hostname
[root@CentOS8 test]#$CMD
CentOS8.Joyce.person1
[root@CentOS8 test]#echo $CMD
hostname
[root@CentOS8 test]#eval $CMD
CentOS8.Joyce.person1
例2:
[root@CentOS8 test]#n=10
[root@CentOS8 test]#for i in {1..$n};do echo $i;done
{1..10}
[root@CentOS8 test]#for i in `eval echo {1..$n}`;do echo $i;done
1
2
3
4
5
6
7
8
9
10
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获
得变量值value的行为
variablel=variable2
variable2=value
bash Shell提供了两种格式实现间接变量引用
eval tempvar= \$Svariable1
tempvar=${!variable1}
例:
[root@CentOS8 test]#ceo=name
[root@CentOS8 test]#name=Me
[root@CentOS8 test]#echo $ceo
name
[root@CentOS8 test]#echo $$ceo
7487ceo
[root@CentOS8 test]#echo \$$ceo
$name
[root@CentOS8 test]#eval echo \$$ceo
Me
#或:
[root@CentOS8 test]#echo ${!ceo}
Me
[root@centos8 ~]#cat test.sh
#!/bin/bash
ceo=ME
title=ceo
declare -n ref=$title
[ -R ref ] && echo reference
echo $ref
ceo=Joyce
echo $ref
[root@centos8 ~]#bash test.sh
reference
ME
Joyce