1 编程基础
Linus:Talk is cheap, show me the code
1.1 程序组成
程序:算法+数据结构
数据:是程序的核心
数据结构:数据在计算机中的类型和组织方式
算法:处理数据的方式
1.2 程序编程风格
- 面向过程语言
-
- 做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理
- 问题规模小,可以步骤化,按部就班处理
- 以指令为中心,数据服务于指令
- C,shell
- 面向对象语言
-
- 一种认识世界、分析世界的方法论。将万事万物抽象为各种对象
- 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
- 对象是类的具象,是一个实体
- 问题规模大,复杂系统
- 以数据为中心,指令服务于数据
- java,C#,python,golang等
1.3 编程语言
计算机:运行二进制指令
编程语言:人与计算机之间交互的语言。分为两种:低级语言和高级语言
- 低级编程语言:
机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大,难懂、难写
汇编:用一些助记符号替代机器指令,称为汇编语言
如:ADD A,B 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中
汇编语言写好的程序需要汇编程序转换成机器指令
汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言.
- 高级编程语言:
编译:高级语言–>编译器–>机器代码文件–>执行,如:C,C++
解释:高级语言–>执行–>解释器–>机器代码,如:shell,python, php,JavaScript,perl
编译和解释型语言:
1.4 编程逻辑处理方式
三种处理逻辑
- 顺序执行:程序按从上到下顺序执行
- 选择执行:程序执行过程中,根据条件的不同,进行选择不同分支继续执行
- 循环执行:程序执行过程中需要重复执行多次某段语句
2 shell 脚本语言的基本用法
2.1 shell 脚本的用途
- 自动化常用命令
- 执行系统管理和故障排除
- 创建简单的应用程序
- 处理文本或文件
2.2 shell 脚本基本结构
shell脚本编程:是基于过程式、解释执行的语言
编程语言的基本结构:
- 各种系统命令的组合
- 数据存储:变量、数组
- 表达式:a + b
- 控制语句:if
shell脚本:包含一些命令或声明,并符合一定格式的文本文件。
格式要求:首行shebang机制
#!/bin/bash #!/usr/bin/python #!/usr/bin/perl
2.3 shell脚本创建过程
第一步:使用文本编辑器来创建文本文件
第一行必须包括shell声明序列:#!
添加注释,注释以#开头
第二步:加执行权限
给予执行权限,在命令行上指定脚本的绝对或相对路径
第三步:运行脚本
直接运行解释器,将脚本作为解释器程序的参数运行
2.4 shell 脚本注释规范
1、第一行一般为调用使用的语言
2、程序名,避免更改文件名为无法找到正确的文件
3、版本号
4、更改后的时间
5、作者相关信息
6、该程序的作用,及注意事项
7、最后是各版本的更新简要说明
2.6 shell 脚本调试
只检测脚本中的语法错误,但无法检查出命令错误,但不真正执行脚本.
bash -n /path/to/some_script 调试并执行: bash -x /path/to/some_script
总结:脚本错误常见的有三种
- 语法错误,会导致后续的命令不继续执行,可以用bash -n 检查错误,提示的出错行数不一定是准确的
- 命令错误,默认后续的命令还会继续执行,用bash -n 无法检查出来 ,可以使用 bash -x 进行观察
- 逻辑错误:只能使用 bash -x 进行观察
2.7 变量
2.7.1 变量
变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据.
2.7.2 变量类型
变量类型:
- 内置变量,如:PS1,PATH,UID,HOSTNAME,$$,BASHPID,PPID,$?,HISTSIZE
- 用户自定义变量
不同的变量存放的数据不同,决定了以下
1. 数据存储方式
2. 参与的运算
3. 表示的数据范围
变量数据类型:
- 字符
- 数值:整型、浮点型,bash 不支持浮点数
2.7.3 编程语言分类
静态和动态语言
- 静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
- 动态编译语言:不用事先声明,可随时改变类型,如:bash,Python
强类型和弱类型语言
- 强类型语言:不同类型数据操作,必须经过强制转换才同一类型才能运算,如java , c# ,python
如:参考以下 python 代码
print(‘magedu’+ 10) 提示出错,不会自动转换类型
print(‘magedu’+str(10)) 结果为magedu10,需要显示转换类型
- 弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
如:bash ,php,javascript
2.7.4 Shell中变量命名法则
- 不能使程序中的保留字:如:if, for
- 只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ – ”,和主机名相反
- 见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM
- 统一命名规则:驼峰命名法, studentname,大驼峰StudentName 小驼峰studentName
- 变量名大写:STUDENT_NAME
- 局部变量小写
- 函数名小写
2.7.5 变量定义和引用
变量的生效范围等标准划分变量类型
- 普通变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
- 环境变量:生效范围为当前shell进程及其子进程
- 本地变量:生效范围为当前shell进程中某代码片断,通常指函数
变量赋值:
name='value' value 可以是以下多种形式: 直接字串:name='root' 变量引用:name="$USER" 命令引用:name=`COMMAND` 或者 name=$(COMMAND)
注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量会随着脚本结束,也会自动删除.
变量引用:
$name ${name}
弱引用和强引用:
- “$name ” 弱引用,其中的变量引用会被替换为变量值
- ‘$name ‘ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
显示已定义的所有变量:set
删除变量:unset <name>
2.7.6 环境变量
环境变量:
- 可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
- 一旦子进程修改从父进程继承的变量,将会新的值传递给孙子进程
- 一般只在系统配置文件中使用,在脚本中较少使用
变量声明和赋值:
#声明并赋值 export name=VALUE declare -x name=VALUE #或者分两步实现 name=VALUE export name
显示所有环境变量:
env printenv export declare -x
bash内建的环境变量:
PATH SHELL USER UID HOME PWD SHLVL #shell的嵌套层数,即深度 LANG MAIL HOSTNAME HISTSIZE _ #下划线 表示前一命令的最后一个参数
2.7.7 只读变量
只读变量:只能声明定义,但后续不能修改和删除,即常量
声明只读变量:
readonly name declare -r name
查看只读变量:
readonly [-p] declare -r
2.7.8 位置变量
位置变量:在bash shell中内置的变量, 在脚本代码中调用通过命令行传递给脚本的参数
$1, $2, ... 对应第1个、第2个等参数,shift [n]换位置 $0 命令本身,包括路径 $* 传递给脚本的所有参数,全部参数合为一个字符串 $@ 传递给脚本的所有参数,每个参数为独立字符串 $# 传递给脚本的参数的个数 注意:$@ $* 只在被双引号包起来的时候才会有差异
清空所有位置变量
set --
2.7.9 退出状态码变量
进程执行后,将使用变量 $? 保存状态码的相关数字,不同的值反应成功或失败,$?取值范例 0-255
$?的值为0 #代表成功 $?的值是1到255 #代表失败
用户可以在脚本中使用以下命令自定义退出状态码
exit [n] 注意: 脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字 如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
2.7.10 展开命令行
展开命令执行顺序
把命令行分成单个命令词 展开别名 展开大括号的声明{} 展开波浪符声明 ~ 命令替换$() 和 `` 再次把命令行分成命令词 展开文件通配*、?、[abc]等等 准备I/0重导向 <、> 运行命令
防止扩展
反斜线(\)会使随后的字符按原意解释
加引号来防止扩展
单引号(’’)防止所有扩展 双引号(”“)也可防止扩展,但是以下情况例外:$(美元符号)
变量扩展
`` : 反引号,命令替换 \:反斜线,禁止单个字符扩展 !:叹号,历史命令替换
2.7.11 脚本安全和 set
set 命令:可以用来定制 shell 环境
$- 变量
h:hashall,打开选项后,Shell 会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选项关闭
i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的 shell。所谓的交互式shell,在脚本中,i选项是关闭的
m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等
B:braceexpand,大括号扩展
H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的s一个历史命令,“!n”返回第 n 个历史命令。
set 命令实现脚本安全
-u 在扩展一个没有设置的变量时,显示错误信息, 等同set -o nounset
-e 如果一个命令返回一个非0退出状态值(失败)就退出, 等同set -o errexit
-o option 显示,打开或者关闭选项
显示选项:set -o
打开选项:set -o 选项
关闭选项:set +o 选项
-x 当执行命令时,打印命令及其参数,类似 bash -x
2.8 格式化输出 printf
格式:
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 |
垂直制表符 |
\ |
表示\本身 |
2.9 算术运算
shell 支持算术运算,但只支持整数,不支持小数
bash中的算术运算
+ - * / % 取模,即取余数,示例:9%4=1,5%3=2 ** 乘方
乘法符号有些场景中需要转义
实现算术运算:
(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] #随机字体颜色 [root@centos8 ~]#echo -e "\033[1;$[RANDOM%7+31]hello\033[0m"
增强型赋值:
+= i+=10 相当于 i=i+10 -= i-=j 相当于 i=i-j *= /= %= ++ i++,++i 相当于 i=i+1 -- i--,--i 相当于 i=i-1
格式:
let varOPERvalue
范例:
[root@centos8 ~]#let i=10*2 [root@centos8 ~]#echo $i 20 [root@centos8 ~]#((j=i+10)) [root@centos8 ~]#echo $j #自增,自减 let var+=1 let var++ let var-=1 let var-- [root@centos8 ~]#unset i j ; i=1; let j=i++; echo "i=$i,j=$j" i=2,j=1 [root@centos8 ~]#unset i j ; i=1; let j=++i; echo "i=$i,j=$j" i=2,j=2
2.10 逻辑运算
true, false
1, 0
与:&:和0相与,结果为0,和1相与,结果保留原值
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0
或:|:和1相或结果为1,和0相或,结果保留原值
1 或 1 = 1
1 或 0 = 1
0 或 1 = 1
0 或 0 = 0
非:!
! 1 = 0 ! true
! 0 = 1 ! false
异或:^
异或的两个值,相同为假,不同为真。两个数字X,Y异或得到结果Z,Z再和任意两者之一X异或,将得出另一个值Y.
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
短路运算
- 短路与
CMD1 短路与 CMD2
第一个CMD1结果为真 (1),第二个CMD2必须要参与运算,才能得到最终的结果
第一个CMD1结果为假 (0),总的结果必定为0,因此不需要执行CMD2
- 短路或
CMD1 短路或 CMD2
第一个CMD1结果为真 (1),总的结果必定为1,因此不需要执行CMD2
第一个CMD1结果为假 (0),第二个CMD2 必须要参与运算,,才能得到最终的结果
2.11 条件测试命令
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成,测试过程,实现评估布尔声明,以便用在条件性环境下进行执行。
若真,则状态码变量 $? 返回0
若假,则状态码变量 $? 返回1
条件测试命令
- test EXPRESSION
- [ EXPRESSION ] #和test 等价,建议使用 [ ]
- [[ EXPRESSION ]]
注意:EXPRESSION前后必须有空白字符
2.11.1 变量测试
#判断 NAME 变量是否定义 [ -v NAME ] #判断 NAME 变量是否定义并且是名称引用,bash 4.4新特性 [ -R NAME ]
2.11.2 数值测试
-eq 是否等于 -ne 是否不等于 -gt 是否大于 -ge 是否大于等于 -lt 是否小于 -le 是否小于等于
2.11.3 字符串测试
test和 [ ]用法 -z STRING 字符串是否为空,没定义或空为真,不空为假, -n STRING 字符串是否不空,不空为真,空为假 STRING 同上 STRING1 = STRING2 是否等于,注意 = 前后有空格 STRING1 != STRING2 是否不等于 > ascii码是否大于ascii码 < 是否小于 [[]] 用法,建议,当使用正则表达式或通配符使用,一般情况使用 [ ] == 左侧字符串是否和右侧的PATTERN相同 注意:此表达式用于[[ ]]中,PATTERN为通配符 =~ 左侧字符串是否能够被右侧的正则表达式的PATTERN所匹配 注意: 此表达式用于[[ ]]中;扩展的正则表达式
2.11.4 文件测试
存在性测试:
-a FILE:同 -e -e FILE: 文件存在性测试,存在为真,否则为假 -b FILE:是否存在且为块设备文件 -c FILE:是否存在且为字符设备文件 -d FILE:是否存在且为目录文件 -f FILE:是否存在且为普通文件 -h FILE 或 -L FILE:存在且为符号链接文件 -p FILE:是否存在且为命名管道文件 -S FILE:是否存在且为套接字文件
文件权限测试:
-r FILE:是否存在且可读 -w FILE: 是否存在且可写 -x FILE: 是否存在且可执行 -u FILE:是否存在且拥有suid权限 -g FILE:是否存在且拥有sgid权限 -k FILE:是否存在且拥有sticky权限
注意:最终结果由用户对文件的实际权限决定,而非文件属性决定
文件属性测试:
-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
2.12 关于 () 和 {}
(CMD1;CMD2;…)和 { CMD1;CMD2;…; } 都可以将多个命令组合在一起,批量执行
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境, 帮助参看:man bash搜索(list)
{ list; } 不会启子shell, 在当前shell中运行,会影响当前shell环境, 帮助参看:man bash 搜索{ list; }
2.13 组合测试条件
2.13.1 第一种方式 [ ]
[ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都是真,结果才为真 [ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真 [ ! EXPRESSION ] 取反
说明: -a 和 -o 需要使用测试命令进行,[[ ]] 不支持
2.13.2 第二种方式 [[ ]]
COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN 如果COMMAND1 成功,将执行COMMAND2,否则,将不执行COMMAND2 COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE 如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2 ! COMMAND #非,取反
2.13 使用read命令来接受输入
使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置变量REPLY
格式:
read [options] [name ...] 常见选项: -p 指定要显示的提示 -s 静默输入,一般用于密码 -n N 指定输入的字符长度N -d '字符' 输入结束符 -t N TIMEOUT为N秒
3 bash的配置文件
bash shell的配置文件很多,可以分成下面类别
3.1 按生效范围划分两类
全局配置:
/etc/profile /etc/profile.d/*.sh /etc/bashrc
个人配置:
~/.bash_profile ~/.bashrc
3.2 shell登录两种方式分类
3.2.1 交互式登录
- 直接通过终端输入账号密码登录
- 使用su – UserName 切换的用户
配置文件执行顺序:
/etc/profile.d/*.sh /etc/bashrc /etc/profile /etc/bashrc #此文件执行两次 .bashrc .bash_profile
注意:文件之间的调用关系,写在同一个文件的不同位置,将影响文件的执行顺序。
3.2.2 非交互式登录
- su UserName
- 图形界面下打开的终端
- 执行脚本
- 任何其它的bash实例
执行顺序:
/etc/profile.d/*.sh /etc/bashrc .bashrc
3.3 按功能划分分类
profile类和bashrc类
3.3.1 Profile类
profile类为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh 个人:~/.bash_profile
功用:
(1) 用于定义环境变量
(2) 运行命令或脚本
3.3.2 Bashrc类
bashrc类:为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc 个人:~/.bashrc
功用:
(1) 定义命令别名和函数
(2) 定义本地变量
3.4 编辑配置文件生效
修改profile和bashrc文件后需生效两种方法:
1. 重新启动shell进程
2. source|. 配置文件
3.5 Bash 退出任务
保存在~/.bash_logout文件中(用户),在退出登录shell时运行
功能:
- 创建自动备份
- 清除临时文件
4 流程控制
4.1 条件选择
4.1.1 条件判断分绍
4.1.1.1 单分支条件
4.1.1.2 多分支条件
4.1.2 选择执行 if 语句
格式:
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
单分支
if 判断条件;then 条件为真的分支代码 fi
双分支
if 判断条件; then 条件为真的分支代码 else 条件为假的分支代码 fi
多分支
if 判断条件1; then 条件1为真的分支代码 elif 判断条件2; then 条件2为真的分支代码 elif 判断条件3; then 条件3为真的分支代码 ... else 以上条件都为假的分支代码 fi
说明:
- 多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
- if 语句可嵌套
4.1.3 条件判断 case 语句
格式:
case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
case 变量引用 in PAT1) 分支1 ;; PAT2) 分支2 ;; ... *) 默认分支 ;; esac
case支持glob风格的通配符:
*: 任意长度任意字符 ?: 任意单个字符 []:指定范围内的任意单个字符 |: 或,如 a或b
4.2 循环
将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件
重复运行次数
- 循环次数事先已知
- 循环次数事先未知
常见的循环的命令:for, while, until
4.2.2 for循环
格式1:
for NAME [in WORDS ... ] ; do COMMANDS; done for 变量名 in 列表;do 循环体 done for 变量名 in 列表 do 循环体 done
执行机制:
依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束。
for循环列表生成方式:
- 直接给出列表
- 整数列表:
{start..end} $(seq [start [step]] end)
- 返回列表的命令:
$(COMMAND)
- 使用glob,如:*.sh
- 变量引用,如:$@,$*,$#
格式2:
双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的变量操作;I=10;((I++))
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式)) do 循环体 done
说明:
- 控制变量初始化:仅在运行到循环代码段时执行一次
- 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断
4.2.3 while循环
格式:
while COMMANDS; do COMMANDS; done while CONDITION; do 循环体 done
说明:
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为true
退出条件:CONDITION为false
4.2.4 until 循环
格式:
until COMMANDS; do COMMANDS; done until CONDITION; do 循环体 done
说明:
进入条件: CONDITION 为false
退出条件: CONDITION 为true
4.2.4 循环控制语句 continue
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
while CONDITION1; do CMD1 ... if CONDITION2; then continue fi CMDn ... done
4.2.5 循环控制语句 break
break [N]:提前结束第N层整个循环,最内层为第1层
while CONDITION1; do CMD1 ... if CONDITION2; then break fi CMDn ... done
4.2.6 循环控制 shift 命令
shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift.
4.2.7 while read 特殊用法
while 循环的特殊用法,遍历文件或文本的每一行
格式:
while read line; do 循环体 done < /PATH/FROM/SOMEFILE
说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
4.2.8 select 循环与菜单
格式:
select NAME [in WORDS ... ;] do COMMANDS; done select variable in list ;do 循环体命令 done
说明:
- select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示 PS3 提示符,等待用户输入
- 用户输入菜单列表中的某个数字,执行相应的命令
- 用户输入被保存在内置变量 REPLY 中
- select 是个无限循环,因此要用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c退出循环
- select 经常和 case 联合使用
- 与 for 循环类似,可以省略 in list,此时使用位置参量
cat select.sh #!/bin/bash # sum=0 PS3="请点菜(1-6): " select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 火锅 点菜结束;do case $REPLY in 1) echo $MENU 价格是 100 let sum+=100 ;; 2) echo $MENU 价格是 88 let sum+=88 ;; 3) echo $MENU价格是 66 let sum+=66 ;; 4) echo $MENU 价格是 166 let sum+=166 ;; 5) echo $MENU 价格是 200 let sum+=200 ;; 6) echo "点菜结束,退出" break ;; *) echo "点菜错误,重新选择" ;; esac done echo "总价格是: $sum"
5 函数介绍
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分函数和shell程序比较相似,区别在于Shell程序在子Shell中运行,而Shell函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改.
5.1 管理函数
函数由两部分组成:函数名和函数体
帮助参看:help function
5.1.1 定义函数
#语法一: func_name (){ ...函数体... } #语法二: function func_name { ...函数体... } #语法三: function func_name () { ...函数体... }
5.1.2 查看函数
#查看当前已定义的函数名 declare -F #查看当前已定义的函数定义 declare -f #查看指定当前已定义的函数名 declare -f func_name #查看当前已定义的函数名定义 declare -F func_name
5.1.3 删除函数
unset func_name
5.2 函数调用
函数的调用方式
- 可在交互式环境下定义函数
- 可将函数放在脚本文件中作为它的一部分
- 可放在只包含函数的单独文件中
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码函数的生命周期:被调用时创建,返回时终止.
5.2.2 在脚本中定义及使用函数
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使用其函数名即可.
[root@centos8 ~]#cat func1.sh #!/bin/bash #name:func1 hello(){ echo "Hello there today's date is `date +%F`" } echo "now going to the function hello" hello echo "back from the function" [root@centos8 ~]#./func1.sh now going to the function hello Hello there today's date is 2019-12-18 back from the function
5.2.3 使用函数文件
可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数文件名可任意选取,但最好与相关任务有某种联系,例如:functions
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用delcare -f 或set 命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数,若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件.
实现函数文件的过程:
1. 创建函数文件,只存放函数的定义
2. 在shell脚本或交互式shell中调用函数文件,格式如下:
. filename 或 source filename
[root@centos8 ~]#cat functions #!/bin/bash #functions hello(){ echo Run hello Function } hello2(){ echo Run hello2 Function } [root@centos8 ~]#. functions [root@centos8 ~]#hello Run hello Function [root@centos8 ~]#hello2 Run hello2 Function [root@centos8 ~]#declare -f hello hello2 hello () { echo Run hello Function } hello2 () { echo Run hello2 Function }
5.3 函数返回值
函数的执行结果返回值:
- 使用echo等命令进行输出
- 函数体中调用命令的输出结果
函数的退出状态码:
- 默认取决于函数中执行的最后一条命令的退出状态码
- 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回
5.4 环境函数
类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
定义环境函数:
export -f function_name declare -xf function_name
查看环境函数:
export -f declare -xf
5.5 函数参数
函数可以接受参数:
- 传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 …
- 在函数体中当中,可使用$1, $2, …调用这些参数;还可以使用$@, $*, $#等特殊变量
5.6 函数变量
变量作用域:
- 普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
- 环境变量:当前shell和子shell有效
- 本地变量:函数的生命周期;函数结束时变量被自动销毁
注意:
如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
在函数中定义本地变量的方法:
local NAME=VALUE
5.7 函数递归
函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环
fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源.
函数实现
:(){ :|:& };: bomb() { bomb | bomb & }; bomb
6 其它脚本相关工具
6.1 信号捕捉 trap
trap ‘触发指令’ 信号
进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap ” 信号
忽略信号的操作
trap ‘-‘ 信号
恢复原信号的操作
trap -p
列出自定义信号操作
trap finish EXIT
当脚本退出时,执行finish函数
范例:
finish(){ echo finish| tee -a /root/finish.log } trap finish exit while true ;do echo running sleep 1 done
6.2 创建临时文件 mktemp
mktemp 命令用于创建并显示临时文件,可避免冲突
格式
mktemp [OPTION]... [TEMPLATE]
说明:TEMPLATE: filenameXXX,X至少要出现三个常见选项:
-d 创建临时目录
-p DIR或–tmpdir=DIR 指明临时文件所存放目录位置
6.3 安装复制文件 install
install 功能相当于cp,chmod,chown,chgrp 等相关工具的集合
install命令格式:
install [OPTION]... [-T] SOURCE DEST 单文件 install [OPTION]... SOURCE... DIRECTORY install [OPTION]... -t DIRECTORY SOURCE... install [OPTION]... -d DIRECTORY...创建空目录
选项:
-m MODE,默认755 -o OWNER -g GROUP -d DIRNAME 目录
6.4 交互式转化批处理工具 expect
expect 是由Don Libes基于 Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助 expect 处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率.
expect 语法:
expect [选项] [ -c cmds ] [ [ -[f|b] ] 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语言:模式-动作)
单一分支模式语法:
[root@centos8 test]#expect expect1.1> expect "hi" {send "You said hi\n"} hahahixixi You said hi
匹配到hi后,会输出“you said hi”,并换行
多分支模式语法:
[root@centos8 test]#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 expect1.4>
匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
expect { "hi" { send "You said hi\n"} "hehe" { send "Hehe yourself\n"} "bye" { send " Good bye\n"} } [root@centos8 ~]#expect expect1.1> expect { +> "hi" { send "You said hi\n"} +> "hehe" { send "Hehe yourself\n"} +> "bye" { send " Good bye\n"} +> } bye Good bye expect1.2>
范例3:expect 变量
#!/usr/bin/expect set ip 10.0.0.7 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 位置参数
#!/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
范例5:expect 执行多个命令
#!/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 magedu |passwd --stdin haha\n" } send "exit\n" expect eof
范例6:shell脚本调用expect
#!/bin/bash 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 magedu |passwd --stdin hehe\n" } expect "]#" { send "exit\n" } expect eof EOF
范例7: shell脚本利用循环调用expect在CentOS和Ubuntu上批量创建用户
NET=10.0.0 user=root password=123456 for ID in 6 7 111;do 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
7 数组array
7.1 数组介绍
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
- 索引的编号从0开始,属于数值索引
- 索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
- bash的数组支持稀疏格式(索引不连续)
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" ...)
(3) 只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
(4) 交互式数组值对赋值
read -a ARRAY
7.4 显示所有数组
显示所有数组:
declare -a
7.5 引用数组
引用数组元素
${ARRAY_NAME[INDEX]} #如果省略[INDEX]表示引用下标为0的元素
引用数组所有元素
${ARRAY_NAME[*]} ${ARRAY_NAME[@]}
数组的长度,即数组中元素的个数
${#ARRAY_NAME[*]} ${#ARRAY_NAME[@]}
7.6 删除数组
删除数组中的某元素,会导致稀疏格式
unset ARRAY[INDEX]
删除整个数组
unset ARRAY
7.7 数组数据处理
数组切片:
${ARRAY[@]:offset:number} offset #要跳过的元素个数 number #要取出的元素个数 #取偏移量之后的所有元素 {ARRAY[@]:offset}
向数组中追加元素:
ARRAY[${#ARRAY[*]}]=value ARRAY[${#ARRAY[@]}]=value
7.8 关联数组
declare -A ARRAY_NAME ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
注意:关联数组必须先声明再调用
#!/bin/bash declare -i min max declare -a nums for ((i=0;i<10;i++));do nums[$i]=$RANDOM [ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]}&& continue [ ${nums[$i]} -gt $max ] && max=${nums[$i]} [ ${nums[$i]} -lt $min ] && min=${nums[$i]} done echo "All numbers are ${nums[*]}" echo Max is $max echo Min is $min
范例:编写脚本,定义一个数组,数组中的元素对应的值是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和。
#!/bin/bash # declare -a files files=(/var/log/*.log) declare -i lines=0 for i in $(seq 0 $[${#files[*]}-1]); do if [ $[$i%2] -eq 0 ];then let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1) fi done echo "Lines: $lines"
8 字符串处理
8.1 字符串切片
基于偏移量取字符串
#返回字符串变量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 script40]#str=abcdef我你他 [root@centos8 script40]#echo ${#str} 9 [root@centos8 script40]#echo ${str:2} cdef我你他 [root@centos8 script40]#echo ${str:2:3} cde [root@centos8 script40]#echo ${str:-3} abcdef我你他 [root@centos8 script40]#echo ${str: -3} 我你他 [root@centos8 script40]#echo ${str:2:-3} cdef [root@centos8 script40]#echo ${str: -2:-3} -bash: -3: substring expression < 0 [root@centos8 script40]#echo ${str: -3:-2} 我 [root@centos8 script40]#echo ${str:-3:-2} abcdef我你他 [root@centos8 script40]#echo ${str: -3:-2} 我 [root@centos8 script40]#echo ${str: -5:-2} ef我
基于模式取子串:
#其中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变量所存储的字符串中,第一次出现的word,删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符 ${var%word*} #同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符 ${var%%word*}
[root@centos8 ~]#file="var/log/messages" [root@centos8 ~]#echo ${file%/*} var/log [root@centos8 ~]#echo ${file%%/*} var
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}
8.3 查找并删除
#删除var表示的字符串中第一次被pattern匹配到的字符串 ${var/pattern} 删除var表示的字符串中所有被pattern匹配到的字符串 ${var//pattern} 删除var表示的字符串中所有以pattern为行首匹配到的字符串 ${var/#pattern} 删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串 ${var/%pattern}
8.4 字符大小写转换
#把var中的所有小写字母转换为大写 ${var^^} #把var中的所有大写字母转换为小写 ${var,,}
9 高级变量
9.1 高级变量赋值
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
9.3 变量间接引用
9.3.1 eval命令
eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描。
9.3.2 间接变量引用
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用,variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过,variable1获得变量值value的行为。
variable1=variable2 variable2=value
bash Shell提供了两种格式实现间接变量引用
eval tempvar=\$$variable1 tempvar=${!variable1}
9.3.3 变量引用reference
[root@centos8 ~]#cat test.sh #!/bin/bash ceo=mage title=ceo declare -n ref=$title [ -R ref ] && echo reference echo $ref ceo=wang echo $ref [root@centos8 ~]#bash test.sh reference mage wang