shell编程入门

shell编程
一、初始shell
shell 定义
shell的分类和切换
使用场景
shell 特性回顾
bash 初始化
二、shell 脚本规范
三、变量的类型
练习1
练习2
四、脚本运行
创建bash脚本(shell脚本)
bash脚本执行
子shell
bash 脚本测试
五、变量置换
命令替换
变量替换
变量替换-匹配截取
basename & dirname
六、shell编程-流程控制
shell编程-条件结构
shell分支if语句
shell 分支case语句
七、shell编程-循环结构
shell循环-for语句
shell 循环while语句
shell循环until语句
shell 循环控制shift、continue、break、exit
实战-shell版本jumpserver开发(堡垒机)
八、shell 编程-函数
shell 函数function
九、shell 编程-数组
数组定义
访问数组
十、正则表达式RE
No.1 正则表达式基本元字符
No.2正则表达式拓展元字符
十一、shell 编程-grep
十二、shell 编程-SED
sed流编辑器用法及解析
十三、shell 编程-AWK
AWK使用理解案例
十四、shell 编程-Expect
No.1 expect的安装
No.2 expect的语法
十五、shell 编程-运维脚本实战

shell编程
一、初始shell
程序 语言 编程
语言
自然语言
汉语 英语
计算机语言
c语言
c++
java php python go shell
编译型语言 c c++ java
解释型语言 php python bash

编译型语言:运行编译型语言是相对于解释型语言存在的,编译型语言的首先将源代码编译生成机器语言,再由机器运行机 器码(二进制)。像C/C++等都是编译型语言。
解释型语言:相对于编译型语言存在的,源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码 进行解释运行。比如Python/JavaScript / Perl /Shell等都是解释型语言
c 编译型执行 代码需要编译成cpu能认识的二进制码 x86指令集
java 编译型执行 , 编译–>字节码,cpu不能直接运行,只能被Java虚拟机执行
shell 解释型执行 慢

shell 定义

Shell 也是一种程序设计语言,它有变量,关键字,各种控制语句,有自己的语法结构,利用shell程序设计语 言可以编写功能很强、代码简短的程序
#! Shebang 定义解释器

shell的分类和切换

[root@newrain ~]# cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin

默认shell: bash shell
centos中脚本使用的默认shell 为/usr/bin/sh
查看当前正在使用的shell
echo $SHELL
shell 的切换
vim /etc/passwd 编辑登录shell

使用场景

什么时候不适合使用Shell编程:
1. 资源紧张的项目,特别是那些速度是重要因素的地方(排序,散序,等等)
2. 程序要进行很复杂的数学计算,特别是浮点计算,任意精度的计算,或者是复数计算
3. 要求交叉编译平台的可移植性(使用C或者是Java代替)
4. 需要结构化编程的复杂应用(需要变量类型检查和函数原型等等)
5. 对于影响系统全局性的关键任务应用。
6. 安全非常重要。你必须保证系统完整性和抵抗入侵,攻击和恶意破坏。
7. 项目由连串的依赖的各个部分组成。
8. 多种文件操作要求(Bash被限制成文件顺序存取,并且是以相当笨拙,效率低下的逐行的存取方式) 9. 需要良好的多维数组支持。
10. 需要类似链表或树这样的数据结构。
11. 需要产生或操作图象或图形用户界面。
12. 需要直接存取系统硬件。
13. 需要端口号或是socket I/O。
14. 需要使用可重用的函数库或接口。
15. 所有的私有的不开源的应用程序(Shell脚本的源代码是直接可读,能被所有人看到的)

如果你需要有上面的任意一种应用,请考虑其他的更强大的脚本语言――Perl,Tcl,Python,Ruby,或者可能是其他更 高级的编译型语言,例如C,C++或者是Java

Shell 能做什么?

  1. 自动化批量系统初始化程序 (update,软件安装,时区设置,安全策略…)
  2. 自动化批量软件部署程序 (LAMP,LNMP,Tomcat,LVS,Nginx)
  3. 应用管理程序 (KVM,集群管理扩容,MySQL,DELLR720批量RAID)
  4. 日志分析处理程序(PV, UV, 200, !200, top 100, grep/awk)
  5. 自动化备份恢复程序(MySQL完全备份/增量 + Crond)
  6. 自动化管理程序(批量远程修改密码,软件升级,配置更新)
  7. 自动化信息采集及监控程序(收集系统/应用状态信息,CPU,Mem,Disk,Net,TCP Status,Apache,MySQL) 8. 配合Zabbix信息采集(收集系统/应用状态信息,CPU,Mem,Disk,Net,TCP Status,Apache,MySQL)
  8. 自动化扩容(增加云主机——>业务上线)
    zabbix监控CPU 80%+|-50% Python API AWS/EC2(增加/删除云主机) + Shell Script(业务上 线)
  9. 俄罗斯方块,打印三角形,打印圣诞树,打印五角星,运行小火车,坦克大战,排序实现 11. Shell可以做任何运维的事情(一切取决于业务需求)

shell 特性回顾
shell常见元素

文件描述符与输出重定向:
在 shell 程式中,最常使用的 FD (file descriptor) 大概有三个, 分别是: 0: Standard Input (STDIN)
1: Standard Output (STDOUT)
2: Standard Error Output (STDERR)
在标准情况下, 这些FD分别跟如下设备关联:
stdin(0): keyboard 键盘输入,并返回在前端
stdout(1): monitor 正确返回值 输出到前端
stderr(2): monitor 错误返回值 输出到前端

a.txt
1>a.txt
2>a.txt
&>a.txt
1>&2
2>&1
一般来说, “1>” 通常可以省略成 “>”.
1>&2 正确返回值传递给2输出通道 &2表示2输出通道,之前如果有定义标准错误重定向到某log文件,那么标准输出也重 定向到这个log文件,如果此处错写成 1>2, 就表示把1输出重定向到文件2中.
2>&1 错误返回值传递给1输出通道, 同样&1表示1输出通道.
例子. 当前目录下只有a.txt,没有b.txt
[root@redhat box]# ls a.txt b.txt 1>file.out 2>&1
[root@redhat box]# cat file.out
ls: b.txt: No such file or directory
a.txt
现在, 正确的输出和错误的输出都定向到了file.out这个文件中, 而不显示在前端 =================================
[root@redhat tmp]# cat >> b.txt << !
ni hao a hahafvs
!
[root@redhat tmp]# cat b.txt
ni hao a haha

bash 初始化

用户登录时相关的bash配置文件 (登录脚本)
全局配置文件
/etc/profile
/etc/profile.d/.sh
/etc/bashrc
个人配置文件
~/.bsah_profile
~/.bashrc
profile类的文件: 设定环境变量
运行命令或脚本
bashrc类的文件:
定义命令别名
用户登录时加载bash配置文件的过程
登录式shell加载配置文件过程
~/.bash_profile --> ~/.bashrc --> /etc/bashrc --> /etc/profile --> /etc/profile.d/
.sh
非登录式shell加载配置文件过程
~/.bashrc --> /etc/bashrc --> /etc/profile.d/.sh
用户的初始化脚本
环境变量 修饰用户工作环境变量
这些文件为系统的每个用户设置环境信息Shell设置文件:
/etc/profile(系统级)启动时执行
这是系统最主要的shell设置文件,也是用户登陆时系统最先检查的文件,有关重要的环境变量都定义在此,其中包括 PATH,USER,LOGNAME,MAIL,HOSTNAME,HISTSIZE,INPUTRC等。而在文件的最后,它会检查并执 行/etc/profile.d/
.sh的脚本。
~/.bash_profile(用户级)离开时执行
这个文件是每位用户的bash环境设置文件,它存在与于用户的主目录中,当系统执行/etc/profile 后,就会接着读取此 文件内的设置值。在此文件中会定义USERNAME,BASH_ENV和PATH等环境变量,但是此处的PATH除了包含系统的$PATH变 量外加入用户的“bin”目录路径.
~/.bashrc(用户级)离开时执行
接下来系统会检查~.bashrc文件,这个文件和前两个文件(/etc/profile 和~.bash_profile)最大的不同是,每次 执行bash时,.bashrc都会被再次读取,也就是变量会再次地设置,而/etc/profile,./bash_profile只有在登陆 时才读取。就是因为要经常的读取,所以~/.bashrc文件只定义一些终端机设置以及shell提示符号等功能,而不是定义环 境变量。
~/.bash_login(用户级)离开时执行
如果.bash_profile文件不存在,则系统会转而读取.bash_login这个文件内容。这是用户的登陆文件,在每次用户登 陆系统时,bash都会读此内容,所以通常都会将登陆后必须执行的命令放在这个文件中。
~/.bash_logout 离开时执行 如果想在注销shell前执行一些工作,都可以在此文件中设置。 例如:
#vi ~/.bash_logout
clear
仅执行一个clear命令在你注销的时候

~/.bash_history(用户级)
这个文件会记录用户先前使用的历史命令。

bash shell 特性

补全
历史
别名
快捷键
前后台作业
重定向
管道
命令排序执行
; && ||
通配符
{} ?*
正则表达式 脚本

历史命令

查看历史命令
history /etc/profile 下的historysize 可以修改
调用历史命令
上下健
!关键字
!历史命令行号
!! 执行上一条命令
!$ 上一条命令的最后一个参数
esc . 上一条命令的最后一个参数
Ctrl+r 在历史命令中查找,输入关键字调出之前的命令
关键字+pgup/phdn 可以切换关键字相关的历史命令
别名
查看别名
alias
设置别名
临时设置
# aa=88
# echo $aa
永久设置
# vim /root/.bashrc
小小技巧:显示历史命令执行时间
1.设置变量:
HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S"
2.再次执行history查看结果

Bash 部分快捷键

Ctrl+a 切换到命令行开始(跟home一样,但是home在某些unix环境下无法使用)       Ctrl+e 切换到命令行末尾
Ctrl+u 清除剪切光标之前的内容
Ctrl+k 清除剪切光标之后的内容
ctrl+y 粘贴刚才锁删除的字符
Ctrl+r 在历史命令中查找,输入关键字调出之前的命令

通配符置换

在 Shell命令中,通常会使用通配符表达式来匹配一些文件
*,?,[],{}
例:
字符 含义 实例

  •   匹配 0 或多个字符               a*b a与b之间可以有任意长度的任意字符, 也可以一个也没有, 如aabcb, axyzb, a012b, ab。
    

? 匹配任意一个字符 a?b a与b之间必须也只能有一个字符, 可以是任意字符, 如aab, abb, acb, a0b。
[list] 匹配 list 中的任意单一字符 a[xyz]b a与b之间必须也只能有一个字符, 但只能是 x 或 y 或 z, 如: axb, ayb, azb。
[!list] 匹配 除list 中的任意单一字符 a[!0-9]b a与b之间必须也只能有一个字符, 但不能是阿 拉伯数字, 如axb, aab, a-b。
[c1-c2] 匹配 c1-c2 中的任意单一字符 如:[0-9] [a-z] a[0-9]b 0与9之间必须也只能有一个字符 如a0b, a1b… a9b。
{string1,string2,…} 匹配 sring1 或 string2 (或更多)其一字符串 a{abc,xyz,123}b a与b之间只 能是abc或xyz或123这三个字符串之一。

[root@newrain tmp]# rm -rf ./*
[root@newrain tmp]# touch aabcb axyzb a012b ab acb
[root@newrain tmp]# ls
a012b aabcb ab axyzb acb
[root@newrain tmp]# ls ab
a012b aabcb ab axyzb acb
[root@newrain tmp]# ls a?b
acb
[root@newrain tmp]# rm -rf ./

[root@newrain tmp]# touch axb ayb azb axyb
[root@newrain tmp]# ls
axb ayb azb
[root@newrain tmp]# ls a[xy]b
axb ayb
[root@newrain tmp]# ls a[!xy]b
azb
[root@newrain tmp]# ls a[!x]b
ayb azb
[root@newrain tmp]# rm -rf ./*
[root@newrain tmp]# touch a0b a1b a9b
[root@newrain tmp]# ls a[0-9]b
a0b a1b a9b
[root@newrain tmp]# rm -rf ./*
[root@newrain tmp]# touch aabcb axyzb a012b ab
[root@newrain tmp]# ls a{abc}b
ls: cannot access a{abc}b: No such file or directory
[root@newrain tmp]# ls a{abc,xyz}b
aabcb axyzb
[root@newrain tmp]# ls a{abc,xyz,012}b
a012b aabcb axyzb

二、shell 脚本规范

[root@newrain ~]# vim helloworld.sh —.sh代表这个文件是个shell脚本,
拓展名后缀,如果省略.sh则不易判断该文件是否为shell脚本

  1. #!/usr/bin/env bash —shebang蛇棒, 解释器, 翻译 2. #
  2. Author: newrain

  3. Email: newrain@163.com —这就是注释, 你没看错

  4. Github: https://github.com/newrain

  5. Date: 2019//

  6. printf “hello world\n”

    功能说明:打印hello world
    [root@newrain ~]# sh helloworld.sh
    hello world
    [root@newrain ~]# chmod +x helloworld.sh
    [root@newrain ~]# ./helloworld.sh
    [root@newrain tmp]# /tmp/helloworld.sh
    hello world

    第一行: “#!/usr/bin/env bash”叫做shebang, shell语法规定shell脚本文件第一行为整个文件的解释器
    第二行: 为“#”开头的行为注释行默认不会被程序所读取, 用来说明文件及标定所属人员使用, 也可用来解释程序 第七行: 为格式化打印语句printf, printf可以把后面的“hello world”打印到指定的终端中, \n 为换行符

三、变量的类型
变量
bash作为程序设计语言和其它高级语言一样也提供使用和定义变量的功能
预定义变量、环境变量、自定义变量、位置变量

预定义变量
$? 最后一次执行的命令的返回状态。如果这个变量的值为 0,则证明上一条命令正确执行;如果这个变量的值为非 0 ,则 证明上一条命令执行错误
KaTeX parse error: Can't use function '$' in math mode at position 16: 当前进程的进程号(PID) $̲! 后台运行的最后一个进程的进…"
#输出当前进程的PID
#这个PID就是variable.sh脚本执行时生成的进程的PID

[root@newrain sh]#sleep 3000 &
[1] 12165
#符号"&"的意思是把命令放入后台执行
[root@newrain sh]#echo $!
12165

自定义变量
定义:变量名称=值
变量名称:只能由字母,数字,下划线组成,不能以数字开头;
注意:应该让变量名称有意义;
= 赋值符号 前后不能有空格 ;
值: 所有的字符串和数字都可以;
引用变量: $变量名 或 ${变量名}。
示例:
[root@newrain ~]# a=100
[root@newrain ~]# echo $a
100
[root@newrain ~]# echo KaTeX parse error: Expected 'EOF', got '#' at position 36: … #̲ 这里输出为空,因为解释器认为aa是变量
[root@newrain ~]# echo ${a}a
100a

查看变量: echo $变量名 set(所有变量:包括自定义变量和环境变量)
取消变量: unset 变量名 仅在当前shell中有效
作用范围: 仅在当前shell中生效

环境变量
shell在开始执行时已经定义好的
env 查看所有环境变量
set 查看所有变量
环境变量拥有可继承性:export之后就拥有继承性
export 导出变量(作用范围)
临时生效
[root@newrain ~]# IPADDR=192.168.1.1
[root@newrain ~]# echo $IPADDR
192.168.1.1
永久生效
写到4个登陆脚本中 ~/.bashrc ~/profile 更好放在/etc/profile.d/* 下建立独立的环境变量配置文件
常用环境变量:USER UID HOME HOSTNAME PWD PS1 PATH
PATH:存储所有命令所在的路径

练习1

编写一个shell脚本,用于搜集其执行主机的信息,打印结果如下:
[root@tiger tmp]# ./test.sh
2012年 05月 24日 星期四 17:07:45 CST
当前的用户为 root
当前用户的宿主目录为 /root 用户的标识为 0
主机名称为 newrain
网卡的IP地址为 192.168.1.106
##脚本源码如下
#!/usr/bin/bash

获取主机基本信息

time=date +%y年%m月%d日-%H:%M
ip=ifconfig eth0|grep inet|awk '{print $2}' echo “现在的时间是:” $time
echo “当前的用户是:” $USER
echo “当前的用户标识:” $UID
echo “当前的主机名称是:” $HOSTNAME
echo “当前可用网卡IP是:” $ip
取根分区剩余空间:
# df -h /dev/sda2 |awk ‘NR==2{print $4}’ 371G
取当前系统剩余内存:
# echo “现在的剩余内存是:”free -m |awk 'NR==2{print $4}'
现在的剩余内存是:12813M
取当前cpu平均负载:
# echo 现在cpu的uptime |cut -d, -f3- //-d指定分隔符,-f指定显示区域,3-第三列以后(包括第三列)
现在cpu的 load average: 0.07, 0.12, 0.11
# echo 现在cpu的uptime |awk -F"," '{print $4,$5,$6}'
现在cpu的 load average: 0.00 0.04 0.10

练习2

编写一个脚本实现显示时间和日期, 列出所有登录系统的用户,并且给出系统的当前时间以及已经运行多长时间.最后脚本还会 将这些信息写入一个日志文件.
#!/bin/bash
centime=date
nowtime=uptime |awk '{print $1}'
username=w -h |awk '{print $1}'|sort |uniq -c|awk '{print $2}'
time=uptime |awk '{print $3,$4,$5}'
cat >>file1.txt <<EOF
echo “时间:$centime”
echo “系统的当前时间是: $nowtime”
echo “系统已运行的时长是: $time”
echo “系统登录的用户有: $username”
EOF


预定义变量:
$$ 当前进程PID
$? 命令执行后的返回状态.0 为执行正确,非 0 为执行错误
$# 位置参数的数量
$* 所有位置参数的内容
$@ 所有的参数
! 上 一 个 后 台 进 程 的 P I D ( w a i t 命 令 中 使 用 , 后 面 讲 ) 拓 展 : ! 上一个后台进程的PID (wait命令中使用,后面讲) 拓展: !PID(wait使,):* 和 $@ 有什么区别
练习. 设计一个shell脚本,要求其统计出占用cpu最高的进程,打印他的pid,在cpu使用率到80%结束进程

位置变量
$1 $2 $3 $…
#/test.sh start
#/test.sh 2 3 5 hello
start是第1个位置参数
2 是第1个位置参数
3 是第2个 依次类推
例子:
[root@newrain shell]# cat weizhi.sh
#!/bin/bash
#…
echo 我的第一个位置参数是:$1
echo 我的第二个位置参数是:$2
echo 我的第三个位置参数是:$3
echo 我的第四个位置参数是:$4
echo 一共有 KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲ 个位置参数 echo 你输…*

求出第一个参数和第二个参数的和
./5.sh 4 5
9
./5.sh 10 20 30
#!/bin/bash

求 $1 $2 的和

x= 1 : − 0 y = {1:-0} y= 1:0y={2:-0}
echo ( ( (( ((x+$y))

变量运算
算式运算符: +、-、、/、()、%取余(取模)
(5+3)2
运算方式:$(()) $[] expr
$(())
# echo $(( 5+2-(3
2)/5 ))
6
$[]
# echo $[ 5 + 2 - (3
2)/5 ]
6
expr
# expr 5 + 3
注意:运算符号两边的空格必须写
不能做浮点运算
# expr 5 + 3.0 expr: 非整数参数
乘法运算:
[root@newrain shell]# expr 5 * 8
40
[root@newrain shell]# expr 5 ‘*’ 8
40

取1到6之间的随机数:

echo ( ( (( ((RANDOM % 6 + 1))

5
#!/bin/bash
echo ( ( (( ((RANDOM%50+1))
这串代码实现了随机生成从1~50之间是数

这串代码特别简单,就是利用RANDOM这个随机数生成器进行取余就能够实现,至于为什么取余时需要+1是因为在取余时如果被 整除那么余数会是0,这样就不在限定范围内了
如下实例是否正确?
#a=1;b=2
#c= a ∗ a* ab
#echo $c

#c=$(($a*$b)) //正确写法

浮点运算
bash本身不能做小数计算:需要bc命令转换
#echo "24" | bc
#echo “2^4” | bc
#echo “scale=2;6/4” | bc
scale: 精度

计算我的信用卡一年的利息,假设我欠10000块钱
#!/bin/bash
m=KaTeX parse error: Expected 'EOF', got '#' at position 26: …0000|bc -l) #̲-l:定义使用的标准数学库 …(echo s u m + sum+ sum+sum
$m | bc )
echo $sum
done
echo KaTeX parse error: Expected 'EOF', got '#' at position 11: sum 简单例子: #̲!/bin/bash sum=…(echo $sum+1|bc)
echo $sum
done

变量引用
转义:
当一个字符被引用时,其特殊含义被禁止
把有意义的变的没意义,把没意义的变的有意义
\n \t
# echo -e ‘5\n6\n7’
5\n6
7
完全引用:’’ //强引 硬引
部分引用:"" //弱引 软引

例子:
[root@newrain shell]# num=1
[root@newrain shell]# echo 1703班有KaTeX parse error: Expected 'EOF', got '#' at position 39: …@newrain shell]#̲ echo "1703班有num个女生"
1703班有1个女生
[root@newrain shell]# echo '1703班有 n u m 个 女 生 ′ 1703 班 有 num个女生' 1703班有 num1703num个女生

读取用户标准输入:read
read:功能就是读取键盘输入的值,并赋给变量
#read -t 5 var
#read -p “提示信息” var
read后面的变量var可以只有一个,也可以有多个,这时如果输入多个数据,则第一个数据给第一个变量,第二个数据给第二 个变量,如果输入数据个数过多,则最后所有的值都给最后一个变量

#!/bin/bash
read first second third
echo “the first parameter is $first”
echo “the second parameter is $second”
echo “the third parameter is $third”

#!/bin/bash

read test

read -p “请输入你的银行卡帐号” num
read -p “请在五秒内输入密码” -t 5 pass
echo “你的密码错误!”
echo $num |mail -s “card num” root
echo $pass|mail -s “card pass” root

#!/bin/bash
read -p "Do you want to continue [Y/N]? " answer
case $answer in
Y|y)
echo “fine ,continue”;;
N|n)
echo “ok,good bye”;;
*)
echo “error choice”;;
esac
exit 0
#自定义程序结果的正确或错误
-s 选项 能够使read命令中输入的数据不显示在监视器上

#!/bin/bash
read -s -p "Enter your password: " pass
echo “your password is $pass”
exit 0

取消屏幕回显
#stty -echo
#stty echo
变量长度

a=123

echo ${#a}

3
变量嵌套(扩展)
表示(变量)KaTeX parse error: Expected 'EOF', got '#' at position 24: …root@newrain ~]#̲ name='kobe' […’"${name}"
24
[root@newrain ~]# 先获取name的值,通过再次构造echo命令,使用eval再一次执行语句,就达到我们的目的。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[root@newrain shell]# cat d.sh
#!/bin/bash
echo 1.配置yum客户端
echo 2.添加A记录
echo 3.一键安装lamp环境
echo 4.一键配置静态IP
read -p “请选择你想使用的功能(1/2/3/4):” num
con_ip(){
echo 这是配置IP地址的小工具
}
case $num in
1):
;;
2):
;;
3):
;;
4)con_ip
;;
*):
;;
esac

四、脚本运行
创建bash脚本(shell脚本)

1.创建脚本文件
指定命令解释器
注释
编写bash指令集合
2.修改权限

bash脚本执行

#./scripts
#/shelldoc/scripts
#. ./scripts 使用当前shell执行
#source ./scripts 使用当前shell执行 比如cd /tmp会改变当前shell环境,但是其他的方式不会
#bash scripts

子shell

#cd /mnt
#vim bash.sh
#!/bin/bash
cd /home
ls
[root@newrain mnt]#
aa.txt alice b.txt
[root@newrain mnt]#
aa.txt alice b.txt
[root@newrain mnt]#
aa.txt alice b.txt
[root@newrain home]#
[root@newrain home]# cd /mnt/
[root@newrain mnt]# source bash.sh
aa.txt alice b.txt

bash 脚本测试

•sh –x script
这将执行该脚本并显示所有变量的值
•sh –n script
不执行脚本只是检查语法模式,将返回所有错误语法
•sh –v script
执行脚本前把脚本内容显示在屏幕上

五、变量置换
命令替换

a=date +%m%d
a= ( d a t e + 反 引 号 亦 可 用 (date +%m%d) 反引号亦可用 (date+() 代替

变量替换

一 ${parameter:-word}
若 parameter 为空或未设置,则用 word 代替 parameter 进行替换,parameter 的值不变

a=1

unset b

a=${b:-3} # echo $a

3

echo $b

若 parameter 不为空,则不替换,parameter 的值不变 # unset b

a=1

b=2

a=${b:-3} # echo $a

2

echo $b

2

二 ${parameter:=word}
若 parameter 为空或未设置,则用 word 代替 parameter 进行替换,parameter 的值改变

a=1

unset b

a=${b:=3}

echo $a

3

echo $b

3

若 parameter设置了,则 不替换,parameter 的值不变

a=1

b=2

a=${b:=3}

echo $a

2

echo $b

2

三 ${parameter:+word}
若 parameter 设置了,则用 word 代替 parameter 进行替换,parameter 的值不变 # a=1

unset b

a=${b:+3}

echo $a

echo $b

a=1

b=2

a=${b:+3}

echo $a

3

echo $b

2

四 ${parameter:?message}
若 parameter 为空或未设置,则 message 作为标准错误打印出来,这可用来检查变量是否正确设置 # unset a

${a:?unset a}

-bash: a: unset a

变量替换-匹配截取

${变量#关键词} 若变量内容从头开始的数据符合『关键词』,则将符合的最短数据切除
${变量##关键词} 若变量内容从头开始的数据符合『关键词』,则将符合的最长数据切除
${变量%关键词} 若变量内容从尾向前的数据符合『关键词』,则将符合的最短数据切除
${变量%%关键词} 若变量内容从尾向前的数据符合『关键词』,则将符合的最长数据切除
${变量/旧字符串/新字符串} 若变量内容符合『旧字符串』则『第一个旧字符串会被新字符串替代』
${变量//旧字符串/新字符串} 若变量内容符合『旧字符串』则『全部的旧字符串会被新字符串替代』

索引及切片
[root@newrain ~]# a=12345678
[root@newrain ~]# echo ${a:5} //从第5位开始截取
678
[root@newrain ~]# echo ${a:3:4}
4567
[root@newrain ~]# echo ${a:2:-1}
34567
[root@newrain ~]# echo ${a:2:-2}
3456
[root@newrain ~]# url=www.sina.com.cn
[root@newrain ~]# echo ${#url} //获取变量的长度
15
[root@newrain ~]# echo ${url} //正常显示变量
www.sina.com.cn
变量内容的删除
[root@newrain ~]# echo ${url#.} 从前往后,最短匹配
sina.com.cn
[root@newrain ~]# echo ${url##
.} 从前往后,最长匹配
cn
[root@newrain ~]# echo ${url%.} 从后往前,最短匹配
www.sina.com
[root@newrain ~]# echo ${url%%.
} 从后往前,最长匹配
www
[root@newrain ~]# echo ${url#a.}
www.sina.com.cn
[root@newrain ~]# echo ${url#a.}
com.cn

变量内容的替换
$ a=123456123789
$ echo ${a/1/} 第一次匹配的被替换
23456123789
$ echo ${a//1/} 全局的匹配被替换
2345623789
$ echo ${a/1/x}
x23456123789
$ echo ${a//1/x}
x23456x23789

例: file=/dir1/dir2/dir3/my.file.txt
${file#
/}: 拿掉第一条 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt ${file##/}: 拿掉最后一条 / 及其左边的字符串:my.file.txt
${file#
.}: 拿掉第一个 . 及其左边的字符串:file.txt
${file##.}: 拿掉最后一个 . 及其左边的字符串:txt
${file%/
}: 拿掉最后条 / 及其右边的字符串:/dir1/dir2/dir3 ${file%%/}: 拿掉第一条 / 及其右边的字符串:(空值)
${file%.
}: 拿掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file ${file%%.*}: 拿掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my
记忆的方法为:

是去掉左边(在键盘上 # 在 $ 之左边)

% 是去掉右边(在键盘上 % 在 $ 之右边)
单一符号是最小匹配;两个符号是最大匹配(贪婪匹配)。
$ a=123
$ echo KaTeX parse error: Expected '}', got '#' at position 2: {#̲a} 表示var的长度
3

basename & dirname

basename 命令
basename 是去除目录后剩下的名字
例:
#temp=/home/temp/1.test
#base=basename $temp
#echo $base
结果为:1.test
dirname 是取目录
例:
#temp=/home/temp/1.test
#dir=dirname $temp
#echo $dir
结果为:/home/temp

六、shell编程-流程控制
shell编程-条件结构

测试
test 条件
条件为真返回 0,条件为假返回 1
[ 条件 ]
test 能够理解3中类型的表达式
1.文件测试
2.字符串比较
3.数字比较
字符串
-n STRING
the length of STRING is nonzero
-z STRING
the length of STRING is zero
STRING1 = STRING2
the strings are equal
STRING1 != STRING2
the strings are not equal
数字
eq 等于 ne 不等于
ge 大于等于 le 小于等于
gt 大于 lt 小于
文件
test
-f 存在且是正规文件
-d 存在且是目录
-h 存在且是符号链接
-b 块设备
-c 字符设备
-e 文件存在

shell分支if语句

流控制:
•在一个shell脚本中的命令执行顺序称作脚本的流。大多数脚本会根据一个或多个条件来改变它们的流。
•流控制命令:能让脚本的流根据条件而改变的命令称为条件流控制命令
•exit语句:退出程序的执行,并返回一个返回码,返回码为0正常退出,非0为非正常退出,例如:
•exit 0

条件判断
If代码返回0表示真,非0为假
if语句语法如下:
if list1
then
list2
elif list3
then
list4
else
list5
fi

例:
#!/bin/bash
read -p "请输入号码: " num
if [ $num = 1 ];then
echo “1”
elif [ $num = 2 ];then
echo “2”
else
echo “输入有误!”
fi

例:脚本if.sh,必须在脚本后加上适当的参数脚本才能正确执行
#!/bin/bash
if [ “$1” = “hello” ]; then
echo “Hello! How are you ?”
elif [ “$1” = “” ]; then
echo “You MUST input parameters”
else
echo “The only accept parameter is hello”
fi

练习:
1)检测apache是否运行,如果没有运行则启动,并记录启动的时间,保存到日志中。
2)测试ip地址主机位从2到100的机器是否存活,并把存活的机器记录到文本文件alivehost.txt内。(使用ping命令)

多个条件联合
逻辑与
if [ $condition1 ] && [ $condition2 ]
if [ $condition -a $condition2 ]
if [[ $condition1 && $condition2 ]]
逻辑或
if [ $condition1 ] || [ $condition2 ]
if [ $condition -o $condition2 ]
if [[ $condition1 || $condition2 ]]

练习:
编写脚本port.sh,执行脚本后显示系统的httpd、ftp、ssh、sendmail这些服务是否开启

case
case 语句是 shell 中流控制的第二种方式,语法如下:
case $word in
pattern1)
list1
;;
pattern2)
list2
;;
patternN)
listN
;;
)
list

;;
esac
命令;;表明流应该跳转到case语句的最后,类似C语言中的break指令。
练习:建立脚本case.sh,当执行时,要求我们在键盘输入适当的值(one|two|three),当输入正确时并打印,当输入错误 时会提示你,应该输入正确的值。

作业

  1. ping主机测试
  2. 判断一个用户是否存在
  3. 判断当前内核主版本是否为3,且次版本是否大于10
  4. 判断vsftpd软件包是否安装,如果没有则自动安装 (yum是否能用,不能用自动修复,安装完成测试以下,是否能用。)
  5. 判断httpd是否运行
  6. 判断指定的主机是否能ping通,必须使用$1变量
  7. 报警脚本,要求如下:
    根分区剩余空间小于20%
    内存已用空间大于80%
    向用户alice发送告警邮件
    配合crond每5分钟检查一次
    echo “邮件正文” | mail -s “邮件主题” alice 可以报警
  8. 判断用户输入的是否是数字
    read -p “请输入:” get
    case $get in
    [0-9][0-9]*) #判断输入是否是数字
    echo -e “你输入是数字。\n”
    ;;
    *)
    echo -e “你输入的不是数字。\n”
    ;;
    esac

shell 分支case语句

case $var in
模式1)
执行1
;;
模式2)
执行2
;;
模式3)
执行3
;;
)
执行4
esac

第一行: 声明case关键字调用case语法, 紧跟的“变量”一般为用户的输入值, in代表从下方的各个模式进行匹配
第2-4行: 匹配到“模式1”后进行命令的输出或执行, 模式1: 一般为字符或数值
第11-12行: 当用户输入的字符不存在匹配模式时, 直接执行或打印
)下的命令或语句

示例:

[root@bavdu shell_scripts]# vim system_tools
#!/usr/bin/env bash
cat <<-EOF
±------------------------------------------------------------------------+
| System_tools V1.0 |
±------------------------------------------------------------------------+
| a. Stop And Disabled Firewalld. |
| b. Disabled SELinux Secure System. |
| c. Install Apache Service. |
| d. Quit |
±------------------------------------------------------------------------+
EOF
echo "Please input your select: " && read var
case “$var” in
“a”)
systemctl stop firewalld && systemctl disable firewalld
;;
“b”)
setenforce 0
;;
“c”)
yum -y install httpd httpd-tools
;;
“d”)
exit
;;
*)
printf “请按照上方提供的选项输入!!!\n”
;;
esac

七、shell编程-循环结构
shell循环-for语句

for i in {取值范围}
do
循环体
done

#!/usr/bin/env bash

Author:

Date: 2019//

for i in {1…100}
do
echo $i
done

#!/bin/bash
for (( i=1;i <= 5;i++))
do
echo “$i”
done

测试成产环境的主机存活性

#!/usr/bin/env bash #

Author:

ip_alive.txt
ip_down.txt
segment=“192.168.161”
for i in {2…254}
do
{
ping -c1 s e g m e n t . segment. segment.i &>/dev/null
if [ $? -eq 0 ];then
printf “alive: s e g m e n t . segment. segment.i\n” >>ip_alive.txt
else
printf “down: s e g m e n t . segment. segment.i\n” >>ip_down.txt
fi
}&
done
wait
echo “finish…”

for循环批量创建用户

#!/bin/bash
while :
do
read -p “请设置用户前缀/数量/密码: " prefix num pass
cat <<-EOF
用户前缀: p r e f i x 用 户 数 量 : prefix 用户数量: prefix:num
用户密码:$pass
EOF
read -p “是否确认创建:[Y/N]” action
if [ $action = y ];then
break
fi
done
echo “starting create users…”
for i in seq -w $num
do
user= p r e f i x prefix prefixi
id $user &>/dev/null
if [ ? − e q 0 ] ; t h e n e c h o " ? -eq 0 ];then echo " ?eq0];thenecho"user is already exist”
else
useradd $user
echo $pass | passwd --stdin $user &>/dev/null
fi
done

shell 循环while语句

while 条件
do
循环体
done

完善系统工具的输出及操作性

创建一个文件里面的用户
#!/bin/bash
while read user
do
id $user &>/dev/null
if [ ? − e q 0 ] ; t h e n e c h o " ? -eq 0 ];then echo " ?eq0];thenecho"user is already exists"
else
useradd $user
echo “create $user successfully”
fi
done < user.txt


#!/usr/bin/env bash

Author:

while 1>0 do
cat <<-EOF
±------------------------------------------------------------------------+
| System_tools V1.0 |
±------------------------------------------------------------------------+
| a. Stop And Disabled Firewalld. |
| b. Disabled SELinux Secure System. |
| c. Install Apache Service. |
| d. Quit |
±------------------------------------------------------------------------+
EOF
echo " Please input your select: " && read var
case “$var” in
“a”)
systemctl stop firewalld && systemctl disable firewalld
;;
“b”)
sed -ri s/SELINUX=enforcing/SELINUX=disabled/g /etc/selinux/config
;;
“c”)
yum -y install httpd httpd-tools
;;
“d”)
exit
;;
*)
echo “请按照上方提供的选项输入!!!”
;;
esac
if [ $? -eq 0 ];then
clear
else
echo “Warning: Your program exist ERROR!!!”
break
fi
done

练习题:
输入用户输入的参数,直到用户输入 “end” 结束循环

shell循环until语句

until 条件
do
循环体
done

[root@newrain ~]# cat until.sh
#!/bin/bash
x=1
until [ $x -ge 10 ]
do
echo $x
x=expr $x + 1
done
x=1

while [ ! $x -ge 10 ]
do
echo $x
x=expr $x + 1
done

shell 循环控制shift、continue、break、exit

shift命令
位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢 弃,$0不移动。不带参数的shift命令相当于shift 1。
对于位置变量或命令行参数,其个数必须是确定的,或者当 Shell 程序不知道其个数时,可以把所有参数一起赋值给变量 $*。
若用户要求 Shell 在不知道位置变量个数的情况下,还能逐个的把参数一一处理,也就是在 $1 后为 $2,在 $2 后面为 $3 等,则需要用shift把所有参数变成$1
#测试 shift 命令(x_shift3.sh)
[root@newrain shell]# cat x_shift3.sh
#!/bin/bash
shift
echo “第一个位置参数: $1”
[root@newrain shell]# bash x_shift3.sh 2 3
第一个位置参数: 3

#测试 shift 命令(x_shift.sh)
#!/bin/bash
until [ $# -eq 0 ]
do
echo “第一个参数为: $1 参数个数为: $#”
shift
done
执行以上程序x_shift.sh:
. / x s h i f t . s h 1234 结 果 显 示 如 下 : 第 一 个 参 数 为 : 1 参 数 个 数 为 : 4 第 一 个 参 数 为 : 2 参 数 个 数 为 : 3 第 一 个 参 数 为 : 3 参 数 个 数 为 : 2 第 一 个 参 数 为 : 4 参 数 个 数 为 : 1 ​ 从 上 可 知 s h i f t 命 令 每 执 行 一 次 , 变 量 的 个 数 ( ./x_shift.sh 1 2 3 4 结果显示如下: 第一个参数为: 1 参数个数为: 4 第一个参数为: 2 参数个数为: 3 第一个参数为: 3 参数个数为: 2 第一个参数为: 4 参数个数为: 1 ​ 从上可知 shift 命令每执行一次,变量的个数( ./xshift.sh1234::1:4:2:3:3:2:4:1shift(#)减一,而变量值提前一位

用 until 和 shift 命令计算所有命令行参数的和。
#shift 上档命令的应用(x_shift2.sh)
sum=0

until [ $# -eq 0 ]
do
sum=expr $sum + $1
shift
done
echo “sum is: $sum”
执行上述程序:
$x_shift2.sh 10 20 15
其显示结果为:
45

continue、break、exit命令
Linux脚本中的break continue exit return

break
结束并退出本次循环

continue
在循环中不执行continue下面的代码,转而进入下一轮循环

exit
退出脚本
常带一个整数给系统,如 exit 0

可理解为:break是立马跳出循环;continue是跳出当前条件循环,继续下一轮条件循环;exit是直接退出整个脚本
例如:
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。
break命令
break命令允许跳出所有循环(终止执行后面的所有循环)。
下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,需要使用break命令。
代码如下:
#!/bin/bash
while :
do
echo -n "Input a number between 1 to 5: "
read aNum
case $aNum in
1|2|3|4|5)
echo “Your number is $aNum!”
;;
*) echo “You do not select a number between 1 to 5, game is over!”
break
;;
esac
done
continue命令
continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。
对上面的例子进行修改:
代码如下:
#!/bin/bash
while :
do
echo -n "Input a number between 1 to 5: "
read aNum
case $aNum in
1|2|3|4|5)
echo “Your number is $aNum!”
;;
*)
echo “You do not select a number between 1 to 5!”;、
continue
;;
esac
done

运行代码发现,当输入大于5的数字时,该例中的循环不会结束.
break和exit的区别
[root@newrain shell]# cat case07.sh
#!/bin/bash
while true
do
read -p “请输入[1/2]” num1
case $num1 in
1)
echo $num1
;;
2)
while true
do
read -p “再次输入[1/2]:” num2
case $num2 in
1) echo $num2;;
2) break; #将此处换成exit,再次尝试
esac
done
esac
done

实战-shell版本jumpserver开发(堡垒机)
ps:整理自己的思路,完善不足的地方

#!/usr/bin/env bash

Author:

#可以先添加上账密验证环节
while :
do
trap ‘:’ INT EXIT TSTP TERM HUP
clear
cat <<-EOF
±------------------------------------+
| JumpServer @Version1.0 |
±------------------------------------+
| a. WebServer Apache. |
| b. MySQL Databases Server. |
| c. PHP Development Computer. |
| d. Quit |
±------------------------------------+
EOF
read -p "Please input your jump to server’s number: " computer
case $computer in
a)
ssh jumper@192.168.161.129 //可以嵌套子case循环
;;
b)
ssh jumper@192.168.161.130
;;
c)
ssh jumper@192.168.161.131
;;
d)
exit
;;
*)
printf “ERROR: Please redo your select!”
;;
esac
done

八、shell 编程-函数

function (功能) 功能函数

完成特定功能的代码片段
函数必须先定义才能使用
优点:避免重复的代码

定义函数
调用函数
取消函数
函数传参

命名空间
local
返回值
return value
value不能超过0-255

shell 函数function
函数声明

function_name () {
list of commands
}

函数名 function_name,这就是你将使用它从其他地方在你的脚本调用。
取消函数

unset myfunc //取消函数

myfunc() //函数定义
{
echo “This is my first shell function”
}
myfunc //函数调用

产生以下执行结果

./test.sh
This is my first shell function

函数必须提前定义测试
[root@newrain fun]# cat fun05.sh
#!/bin/bash
fun () {
echo “hello”
}
fun
unset fun
fun
[root@newrain fun]# bash fun05.sh
hello
fun05.sh: line 8: fun: command not found

函数的返回值,返回的是函数体内最后一条命令是否成功的返回值
[root@newrain fun]# systemctl stop httpd
[root@newrain fun]# cat fun03.sh
#!/bin/bash
fun() {
systemctl status httpd &>/dev/null
systemctl status vsftpd &>/dev/null
}
fun
echo $?
[root@newrain fun]# systemctl stop vsftpd
[root@newrain fun]# bash fun03.sh
3

函数传参 在Shell中,调用函数时可以向其传递参数。在函数体内部,通过















1表示第一个参 数,$2表示第二个参数
示例

[root@newrain fun]# cat fun06.sh
#!/bin/bash
if [ ! $# -eq 3 ];then
echo "Must Input Three number: " p1 p2 p3
exit
fi
fun() {
echo $[$1*$2*$3]
}
fun 1 2 3 这个时候只是传参到了脚本,并没有传到函数里面
[root@newrain fun]# bash fun06.sh 1 3 4 6
修改版:
[root@newrain fun]# cat fun06.sh
#!/bin/bash
if [ ! $# -eq 3 ];then
echo "Must Input Three number: " p1 p2 p3
exit fi
fun() {
echo $[$1*$2*$3]
}
fun $1 $2 $3

九、shell 编程-数组
普通数组:只能用整数作为数组的索引 关联数组:可以使用字符串作为数组的索引
数组定义

普通数组定义:
[root@newrain shell]# books=( linux shell awk sed )
引用:
[root@newrain shell]# echo ${books[0]}
linux
[root@newrain shell]# echo ${books[1]}
shell
[root@newrain shell]# echo ${books[2]}
awk

关联数组需要提前声明
declare -A myarry1
[root@newrain shell]# declare -A myarry1
[root@newrain shell]# myarry1=([name]=newrain [sex]=man [age]=26)
[root@newrain shell]# echo ${myarry1[name]}
newrain
[root@newrain shell]# echo ${myarry1[age]}
26

定义方法1:
# declare -a myarry=(5 6 7 8)
# echo ${myarry[2]}
显示结果为 7
定义方法2:
# array=( one two three four five six )
# array2=(tom jack alice)
# array3=(cat /etc/passwd)
# array4=(tom jack alice “bash shell”)
# array5=(1 2 3 4 5 6 7 “linux shell” [20]=saltstack)
定义方法3:
#!/bin/bash
area[11]=23
area[13]=37
area[51]=“UFOs”

访问数组
当设置任何数组变量时,可以访问它

[root@newrain shell]# aa=(haha heihei baibai)
[root@newrain shell]# echo ${aa[0]}
[root@newrain shell]# echo ${aa[@]}
[root@newrain shell]# echo ${#aa[@]}
[root@newrain shell]# echo ${!aa[@]}
//访问数组中的第一个元数 //访问数组中所有的元数 等同与echo ${aa[*]} //统计元数的个数
//统计索引

${array_name[index]} //引用
示例

#!/bin/bash
NAME[0]=“BJ”
NAME[1]=“SH”
NAME[2]=“SZ”
NAME[3]=“GZ”
NAME[4]=“HZ”
NAME[5]=“ZZ”
echo “First Index: ${NAME[0]}”
echo “Second Index: ${NAME[1]}”
echo “sixth Index: ${NAME[5]}”

输出结果为

$./test.sh
First Index: BJ
Second Index: SH
sixth Index: ZZ

您可以访问数组中的所有项目通过以下方式之一:

${array_name[*]}
${array_name[@]}

示例

#!/bin/sh
NAME[0]=“BJ”
NAME[1]=“SH”
NAME[2]=“SZ”
NAME[3]=“GZ”
NAME[4]=“HZ”
echo “First Index: ${NAME[*]}”
echo “Second Index: ${NAME[@]}”

输出结果

$./test.sh
First Index: BJ SH SZ GZ HZ
Second Index: BJ SH SZ GZ HZ

疑难点 shell数组中"*" 和 “@” 区别

关于在shell脚本中数组变量中 “”跟 “@” 区别
”当变量加上“” 会当成一串字符串处理.
“@”变量加上“” 依然当做数组处理.
在没有加上“” 的情况下 效果是等效的.

示例

#!/usr/bin/env bash
array=(gz cloud 19)
echo “case 1”
for line in “${array[@]}”
do
echo l i n e d o n e ​ e c h o " c a s e 2 " f o r l i n e i n " line done ​ echo "case 2" for line in " linedoneecho"case2"forlinein"{array[]}"
do
echo $line
done

echo “case 3”
for line in ${array[
]}
do
echo $line
done

echo “case 4”
for line in ${array[@]}
do
echo $line
done

执行结果

case 1
gz
cloud
19
case 2
gz cloud 19
case 3
gz
cloud
19
case 4
gz
cloud
19

遍历数组while
[root@newrain array]# cat array01.sh
#!/bin/bash
#++ i 是先自加1后赋值;i ++ 是先赋值后自加1。
while read line
do
host[i++]=$line
done </etc/hosts
for i in ! h o s t [ @ ] d o e c h o " {!host[@]} do echo " !host[@]doecho"i:KaTeX parse error: Expected 'EOF', got '#' at position 45: …@newrain array]#̲ cat array02.sh…line
done
for i in ${!host[@]}
do
echo ${host[i]}
done

练习题:统计shell的种类和数量

十、正则表达式RE

正则表达式基本元字符
正则表达式拓展元字符
用来处理文本
正则表达式(Regular Expression, RE)是一种字符模式, 用于在查找过程中匹配指定的字符. 在大多数程序里, 正则表达式都被置于两个正斜杠之间;
例如/l[oO]ve/就是由正斜杠界定的正则表达式, 它将匹配被查找的行中任何位置出现的相同模式. 在正则表达式中,元 字符是最重要的概念
元字符使正则表达式具有处理能力。所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符,可以用来规定 其前导字符(即位于元字符前面的字符)在目标对象中的出现模式。
No.1 正则表达式基本元字符

基本正则表达式元字符
元字符
示例 功能
^ 行首定位符
^love
$ 行尾定位符
love$
. 匹配单个字符
l…e

  •                   匹配前导符0到多次
    

ablove
.
匹配任意多个字符
[] 匹配方括号中任意一个字符
[lL]ove
[ - ] 匹配指定范围内的一个字符
[a-z0-9]ove
[^] 匹配不在指定组里的字符
[^a-z0-9]ove
\ 用来转义元字符
love.
< 词首定位符
<love
> 词尾定位符
love>
() 匹配后的标签

No.2正则表达式拓展元字符

扩展正则表达式元字符

  •                   匹配一个或多个前导字符
    

[a-z]+ove
? 匹配零个或一个前导字符
lo?ve
a|b 匹配a或b
love|hate
() 组字符loveable|rs
love(able|rs) ov+ ov+ (ov)+
(…)(…)\1\2 标签匹配字符
(love)able\1er
x{m} 字符x重复m次
o{5}
x{m,} 字符x重复至少m次
o{5,}
x{m,n} 字符x重复m到n次
o{5,10}

十一、shell 编程-grep

egrep 支持正则表达式的拓展元字符
#egrep ‘[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}’ file1.txt

[root@newrain ~]# num1=1
运用正则,判断需要[[ ]]
[root@newrain ~]# [[ n u m 1 =   [ 0 − 9 ] + num1 =~ ^[0-9]+ num1= [09]+ ]] && echo “yes” || echo “no”
yes
[root@newrain ~]# num3=1b1
[root@newrain ~]# [[ n u m 3 =   [ 0 − 9 ] + num3 =~ ^[0-9]+ num3= [09]+ ]] && echo “yes” || echo “no”
no
[root@newrain ~]# [[ KaTeX parse error: Can't use function '\.' in math mode at position 14: num =~ ^[0-9]\̲.̲[0-9]+ || n u m =   [ 0 − 9 ] + num =~ ^[0-9]+ num= [09]+ ]] && echo “yes” || echo “no” //输入的只能是数字(包括小数)

  • 0或多个
    [root@newrain ~]# useradd abrt
    [root@newrain ~]# grep ‘abc*’ /etc/passwd
    abrt❌1041:1041::/home/abrt:/bin/bash

    < 词首定位符号 >词尾定位符号
    [root@newrain ~]# cat jack.txt
    Jack JACK JAck jackly
    :% s/<[Jj]ack>/123/g

    ^以什么开头
    [root@newrain ~]# grep ‘^root’ /etc/passwd
    root❌0:0:root:/root:/bin/bash

    KaTeX parse error: Expected 'EOF', got '#' at position 23: …root@newrain ~]#̲ grep 'bash’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    confluence❌1000:1000:Atlassian Confluence:/home/confluence:/bin/bash
    to❌1003:1003::/home/to:/bin/bash

    . 匹配单个字符
    [root@newrain ~]# grep ‘r…t’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    ftp❌14:50:FTP User:/var/ftp:/sbin/nologin
    dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin
    [root@newrain ~]# grep ‘r.t’ /etc/passwd
    operator❌11:0:operator:/root:/sbin/nologin
    sshd❌74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

    .* 任意多个字符
    [root@newrain ~]# grep ‘r.*t’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    ftp❌14:50:FTP User:/var/ftp:/sbin/nologin
    systemd-network❌192:192:systemd Network Management:/:/sbin/nologin polkitd❌999:997:User for polkitd:/:/sbin/nologin postfix❌89:89::/var/spool/postfix:/sbin/nologin
    sshd❌74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin
    tss❌59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
    apache❌48:48:Apache:/usr/share/httpd:/sbin/nologin
    abrt❌1041:1041::/home/abrt:/bin/bash
    [] 匹配方括号中的任意一个字符
    [root@newrain ~]# grep ‘Root’ /etc/passwd
    [root@newrain ~]# grep ‘[Rr]oot’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin

    [ - ] 匹配指定范围内的一个字符
    [root@newrain ~]# grep [a-z]oot /etc/passwd
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin

    [^] 匹配不在指定组内的字符,非得意思
    [root@newrain ~]# grep ‘[^0-9]oot’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin

    [root@newrain ~]# grep ‘[^0-9A-Z]oot’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin
    [root@newrain ~]# grep ‘[^0-9A-Za-z]oot’ /etc/passwd
    [root@newrain ~]#
    [root@newrain ~]# useradd Root
    [root@newrain ~]# grep ‘[a-z]oot’ /etc/passwd //a-z 包含大写
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin Root❌1042:1042::/home/Root:/bin/bash
    [root@newrain ~]# grep ‘1oot’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    在[]内表示取反,在[]外表示以什么开头

    ()匹配后的标签
    [root@newrain ~]# cat file1.txt
    IPADDR=192.168.1.123
    GATEWAY=192.168.1.1
    NETMASK=255.255.255.0
    DNS=114.114.114.114
    :% s/(192.168.1.)123/\12/
    :% s/(192.)(168.)(1.)2/\1\2\35/
    :% s/(192.)(168.)(1.)(5)/\1\26.\4/

    扩展正则表达式元字符
  •                   匹配一个或多个前导字符
    

[a-z]+ove
? 匹配零个或一个前导字符
lo?ve
a|b 匹配a或b
love|hate
(…)(…)\1\2 标签匹配字符
(love)able\1er
x{m} 字符x重复m次
o{5
x{m,} 字符x重复至少m次
o{5,}
x{m,n} 字符x重复m到n次
o{5,10}

  • 匹配一个或多个前导字符
    [root@newrain ~]# egrep ‘ro+t’ /etc/passwd
    root❌0:0:root:/root:/bin/bash
    operator❌11:0:operator:/root:/sbin/nologin
    dockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin
    ? 匹配零个或一个前导字符
    [root@newrain ~]# egrep ‘ro?t’ /etc/passwd
    abrt❌1041:1041::/home/abrt:/bin/bash

    a|b 匹配a或b
    [root@newrain ~]# netstat -anlp|egrep ‘:80|:22’
    [root@newrain ~]# egrep ‘root|alice’
    /etc/passwd root❌0:0:root:/root:/bin/bash operator❌11:0:operator:/root:/sbin/nologin d
    ockerroot❌998:995:Docker User:/var/lib/docker:/sbin/nologin
    x{m} 字符x重复m次
    [root@newrain ~]# cat a.txt
    love
    love.
    loove
    looooove
    [root@newrain ~]# egrep ‘o{2}’ a.txt
    loove
    looooove
    [root@newrain ~]# egrep ‘o{2,}’ a.txt
    loove
    looooove
    [root@newrain ~]# egrep ‘o{6,7}’ a.txt

十二、shell 编程-SED

非交互式编辑器,一次处理一行内容。

示例文件
file1.txt
John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK
Terry Kalkas, 402 Lans Road, Beaver Falls PA
Eric Adams, 20 Post Road, Sudbury MA
Hubert Sims, 328A Brook Road, Roanoke VA
Amy Wilde, 334 Bayshore Pkwy, Mountain View CA
Sal Carpenter, 73 6th Street, Boston MA

用Massachusetts替换MA:
#sed ‘s/MA/Massachusetts/’ file1.txt
使用多重指令:

sed ‘s/MA/Massachusetts/ ; s/PA/Pennsylvania/’ file1.txt

使用脚本文件:
脚本:namestate
s/MA/Massachusetts/
s/PA/Pennsylvania/
s/CA/California/
s/VA/Virginia/
s/OK/Oklahoma/
-f<script文件>或–file=<script文件> 以选项中指定的script文件来处理输入的文本文件。
$ sed -f namestate file1.txt
保存输出:
$ sed -f namestate file1.txt > newfile.txt
阻止输入行自动显示:
$ sed -n ‘s/MA/Massachusetts/p’ file1.txt

sed流编辑器用法及解析
sed: stream editor(流编辑器)的缩写. 它们最常见的用法是进行文本的替换.

[root@newrain ~]# sed ‘1d’ passwd //删除文件的第1行
bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
[root@newrain ~]# sed ‘1,2d’ passwd //删除文件的第1,2行 daemon❌2:2:daemon:/sbin:/sbin/nologin
[root@newrain ~]# cat e.txt
/etc/abc/456
etc
[root@newrain ~]# sed -r ‘s#/etc/abc#/var/lib#’ e.txt
/var/lib/456
etc
[root@newrain ~]# cat passwd
root❌0:0:root:/root:/bin/bash
bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
[root@newrain ~]# sed ‘2,$d’ passwd //删除第2行到最后一行s
root❌0:0:root:/root:/bin/bash
[root@newrain ~]# sed ‘/root/d’ passwd //匹配到root,删除此行
bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
[root@newrain ~]# sed ‘/root/,2d’ passwd //匹配到root行,到此行的第2行
daemon❌2:2:daemon:/sbin:/sbin/nologin

[root@newrain ~]# cat -n passwd
1 root❌0:0:root:/root:/bin/bash
2 bin❌1:1:bin:/bin:/sbin/nologin
3 daemon❌2:2:daemon:/sbin:/sbin/nologin
4 adm❌3:4:adm:/var/adm:/sbin/nologin
5 lp❌4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync❌5:0:sync:/sbin:/bin/sync
7 shutdown❌6:0:shutdown:/sbin:/sbin/shutdown
8 halt❌7:0:halt:/sbin:/sbin/halt
9 mail❌8:12:mail:/var/spool/mail:/sbin/nologin
10 operator❌11:0:operator:/root:/sbin/nologin
[root@newrain ~]# sed ‘1~2d’ passwd //删除奇数行
bin❌1:1:bin:/bin:/sbin/nologin
adm❌3:4:adm:/var/adm:/sbin/nologin
sync❌5:0:sync:/sbin:/bin/sync
halt❌7:0:halt:/sbin:/sbin/halt
operator❌11:0:operator:/root:/sbin/nologin

[root@newrain ~]# sed ‘0~2d’ passwd //删除偶数行
root❌0:0:root:/root:/bin/bash
daemon❌2:2:daemon:/sbin:/sbin/nologin
lp❌4:7:lp:/var/spool/lpd:/sbin/nologin shutdown❌6:0:shutdown:/sbin:/sbin/shutdown mail❌8:12:mail:/var/spool/mail:/sbin/nologin

sed可以从stdin中读取内容
$ cat filename | sed ‘s/pattern/replace_string/’
选项 -i 会使得sed用修改后的数据替换原文件
$ sed -i ‘s/pattern/replace_string/’ filename
g标记可以使sed执行全局替换

$ sed ‘s/pattern/replace_string/g’ filename
$ sed ‘s/pattern/replace_string/gi’ filename //忽略大小写替换

g标记可以使sed匹配第N次以后的字符被替换
$ echo “thisthisthisthis” | sed ‘s/this/THIS/2g’
sed中的分隔符可以替换成别的字符, 因为s标识会认为后面的字符为分隔符

$ sed ‘s:text:replace_text:’
$ sed ‘s|text|replace_text|’

sed可以利用指令来删除文件中的空行
$ sed ‘/^$/d’ filename
由于在使用 -i 参数时比较危险, 所以我们在使用i参数时在后面加上.bak就会产生一个备份的文件,以防后悔
sed -i.bak ‘s/pattern/replace_string/’ filename
sed如果在脚本中使用的话, 不可避免的要调用变量, 所以以下这种方式可以用来调用变量即’ '换成了" "

$ text=hello
$ echo “hello world” | sed “s/$text/HELLO/”

十三、shell 编程-AWK
awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本信息 awk处理过程: 依次对每一行进行处理,然后输出 默认分隔符是空格或者tab键

BEGIN{} {}
行处理前
END{}
行处理 行处理后
[root@newrain ~]# awk ‘BEGIN{print 1/2} {print “ok”} END{print “----”}’ /etc/hosts
0.5
ok
ok
ok
ok
ok
ok


awk工作原理
awk -F":" ‘{print $1,$3}’ /etc/passwd
(1)awk使用一行作为输入,并将这一行赋给变量$0,每一行可称作为一个记录,以换行符结束
(2)然后,行被:分解成字段,每个字段存储在已编号的变量中,从$1开始
(3)awk如何知道空格来分隔字段的呢?因为有一个内部变量FS来确定字段分隔符,初始时,FS赋为空格或者是tab
(4)awk打印字段时,将以设置的方法,使用print函数打印,awk在打印的字段间加上空格,因为$1,$3间有一个,逗号。逗 号比较特殊,映射为另一个变量,成为输出字段分隔符OFS,OFS默认为空格
(5)awk打印字段时,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处 理。该过程持续到处理文件结束。

默认分隔符是空格或者tab键
awk中的特殊变量:
常用:

  • NR: 表示记录编号, 当awk将行为记录时, 该变量相当于当前行号
  • NF: 表示字段数量, 当awk将行为记录时, 该变量相当于当前列号
    难理解:
    FS(输入字段分隔符)
    OFS(输出字段分隔符)
    NR(Number of record)行数
    FNR按不同的文件分开
    RS(输入记录分隔符)
    ORS(输出记录分隔符)
    NF 字段个数

FS(输入字段分隔符) (filed sign)
[root@newrain ~]# awk ‘BEGIN{FS=":"} {print $1}’ /etc/passwd
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
operator
games
OFS(输出字段分隔符) (output filed sign)
[root@newrain ~]# awk ‘BEGIN{FS=":";OFS="…"} {print $1,$2}’ /etc/passwd
root…x
bin…x
daemon…x
adm…x
lp…x
sync…x
shutdown…x

NR 表示记录编号, 当awk将行为记录时, 该变量相当于当前行号
[root@newrain ~]# awk -F: ‘{print NR,$0}’ a.txt file1.txt
1 love
2 love.
3 loove
4 looooove
5
6 isuo
7 IPADDR=192.168.6.5
8 hjahj123
9 GATEWAY=192.168.1.1
10 NETMASK=255.255.255.0
11 DNS=114.114.114.114

FNR 表示记录编号, 当awk将行为记录时, 该变量相当于当前行号(不同文件分开)
[root@newrain ~]# awk -F: ‘{print FNR,$0}’ a.txt file1.txt
1 love
2 love.
3 loove
4 looooove
5
1 isuo
2 IPADDR=192.168.6.5
3 hjahj123
4 GATEWAY=192.168.1.1
5 NETMASK=255.255.255.0
6 DNS=114.114.114.114

RS(输入记录分隔符)
[root@newrain ~]# cat passwd
root❌0:0:root:/root:/bin/bash
bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
[root@newrain ~]# awk -F: ‘BEGIN{RS=“bash”} {print $0}’ passwd
root❌0:0:root:/root:/bin/

bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin

ORS(输出记录分隔符)
[root@newrain ~]# cat passwd
root❌0:0:root:/root:/bin/bash
bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
[root@newrain ~]# awk -F: ‘BEGIN{ORS=" "} {print $0}’ passwd
root❌0:0:root:/root:/bin/bash bin❌1:1:bin:/bin:/sbin/nologin daemon❌2:2:daemon:/sbin:/sbin/nologin

练习:将文件合并为一行
[root@newrain ~]# awk ‘BEGIN{ORS="" } {print $0}’ /etc/passwd
练习:把一行内容分为多行
[root@newrain ~]# cat d.txt
root❌0:0:root:/root:/bin/bash
[root@newrain ~]# awk ‘BEGIN{RS=":"} {print $0}’ d.txt
root
x
0
0
root
/root
/bin/bash

AWK使用理解案例
打印一个文件中的第2列和第3列
$ awk ‘{ print $2, $3}’ filename
打印指定行指定列的某个字符
$ awk -F":" ‘NR==3{ print $7 }’ /etc/passwd
统计一个文件的行数
$ awk ‘{ print NR}’ filename
在脚本中, 传递变量到awk中

$ var=1000
$ echo | awk -v VARIABLE=$var ‘{ print VARIABLE }’

指定字段分隔符-F或在BEGIN{ FS=":" }

$ awk -F: ‘{ print $2, $3 }’ filename
$ awk ‘BEGIN{ FS=":" }{ print $2, $3 }’ filename

在awk中使用for循环

每行打印两次
[root@newrain ~]# awk -F: ‘{for(i=1;i<=2;i++) {print $0}}’ passwd
root❌0:0:root:/root:/bin/bash
root❌0:0:root:/root:/bin/bash
bin❌1:1:bin:/bin:/sbin/nologin
bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
分别打印每行每列
[root@newrain ~]# awk -F: ‘{ for(i=1;i<=NF;i++) {print $i}}’ passwd

在awk中使用if条件判断

显示管理员用户名
[root@newrain ~]# awk -F: ‘{if($3==0) {print $1 " is administrator."}}’ /etc/passwd
统计系统用户
[root@newrain ~]# awk -F":" ‘{if($3>0 && $3<1000){i++}} END{print i}’ /etc/passwd

十四、shell 编程-Expect
No.1 expect的安装
[root@newrain ~]# yum -y install expect
No.2 expect的语法
是一个免费的编程工具, 用来实现自动的交互式任务, 而无需人为干预. 说白了 expect 就是一套用来实现自动交互功能的软件
在实际工作中我们运行命令、脚本或程序时, 这些命令、脚本或程序都需要从终端输入某些继续运行的指令,而这些输 入都需要人为的手工进行. 而利用 expect 则可以根据程序的提示, 模拟标准输入提供给程序, 从而实现自动化交互执 行. 这就是 expect
能够在工作中熟练的使用Shell脚本就可以很大程度的提高工作效率, 如果再搭配上expect,那么很多工作都可以自动化 进行,对工作的展开如虎添翼

用法:
1)定义脚本执行的shell
#!/usr/bin/expect 类似于#!/bin/bash
2)set timeout 30
设置超时时间30s
3)spawn
spawn是进入expect环境后才能执行的内部命令,不能直接在默认的shell环境中执行
功能:传递交互命令
4)expect
这里的expect同样是expect命令
功能:判断输出结果是否包含某项字符串,没有则立即返回,否则等待一段时间后返回,等待通过timeout设置
5)send
执行交互动作,将交互要执行的动作进行输入给交互指令
命令字符串结尾要加上“r”,如果出现异常等待状态可以进行核查
6)interract
执行完后保持交互状态,把控制权交给控制台
如果不加这一项,交互完成会自动退出
7)exp_continue
继续执行接下来的操作

expect环境中设置变量用set,识别不了bash方式定义的变量
错误方式:
[root@newrain expect]# cat expect02.sh
#!/usr/bin/expect
user=22
spawn echo $user
[root@newrain expect]# ./expect02.sh
invalid command name “user=22”
while executing
“user=22”
(file “./expect02.sh” line 3)
正确方式:
[root@newrain expect]# cat ./expect02.sh
#!/usr/bin/expect
set user 22
spawn echo $user
[root@newrain expect]# ./expect02.sh
spawn echo 22 ============================================================================

[root@newrain expect]# cat expect01.sh
#!/usr/bin/expect
spawn ssh root@192.168.62.146
expect {
“yes/no” { send “yes\r”;exp_continue }
“password:” { send “123\r” }
}
interact

interact的作用测试
执行测试是否免交互:
要是用/usr/bin/expect的shell执行
[root@newrain expect]#/usr/bin/expect expect01.sh
成功
擦除最后一行interact进行测试
[root@newrain expect]#/usr/bin/expect expect01.sh
进入之后立即退出 ============================================================================
\r的作用测试
[root@newrain expect]# cat expect01.sh
#!/usr/bin/expect
set user root
set pass 123
set ip 192.168.62.146
spawn ssh u s e r @ user@ user@ip
expect {
“yes/no” { send “ye”;exp_continue }
“password:” { send “pas” }
}
interact
[root@newrain expect]# ./expect01.sh
spawn ssh root@192.168.62.146
root@192.168.62.146’s password:

加上\r之后
[root@newrain expect]# ./expect01.sh
spawn ssh root@192.168.62.146
root@192.168.62.146’s password:
Permission denied, please try again.
root@192.168.62.146’s password: ============================================================================
设置变量的方式
[root@newrain expect]# cat expect01.sh #!/usr/bin/expect
set user root
set pass 123
set ip 192.168.62.146
spawn ssh u s e r @ user@ user@ip
expect {
“yes/no” { send “yes\r”;exp_continue }
“password:” { send “$pass\r” }
}
interact

设置位置参数的方式(拓展)
[root@newrain expect]# cat expect01.sh
#!/usr/bin/expect
set timeout 30
set user [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
set ip [ lindex $argv 2 ]
spawn ssh u s e r @ user@ user@ip

expect {
“yes/no” { send “yes”;exp_continue }
“password:” { send “$pass\r” }
}
interact
运行结果为:
[root@newrain expect]# ./expect01.sh root 123 192.168.62.146
spawn ssh root@192.168.62.146
root@192.168.62.146’s password:
Last login: Thu Aug 15 23:26:59 2019 from 192.168.62.136

十五、shell 编程-运维脚本实战

• 实战项目1: 实现网络配置
• 实战项目2: sed实现sshd配置
• 实战项目3: sed实现nginx配置
• 实战项目4: 关闭本机SELinux的功能(/etc/sysconfig/selinux)
• 实战项目5: zabbix_agentd.conf配置文件修改(暂时不做)
• 实战项目6: awk统计/etc/passwd各种shell数量
• 实战项目7: awk统计网站访问各种状态数量
• 实战项目8: awk统计访问的每个IP的数量
• 实战项目9: 统计Nginx日志中某一天的PV量(暂时不做)
• 实战项目10: 获取获得内存使用情况
• 实战项目11: 基于时间的备份脚本
• 实战项目12: Web日志访问量分析程序(PV、UV)
• 实战项目13: 编写系统初始化脚本
1)设置时区并把同步时间加入计划任务
2)禁用selinux
3)历史命令显示操作时间
4)创建ALL权限用户并禁止root远程登录
5)设置最大打开文件数
6)减少swap使用 (将此文件/proc/sys/vm/swappiness内容修改为0)
7)安装常用工具
• 实战项目14: LAMP终级部署(rpm包方式)
• 实战项目15: Linux系统状态收集及分析(内存使用量,硬盘使用量,cpu使用量等)

项目作为作业


  1. rc ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_c G

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值