- 1. Shell编程
- 1.6. 六. Shell数组
Shell编程
语言:自然语言,计算机语言(低级语言:0 1 汇编 C C++ 高级语言:java php python oc swift shell(bash))
编译型:C C++ java
解释型:php python shell(bash)
编程:让计算机根据自己定义的路线去执行命令
编译型:
1 | (1)只须编译一次就可以把源代码编译成机器语言,后面的执行无须重新编译,直接使用之前的编译结果就可以;因此其执行的效率比较高; |
解释型:
1 | (1)源代码不能直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行; |
一. Shell编程初识
Shell概览
Shell 能做什么?
1 | 1. 自动化批量系统初始化程序 (update,软件安装,时区设置,安全策略...) |
shell的定义
1 | Shell 是命令解释器 |
shell的分类和更改
1 | cat /etc/shells 查看系统支持那些shell |
其他
1、linux命令的分类:
内嵌命令 外部命令 别名 函数
1 | 别名: alias |
2、历史命令
history, 默认只记录1000条
清除历史命令: history -c
如要每行历史命令前面加上时间
1 | echo 'export HISTTIMEFORMAT="%F %T "' >> ~/.bashrc |
Shell的特性及特点
一、shell? 命令解释器
shell命令 cp ls date
Linux支持的shell
1 | cat /etc/shells |
二、GNU/bash shell特点
- 命令和文件自动补齐 - Tab
- 命令历史记忆功能 上下键、!number(命令的序号)、!$
- 快捷键
1 | Ctrl+a 切换到命令行开始(跟home一样,但是home在某些unix环境下无法使用) |
- 前后台作业控制 &、nohup、ctrl + C、ctrl + Z(停止),bg %1(将一个在后台暂停的命令,变成继续执行)、fg (将后台中的命令调至前台继续运行)%1、kill %3、screen
案例:bg fg tailf /var/log/messages ^Z sleep
输入输出重定向 0,1,2 > >> 2> 2>> 2>&1 &>
cat < /etc/hosts
cat <<EOF
cat >file1 <<EOF
管道 | tee
1 | ip addr |grep 'inet ' |grep eth0 |
- 命令排序
&& || ; 具备逻辑判断,连接命令用的
1 | ; 无论前面是否执行成功,分号后的命令都会继续执行 |
注意:
command & 后台执行
command &>/dev/null 混合重定向(标准输出1,错误输出2)
command1 && command2 命令排序,逻辑判断
shell通配符(元字符)表示的不是本意
基本元字符
* 匹配任意多个字符
ls in* rm -rf * rm -rf .pdf find / -iname “-eth0” yum -y install epel*
? 匹配任意一个字符
touch love loove live l7ve; ll l?ve
. 匹配任意一个字符
[] 匹配括号中任意一个字符
[abc] [a-z] [0-9] [a-zA-Z0-9]
() 在子shell中执行(cd /boot;ls)
(umask 077; touch file1000)
注意:如何查看当前子shell ps | grep $$ $$:当前shell的pid
{} 集合
touch file{1..9}
# mkdir /home/{111,222} mkdir -pv /home/{333/{aaa,bbb},444}
# cp -rv /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.old
# cp -rv /etc/sysconfig/network-scripts/{ifcfg-eth0,ifcfg-eth0.old}
# cp -rv /etc/sysconfig/network-scripts/ifcfg-eth0{,.old}
\ 转义符,让元字符回归本意
# echo *
# echo *
# touch xing\ dian
备注:
mysql % 所有字符 (like 模糊查询) _ 单个字符 (like 模糊查询)
- 计算器
计算符号:
加 | 减 | 乘 | 除 | 取余 | ** |
---|---|---|---|---|---|
+ | - | * | / | % | 次方 |
计算整数
# echo $[1+2] //计算整数1+2 ~~ echo $((1+2))
# expr 1 + 1 // + 号两边一定要有空格
计算小数 bc
# echo 1.5+1.5 | bc
脚本编写
创建bash脚本: 一般以.sh结尾的文件 .py结尾的文件是python的脚本
1. 创建脚本文件
#!/bin/bash #!/usr/bin/env bash
指定命令解释器:第一行的专门解释命令解释器
注释 :以#开头的都不生效
编写bash指令集合
2. 修改权限(改不改路径都可以执行)
1 | !/bin/bash |
执行shell脚本
直接执行,在命令行写脚本的路径 开启子shell执行,将执行的输出返回到父shell中,前提,脚本要有可执行权限
调用解释器执行
source 脚本的绝对路径,点执行 . 脚本的绝对路径 直接在当前shell下执行,会影响当前的shell环境
练习1:
编写脚本,一键部署青蛙吃苍蝇小游戏???
1 | 1. 安装httpd服务 |
练习2:
编写脚本,一键配置腾讯云的yum源,Base epel
1. 现存的源备份掉(删除)
2. 建立新的源!!
cat > /ect/yum.repos.d/tencent_base.repo <<EOF
....
EOF
3. yum repolist
bash脚本调试 [了解]
•sh –x script
这将执行该脚本并显示所有变量的值
•sh –n script
不执行脚本只是检查语法模式,将返回所有错误语法
•sh –v script
执行脚本前把脚本内容显示在屏幕上
变量☆
一、shell 变量
什么是shell变量?
变量的类型
变量的定义方式
变量的运算
变量”内容”的删除和替换[了解]
shell 变量?
用一个固定的字符串去表示不固定的内容
如何去赋值变量??
- 显式赋值
变量名=变量值
示例:
1 | ip1=192.168.1.251 |
注意事项:
1. = 两边不要有空格
2. 变量名不能以数字或符号开头,例如 1a @a
3. 变量名可以由大写+小写+数字+下划线组成例如 a1 A1 a_1 A_2
- read 从键盘读入变量值
read 变量名
1 | read -p "提示信息: " 变量名 |
二、变量的类型
1. 自定义变量
定义变量: 变量名=变量值 变量名必须以字母或下划线开头,区分大小写 ip1=192.168.2.115
引用变量: $变量名 或 ${变量名}
查看变量: echo $变量名 set(所有变量:包括自定义变量和环境变量)
取消变量: unset 变量名
作用范围: 仅在当前shell中有效
2. 环境变量
定义环境变量: 方法一 export back_dir2=/home/backup
方法二 export back_dir1 将自定义变量转换成环境变量
引用环境变量: $变量名 或 ${变量名}
查看环境变量: echo $变量名
取消环境变量: unset 变量名
变量作用范围: 在当前shell和子shell有效
环境变量拥有可继承性:export之后就拥有继承性
永久生效:写到四个登录脚本,/etc/profile ~/.baserc ~/.bash_profile /etc/bashrc source ~/.bashrc
举例:
vim /etc/profile
JAVA_HOME=/usr/local/java
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME PATH
vim ~/.bash_profile
:/usr/local/mycat/bin
=====================================================
如何设置环境变量
ssh登入或su 登入就会执行加载以下文件
1)/etc/profile
2)/etc/bashrc
3)/root/.bash_profile //配置roo用户的环境变量
4)~/.bashrc
5)/home/tom/.bashrc ///配置tom用户的环境变量
所有用户登录都会加载这四个配置文件
/etc下两个为全局环境配置文件,对所有用户生效
家目录下两个为用户自己的环境配置文件,只对用户本人生效
/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都会读此内容,所以通常都会将登陆后必须执行的命令放在这个文件中。
.profile
如果./bash_profile ~./bash_login两个文件都不存在,则会使用这个文件的设置内容,其实它的功能与/.bash_profile相同。
.bash_logout
如果想在关机前(退出登入前)执行一些工作,都可以在此文件中设置。
例如:
#vi ~.bash_logout
clear
仅执行一个clear命令在你注销的时候
~/.bash_history
这个文件会记录用户先前使用的历史命令。
注意:在/etc/profile.d建立独立的环境变量配置文件
常用环境变量: USER 当前登入的用户
UID 当前登入用户的UID
HOME 当前登入用户的家目录
HOSTNAME 当前登入的主机名
PWD 当前目录的绝对路径
PS1 当前的终端提示符的格式
PATH
PATH:这个变量存放的是所有命令所在的路径 修改:PATH=$PATH:+目录
意义:让所有命令在执行的时候不必输入路径
练习:
编写一个脚本,执行脚本会输出以下内容 //任何用户都可以执行
# msg
2024年 09月 10日 星期一 16:08:20 CST
当前登入的用户名:xx
当前登入的用户ID:xx
当前用户的家目录:xx
当前所在的目录: xx
3. 位置变量
$1 $2 $3 $4 $5 $6 $7 $8 $9 ${10}
4. 预定义变量
$0 脚本名
$* 所有的参数
$@ 所有的参数
$# 参数的个数
$$ 当前进程的PID
$! 上一个后台进程的PID
$? 上一个命令的返回值 0表示成功 ,1-254 表示不成功 【重点】
示例1:
1 | vim test.sh |
示例2:
1 | vim ping.sh |
示例3:
1 | vim first.sh |
示例4:
1 | vim ping2.sh |
定义或引用变量时注意事项:
“ “ 弱引用 可以实现变量和命令的替换
‘ ‘ 强引用 不完成变量替换
[root@qiangge ~]# school=1000phone
[root@qiangge ~]# echo “${school} is good”
1000phone is good
[root@qiangge ~]# echo ‘${school} is good’
${school} is good
命令替换 等价于 $() 反引号中的shell命令会被先执行
1 | [root@qiangge ~]# touch `date +%F`_file1.txt |
练习:
1、写一个计算年龄的脚本,用户输入出生年份,程序输出 今年多少 xx 岁?
5. 变量”内容”的删除和替换(扩展)
===”内容”的删除===
1 | [root@qiangge ~]# url=www.sina.com.cn |
===索引及切片===
1 | [root@qiangge ~]# echo ${url:0:5} |
===”内容”的替换===
1 | [root@qiangge ~]# url=www.sina.com.cn |
===变量的替代===
条件:
1. 如果没有该变量,就会被替换
2. 如果变量有值或者值为空,就不会被替换
1 | [root@qiangge ~]# unset var1 |
6. i++ 和 ++i (了解)
i++是自增运算,规则是先运算在let i++
1 | [root@qiangge ~]# echo $i自增,每次自增的步距为1 |
企业案例(作业)
1.编写一个shell脚本,用于搜集其执行主机的信息,打印结果如下:
当前时间为:
当前用户为:
当前用户的家目录为:
用户的标识为:
主机名称为:
mem的使用率:
当前主机IP地址:
cpu使用率:
磁盘剩余量:
二. Shell流程控制
主要知识点:
条件测试
if判断语句
case菜单
for循环语句
while循环
Shell条件测试
1 | 文件测试 |
Shell 条件测试
1 | 格式1: test 条件表达式 |
1.文件测试 [ 操作符 文件或目录 ]
案例:
1 | [root@localhost ~]# test -d /home |
[ -e dir或file ] //单独判断是否有文件 -e 后面可以接文件也可以接目录
[ -d dir ]
[ -f file ] 是否存在,而且是文件
[ -r file ] 当前用户对该文件是否有读权限
[ -x file ]
[ -w file ]
——————– 判断最好加上绝对路径—————
1 | [root@localhost ~]# [ ! -d /ccc ] && mkdir /ccc |
test命令或者返回0(真) 或者返回1(假).
test可理解的表达式类型分为四类:
1)判断表达式
if (表达式为真)
if ! 表达式为假
[ 表达式1 -a 表达式2 ] 或者 [ 表达式1 ] && [ 表达式2 ] //两个表达式都为真,满足两个条件
[ 表达式1 -o 表达式2 ] 或者 [ 表达式1 ] || [ 表达式2 ] //两个表达式有一个为真,满足其中一个条件即可
2)判断字符串(一般用于判断变量) 加双引号
test –n “字符串” 字符串的长度非零,则为真; 变量有值 (变量为空格也为真,定义变量为空则为假)
test –z “字符串” 字符串的长度为零,则为真;变量没有值
test “字符串1” == “字符串2” 字符串相等,则为真
test “字符串1” != “字符串2” 字符串不等,则为真
[[ “123” =~ ^[0-9]+$ ]] 字符串约等于,正则能匹配
3)判断整数
test 整数1 -eq 整数2 整数相等
test 整数1 -ge 整数2 整数1大于等于整数2
test 整数1 -gt 整数 2 整数1大于整数2
test 整数1 -le 整数 2 整数1小于等于整数2
test 整数1 -lt 整数 2 整数1小于整数2
test 整数1 -ne 整数 2 整数1不等于整数2
4)判断文件
test File1 –ef File2 两个文件具有同样的设备号和i结点号
test File1 –nt File2 文件1比文件2 新
test File1 –ot File2 文件1比文件2 旧
test –b File 文件存在并且是块设备文件
test –c File 文件存在并且是字符设备文件
test –d File 文件存在并且是目录
test –e File 文件存在 exist
test –f File 文件存在并且是普通文件
test –g File 文件存在并且是设置了组ID
test –G File 文件存在并且属于有效组ID
test –h File 文件存在并且是一个符号链接(同-L)
test –k File 文件存在并且设置了sticky位
test –b File 文件存在并且是块设备文件
test –L File 文件存在并且是一个符号链接(同-h)
test –o File 文件存在并且属于有效用户ID
test –p File 文件存在并且是一个命名管道
test –r File 文件存在并且可读
test –s File 文件存在并且是一个套接字
test –t FD 文件描述符是在一个终端打开的
test –u File 文件存在并且设置了它的suid位
test –w File 文件存在并且可写
test –x File 文件存在并且可执行
注意:在使用[]简写test时,左中括号后面的空格和右括号前面的空格是必需的,如果没有空格,Shell不可能辨别表达式何时开始何时结束.
2.数值比较 [ 整数1 操作符 整数2 ]
[ 1 -gt 10 ] 大于
[ 1 -lt 10 ] 小于
[ 1 -eq 10 ] 等于
[ 1 -ne 10 ] 不等于
[ 1 -ge 10 ] 大于等于
[ 1 -le 10 ] 小于等于
1 | [root@localhost ~]# disk_use=$(df -P |grep '/$' |awk '{print $5}' |awk -F% '{print $1}') |
C语言风格的数值比较(了解)
1 | [root@localhost ~]# ((1<2));echo $? |
3.字符串比较
提示:使用双引号
1 | [root@localhost ~]# [ "$USER" == "root" ];echo $? |
注意:
“”:弱引用,可以实现变量和命令的替换
1 | [root@localhost ~]# x=* |
‘’:强引用,不完成变量替换
1 | [root@localhost ~]# x=* |
扩展:
1 | [root@localhost ~]# var1=111 |
Shell条件结构-if判断
if判断的语法结构
注意:if和fi要成对出现,脚本里有几个if就得有几个fi
1)最简单的语法-单分支
1 | if 条件表达式或者命令 |
2)分支的if结构语法-两个分支
1 | if 条件表达式或命令 |
例子:
如果使用该脚本的用户是root,则执行以下操作,否则退出并提示使用root用户执行
判断文件/tmp/a.txt是否存在且为普通文件,若存在则提示文件已存在,否则创建文件a.txt并提示文件成功创建.
1 |
3)多分支if结构语法结构
1 | if 条件表达式或者命令 |
多重条件判断
条件表达式1 选项 条件表达式2
选项:
1 | -a:并且 |
练习:
1、提示输入一个1-100以内的数字,
1.当你输入的是81~100,提示优秀
2.当你输入的是60~80,提示良好
3.当你输入的是60以下,不含60,提示真菜
思考: 4.如果输入的数字不在1~100范围以内,退出脚本,并且给他一jio
5.要求输入的数字一定只能是正整数,如果不是则进行提示
1 |
随堂作业
1.提示用户输入想创建的用户名,判断若该用户名存在,则提示用户已存在,否则创建用户并提示用户成功创建.
1 |
2.改写练习1,先判断执行脚本的用户是不是root,如果是就执行练习1的操作;否则就提示不是root并退出。
1 |
3.写一个脚本,从标准输入读取一个IP地址,如果用户输入的IP不为空就测试该主机是否存活,活着输出“alive”,否则输出“not alive” ,如果为空,直接退出脚本
1 |
case匹配模式
模式匹配:case
========================================================
case:多分支判断
语法结构:
1 | case 引用变量 in |
同时匹配两个变量的方法一:将两个变量看成一个整体
1 | [root@srv254-200 scripts]# vim case2.sh |
测试
1 | [root@srv254-200 scripts]# sh case2.sh |
同时匹配两个变量的方法二:用逗号分隔多个变量
1 | [root@srv254-200 scripts]# vim case3.sh |
========================================================
案例1:简单的模式匹配
确定要继续删除吗 yes/no: “ yes
1 |
案例2:系统管理工具箱
执行结果
1 | [root@srv254-200 scripts]# bash tool.sh |
思考额外条件?
如果使用其中一个工具,不能退出,按Q/q 才能退出该脚本
Shell循环结构-for
Shell循环:for 循环次数是固定的
语法结构
1 | for 变量 in 变量的列表 |
在命令行直接运行for命令
1 | for 变量 in 变量列表; do 命令块; done |
变量列表决定循环次数
注意:如果是以文件行数为单位进行循环,行内有空格会影响循环范围
IFS=$’\n’ //约束,只能以行为单位进行循环
1 | for i in 1 2 3 4 5 |
练习1:
打印1~100之间的偶数
假设打印 1~100的奇数呢?
1 |
练习2:
计算1~100的和
1 |
-——————————————
C写法:
1 | for ((i=1;i<=10;i++)) |
==================================================
插曲 —— seq
1 | seq 1 2 10 //打印奇数 起始位置 步长 结束位置 |
随堂练习
用for循环,打印/etc/passwd的第一个字段
如果要彻底删除uid大于1000的用户
1 |
- 批量添加10个用户user01-user10,并设置密码,密码分别为pass01-pass10,
1 |
2.1 如果不创建user05用户那该怎么做?
1 |
- 用for循环在/haowan目录下批量创建10个文件,文件名为up-1,up-2,…,up-10
1 |
- 用for循环将第三题文件名中的up全部改成down
1 |
- 求1到100所有正整数之和
1 |
- 求1到100所有偶数之和
1 |
- 打印9*9乘法表
1 |
- 输出随机的10个手机号码
手机号: 1xxxxxxxxxx
1 |
Shell循环结构-while
while语法: 无限循环 (也可以有限循环)
1 | while 条件表达式或命令 |
在命令行直接运行while命令
1 | i=1 ; while [ $i -le 10 ]; do echo $i; let i++; done |
案例
1 | [root@srv254-200 scripts]# cat while.sh |
-———————————–
i++:先用,后加1
++i:先加1,后用
-———————————–
扩展:
添加用户脚本,要求用户的信息来自于文本文件
1 | [root@srv254-200 scripts]# cat user_list |
1 | [root@srv254-200 scripts]# cat while1.sh |
随堂练习
- 使用while循环,添加test1-test10,分别设置密码为pass1-pass10,不添加test5
1 |
- 使用while循环,将上题中的9个用户删除干净,删除成功给出如下提示“user xxx delete successfully.”
1 |
- 使用while循环,机器里有test1~10个用户,其中有一个用户已经被删除,请找出该用户,并且删除其他用户?
1 |
- 使用while循环计算1+2+3+…+100的和?
1 |
- 现在有一个文件,内容如下
1 |
要求进行发送邮件给tom jerry jack 三个用户,标题分别为hello abc 123,内容接着来
1 |
echo “邮件内容” | mail -s 标题 收件人
循环控制
break 跳出整个循环
sleep 10 //等待10秒,再继续下一操作
continue 跳出当前这一次循环
exit 直接退出,直接退出整个脚本
{ 命令1
命令2
}& 把多个命令同时放到后台执行
使用wait等待所有子任务结束
1 | !/bin/bash |
三. printf格式化打印
printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。使用 printf 的脚本比使用 echo 移植性好。
语法格式:printf format-string [arguments…]
- format-string: 为格式控制字符串
- arguments: 为参数列表。
命令案例:
1 | echo "Hello, Shell" |
脚本案例:
1 | vim printf.sh |
%s %d %f //都是格式替代符
字符串 整数 小数
%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4.2f 指格式化为小数,其中.2指保留2位小数。
click me | click me |
---|---|
\f | 换页(formfeed) |
\n | 换行 |
\r | 回车(Carriage return) |
字符串:%s
小数:%f
数字:%d
-——————————————————————
练习:
现在有一个名单,name.txt
内容如下
1 | tom:男:牛 |
按要求修改其格式写入到name.txt.bak内并进行输出,格式如下
# cat name.txt.bak
1 | 姓名 性别 生肖 |
案例:
1 |
四. echo打印颜色
shell脚本中echo显示内容带颜色显示,需要使用到-e参数
格式1:echo -e “\033[背景颜色;字体颜色m 要输出的字符 \033[0m”
格式2:echo -e “\e[背景颜色;字体颜色m 要输出的字符 \e[0m”
1 | echo -e "\033[30m 黑色字 \033[0m" |
实现上下键选择菜单执行指定功能
1 | !/bin/bash |
五. Shell函数
函数function
1 | 定义函数 |
函数变量的适用范围:
1 | 默认,函数里的变量会在函数外面生效 |
myfunc() //函数定义
{
echo “This is my first shell function”
}
myfunc //函数调用
unset myfunc //取消函数
myfunc(){
echo “This is a new function“
}
myfunc
例:
1 | lsl(){ |
-—————————-
return
函数返回值 // 提前先定义好 $? 的返回值
使用$1,$2传参
1 | !/bin/bash |
获取函数参数的个数
在函数内部,用户也是可以通过位置变量来接受函数的值
案例:
1.如何通过$#来获取函数参数的个数
1 | !/bin/bash |
注意:函数在使用位置变量的过程中,使用空格隔开,如果参数中有空格就用引号印起来。
- 如何通过 $1 $2 来获取函数的位置变量的值
随堂练习
编写系统管理工具箱
- 查看内存的使用率 查看磁盘的使用率和剩余容量 查看系统的负载 只能按q键退出程序
1 |
- 编写函数,实现打印绿色字体OK和红色字体FAILED 判断是否有位置参数$#,存在为Ok,不存在为FAILED
1 |
六. Shell数组
数组(array):
把多个元素按一定顺序排列的集合,就是把有限个元素用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为下标[index]。
数组是可以保存一组值的变量
索引数组
索引数组—–只支持正整数下标
1. 定义数组
1 | nums=(2 5 8) #下标从0开始,依次递增 |
2. 数组的调用 ${name[index]} #name数组的名称,index下标值
1 | echo ${nums[1]} |
3. 查看数组的下标
1 | echo ${!nums[*]} #按顺序列出所有值的下标 |
4. 数组的遍历 //遍历: 把所有的元素打印出来
1).遍历值
1 | echo ${数组[*]} |
2).遍历下标
1 | echo ${!数组[*]} |
5. 数组的长度 #元素的个数
1 | echo ${#nums[@]} |
6. 取消数组
1 | unset nums[100] #取消数组中的一个元素 |
关联数组
关联数组—–支持字符串下标
1 | declare -A tom #定义一个关联数组 |
数组扩展
案例1:for循环遍历数组
1 | !/bin/bash |
案例2: for创建数组
1 | IFS=$'\n' |
1 | for i in ${!hosts[*]} //遍历 ; 以数组的下标为循环列表 |
案例3:通过while 赋值
1 | while read line |
定义换行符,定义某一段
OLD_IFS=$IFS
实战案例
现有一个成绩名单,内容如下
1 | # cat name.txt |
请编写脚本进行输出;男女个数分别是多少?
及格与不及格人数分别是多少?
打印出成绩最好的是?
1 | !/usr/bin/bash |
随堂练习
1、编写脚本,生成100组 以1开头的电话号码
//要求必须使用数组
1 |
2、 编写脚本,输入出生年份,程序告知生肖
1 |
3、编写一个双色球程序
红球 33个 01~33
篮球 16个 01~16
条件 1. 红球不能重复,并且不能取到空球
# bash ssq.sh
01 08 33 30 15 17 + 08
1 |
七. Shell正则表达式
正则表达式 (Regular Expression)
=======================================================
正则表达式是一种字符特征的描述方式,用来在文本中匹配用户想要的内容。
正则表达式与通配符
正则表达式一般用于处理输入数据,常用命令有grep、sed、awk、vim等
通配符一般用于匹配文件名,常用命令有find、ls、cp、rm等
什么地方使用正则表达式
vim grep sed awk nginx apache mail perl java python 等等都使用正则
构成
- 元字符(基本元字符、扩展元字符):拥有特殊含义
- 普通字符:仅表示他字面意思
正则表达式的匹配过程
正则表达式是按照从最左端第一个字符开始,左到右依次一个一个字符进行匹配.
特征:贪婪
基本元字符与扩展元字符
基本元字符grep、sed、awk都支持
扩展元符:
egrep 或 grep -E
sed -r
awk
正则表达式元字符
- 以下没有特殊注明的均为基本元字符
字符匹配
. 任意单个字符
[] []内的任意单个字符
1 | 转义符 正则表达式 |
以上转义字符不支持三剑客,但可以在其他地方使用(了解)
次数匹配 - 不能单独存在
1 | * # 前面的字符重复 0 次到多次 |
位置匹配
1 | ^ # 行首 |
其他元字符
1 | 注: 以下为扩展正则表达式 |
编写正则表达式的步骤
- 知道要匹配的内容以及它如何出现在文本中
- 编写正则表达式
- 测试它匹配的内容,不能出现错匹配,漏匹配或多匹配
匹配 ipv4 地址,手机号码,时间
1 | ip地址:1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[1-9][0-9]?)\.((1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[0-9][0-9]?)\.){2}(1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[1-9][0-9]?) |
POSIX字符类(扩展)
点击这里 | 点击这里 |
---|---|
[:digit:] | 任何数字 [0-9] |
[:xdigit:] | 任何十六进制数字 [0-9a-fA-F] |
[:alpha:] | 任何字母 [a-zA-Z] |
[:lower:] | 任何小写字母 [a-z] |
[:upper:] | 任何大写字母 [A-Z] |
[:alnum:] | 任何字母或数字 [a-zA-Z0-9] |
[:cntrl:] | ASCII控制字符(ASCII 0~31 和 ASCII127) |
[:punct:] | 不属于[:alnum:]和[:cntrl:]的任何字符 |
[:blank:] | 空格或制表符([\t ]) |
[:space:] | 任何空白字符,包括空格([\f\n\r\t\v ]) |
[:print:] | 任何可打印字符 |
[:graph:] | 同[:print:],但不包括空格 |
随堂作业
练习:head /etc/passwd > /opt/pass
1 | sed |
八. 文本处理三剑客
sed
文本的操作
sed是一个“非交互式的”面向字符流的编辑器。
awk是一种负责模式匹配的程序设计语言,的典型示例是将数据转换成格式化的报表。
sed stream editor
是一种文本编辑器,默认情况下是不会修改原文件的。
也是一种非交互式的编辑器
工作原理
一行一行处理的
当从文件中读取一行后,首先放到模式空间中对该行进行相应的处理,处理完将结果输出到屏幕上。然后继续读取下一行内容,直到所有行都读取完毕,sed结束。
语法:
1 | sed [选项] '模式 动作' 文件... |
选项:
1 | -n:静默输出,关闭模式空间的输出,不会输出未匹配到的行 一般与p命令结合使用(只显示处理过的行) |
模式(处理具体哪些行):
空模式,表示所有的行都执行动作
以行号作为模式
1 | 1). 单独的行号 |
- 以正则作为模式
1 | 1). /正则表达式/ |
动作—–处理命令:
! 非
: 放在命令(动作)前面表示取反 (以行为单位)
1 | 1. d 删除 delete |
11. s 查找替换
‘模式 s/旧的内容(正则表达式)/替换内容(新的内容)/[修饰符]’
修饰符:
g:全局替换
n:n为数字,1-512 替换第n个匹配到的内容
p:打印 -n
w:把处理过的行写入到另一个文件
-———————————————————————-
\u upper 大写字母
\l lower 小写字母
将文件中所有小写字母替换成大写字母:
1 | sed 's/[a-z]/\u&/g' /tmp/pass |
将文件中所有大写字母替换成小写字母
1 | sed 's/[A-Z]/\l&/g' /tmp/pass |
随堂练习
- 从以root开头的行,到以login结尾的行,将sbin替换为bin (/etc/passwd文件或其一部分)
1 |
- 将格式为2024/09/17的日期,替换为2024; 09; 17这样的格式(注意分号后面有空格)
1 |
- 将pass文件每行打印3次
1 |
- 只打印pass文件的第1行和第3行
1 |
- 删除pass文件的第一行和最后一行
1 |
- 删除pass文件中所有的数字
1 |
- 将文件中所有的root单词(仅root,不操作rooter这样的单词)替换为ROOT
1 |
- 在文件的第一行插入information
1 |
- 将含有root的整行的内容替换为ROOT
1 |
- 将文件中所有的a或b或c替换为大写字母
1 |
- 用sed分别实现head -1和tail -1的功能
1 |
模式空间和保留空间(扩展)
模式空间:
用于处理文本行,最大保存8192字节
保留空间:
用于保留文本行,最大保存8192字节,默认有一个空行
置换命令
h:将模式空间的内容复制到保留空间 —— 覆盖模式
H:将模式空间的内容追加到保留空间 —— 追加模式
g:将保留空间的内容复制到模式空间 —— 覆盖模式
G:将保留空间的内容追加到模式空间 —— 追加模式
x:将模式空间和保留空间中的内容进行交换
案例:
1.在每行下面添加一个空行
1 |
2.将第一行移动到最后一行
1 |
3.移动第一行到第二行下面
1 |
4.将第一行复制到每个偶数行下面
1 |
- 移动第一行到第三行下面
1 |
6.用sed实现tac
1 |
扩展:
控制流
! 命令取反 例: 1!d 删除第一行以外的行
{} 命令组合 命令用分号分隔 {1h;G} 可以理解为 -e 参数的另一种写法
= 打印行号(输入行的号码,而非处理的次数行号) 例如: sed -n ‘2{=;p}’ infile
n 读入下一行到模式空间 例:’4{n;d}’ 删除第5行
N 追加下一行到模式空间.
P 输出多行模式空间的第一部分,直到第一个嵌入的换行符为止。在执行完脚本的最后一个命令之后,模式空间的内容自动输出。P命令经常出现在N命令之后和D命令之前。
D 删除模式空间中直到第一个换行符的内容。它不会导致读入新的输入行,相反,它返回到脚本的顶端,将这些指令应用与模式空间剩余的内容。
这三个命令能建立一个输入. 输出循环,用来维护两行模式空间,但是一次只输出一行。
这个循环的目的是只输出模式空间的第一行,然后返回到脚本的顶端将所有的命令应用于模式空间的第二行。没有这个循环,当执行脚本中的最后一个命令时,模式空间中的这两行都将被输出。
删除文件倒数第二行
# sed ‘N;$!P;D’ a.txt
删除文件最后两行
# sed ‘N;$!P;$!D;$d’ a.txt
awk
awk 文本编辑器. 也是一种非交互式的编辑器
一种编程语言
功能:
对文本数据进行汇总和处理,是一个报告生成器,能够对数据进行排版printf
工作模式
行工作模式,读入一行文件,存在“$0”里
使用内置变量FS(字段分隔符)分割一行,存到$1-$100
输出时也是用内置变量OFS,输出该行
与sed主要异同:
相同点:
1.他们的语法基本相同
2.他们都是流编辑器,工作方式都是读入一行,处理动作,输出结果
3.他们都可以使用正则表达式作模式匹配
4.他们都允许使用脚本
不同点:
1.sed主要处理行,awk主要处理列
2.sed处理动作必须与文件内容相关,awk处理动作可以与文件内容无关,并且awk的动作要用{}括起来.
1. awk的语法
1 | awk [选项] '模式{动作...}' [文件列表]... |
选项
1 | -F 指定分隔符 //默认是用空格为分隔符 |
2. 简单使用
//匹配含有root的行,并以 : 号为分隔符,打印 1 3 4 列
1 | awk -F: '/root/{print $1,$3,$4}' /tmp/pass |
3. awk的模式匹配
1)空模式:也就是每一行都处理的模式
2)以行为单位
1 | a. 匹配行 NR==5 -> 第5行 |
3)正则表达式
注意:awk****是不支持使用行号定址的
1 | /^root/ -> 匹配以 root开头的行 |
4. 基本操作
1)设定输入分隔符 -F
分隔符可以是字母. 数字. 符号和空白
可以同时指定多个分隔符
默认是空白
——单个字符做分隔符
——复合分隔符,多个字符组成的分隔符 比如: :/
——同时指定多个分隔符 如 (空格)和/都是分隔符 # df -h |awk -F “ +|%” ‘//$/{print $5}’
——每个字符都是一个字段 即以空为分隔符 # echo hello |awk -F “” ‘{print $1,$2}’
2)awk输出:
(1) print
打印内容可以是文件中内容. 也可以和文件无关
例:
1》打印passwd文件中的用户名及其shell
1 | awk -F ":" '{print $1,$7}' /etc/passwd |
2》对1》中的输出进行格式化,要求输出结果:用户的shell是shell名
root用户的shell是/bin/bash
bin用户的shell是/sbin/nologin
1 | awk -F ":" '{print $1" 用户的shell是 "$7}' /etc/passwd |
print要点:
a. 各个输出字段之间用逗号分隔,而输出时默认以空格分隔
b. print后面如果不指定字段,那么就会打印一整行 =~ print $0
c. print输出时默认是有换行符的
练习:
- 在每行下面打印一个空行
1 | awk '{print $0"\n"}' /opt/pass |
- ifconfig和awk结合,取出IP地址
1 | ifconfig ens33 | awk '/netmask/{print $2}' |
- df和awk结合,取出根分区已用空间的百分比数值(去掉%)
1 | df -Th | awk '/\/$/{print $6}' | awk -F"%" '{print $1}' |
(2) printf
—— 可以格式化输出,默认没有换行
使用格式:
printf “format”,item1,item2,… …,itemn
format的指示符都是以%开头的,后面跟一个字符表示不同的数据类型,如:
%s:表示是字符串
%d:表示十进制整数
%f:表示浮点数,也就是小数
%%:表示%本身
%x:表示十六进制数
%o:表示八进制数
%c :表示字符
修饰符: N(数字): 表示显示宽度 %5d
-:左对齐,默认是右对齐 %-5s
printf要点:
a. 与print不同的是,printf需要指定格式
b. 格式(format)是用来指定后面的每个item的输出格式
c. printf默认不会自动打印换行符,需要时候手动添加”\n”
d. printf默认没有输出分隔符
例:
格式化输出passwd文件的用户名,UID,GID三列
1 | awk -F ":" '{printf "%-20s %-5s %s\n",$1,$3,$4}' /etc/passwd |
3)awk的操作符
(1)算数运算符
-x:表示负数 +x 或 (x):表示正数
x+y x-y x*y x/y x%y x**y x^y (x的y次幂)
(2)关系运算符
a. 数值之间的关系运算符
> < >= <= == !=
b. 字符串之间的关系运算符
== != ~ !~
$1==”root”
$1!=”root”
x ~ /y/ —— 匹配正则
x !~ /y/ —— 不匹配正则
随堂练习
- 打印/etc/fstab中含有boot的行
1 |
- 打印/etc/passwd文件中UID为14的用户的用户名. uid以及家目录
1 |
- 打印用户的shell为可登录shell的用户名及shell
1 |
(3)逻辑运算符
&& NR >= 4 && NR <=8
|| NR == 4 || NR ==8
!
(4)赋值运算符(详见变量)
= += -= *= /= %= **= ^=
++ –
练习: 以 /etc/passwd 例子
- 打印uid在59到99之间的用户名和uid(包含59,但不包含99)
1 |
- 打印uid和gid不相同的用户的用户名,uid及gid
1 |
- 交换passwd文件的第一个字段和第二个字段(以冒号为分隔)
1 |
- 打印100以内能够被7整除以及包含7的数: 如7,14,17…
1 |
5. awk的变量
1)内置变量
(1) $0 表示一整行的内容
(2) $1~$100
$1:第一列
$2:第二列
… …
$100:第100列
(3) 与记录相关的变量
FS(Field Separator):字段分隔符,默认是空白 默认 空格
RS(Record Separator):记录分隔符,即行的分隔符 默认 \n
OFS(Output):输出字段分隔符 (列的分隔符,默认是空格)
ORS:输出记录分隔符 (行的分隔符)
(4) 与数据相关的变量
NR:记录数,awk所处理的记录的总数 NR在很多情况下可以看成行号
FNR:当前文件所处理的记录数
NF:当前行的字段数 ,列 ,
$NF:当前行最后一个字段的值
$(NF-1):当前行倒数第二个字段的值
2)自定义变量
命名:由字母. 数字. 下划线组成,不能以数字开头,不要使用关键字,最好见名知意
变量名区分大小写,变量可以先定义再使用,也可以直接使用(整型)
给变量赋值:
变量名=数值
变量名=”字符串” 注意:此处一定有引号
取消变量:
delete var_name
变量的长度:
length(var_name)
6. 赋值运算
a+=$2 等效于 a=a+$2 //先运算在赋值
a+=5 等效于 a=a+5
a++ 先赋值,再自增
++a 先自增,再赋值
7. awk的特殊模式
awk [选项] ‘BEGIN{动作}模式{动作}END{动作}’ 文件列表
BEGIN{} :在读取文件之前就执行,并且只执行一次
模式{}:这一部分可能会执行多次
END{}:在处理完文本之后执行,并且只执行一次
BEGIN:一般用于初始化分隔符. 定义变量. 定义数组. 打印表头等
END:汇总数据 比如:打印总成绩. 打印平均成绩等等
说明:
BEGIN和END可以单独存在
1)只有BEGIN,后面不需要加文件
2)只有END 后面必须接文件,文件可以是空文件,也可以是有内容的文件
8. 扩展: awk 内嵌 if for
基本的If语句:if (condition 判断表达式) { # 条件满足时执行的代码 } //单分支
例如1
1 | awk -F: ‘{if($1=="root"){print $1,$3}}’ /etc/passwd |
例如2
1 | awk -F: ‘BEGIN{i=0} {if($3>=1000){i++}}END{print i}’ /etc/passwd |
if (condition) { # 条件满足时执行的代码 } else { # 条件不满足时执行的代码 } //两分支
例如1
1 | awk -F: ‘{if($3>=1000){i++}else{j++}} END{print i,j}' /etc/passwd |
基于数字范围的For循环:for (i = start; i <= end; i++) { # 循环执行的代码 }
例如1
1 | awk 'BEGIN{for(i=0;i<=5;i++){print i}}' |
例如2
1 | # awk 'BEGIN{sum=0;for(i=1;i<=100;i++){sum=sum+i};print sum} |
9. 扩展:awk数组
数组:array
awk数组为关联数组,即可以使用字符串作下标.
常用于收集信息, 计算次数等
数组的定义:
1 | 数组名[下标]=值 |
数组长度:
1 | length(数组名) //统计数组有多少个元素 |
数组的取消:
1 | delete 数组名 # 取消整个数组 |
数组的遍历: 使用for循环来遍历数组
1 | 语法:for (变量 in 数组名){ print 数组名[变量]} |
统计apache日志中每个IP的访问次数,取出前五名
练习:
1. 统计系统中每种shell的用户有几个?
2. 统计服务器上tcp连接情况(即每种状态有多少个?) netstat -tan
3. 使用awk实现tac的功能
扩展:
- 有两个文件,内容如下:
1 | cat a.txt |
使用awk处理这两个文件,输入效果如下:
1 | lily 18 female |
1 |
随堂练习
- 打印passwd文件的奇数行的行号和内容
1 |
- 打印每行的倒数第3个字段
1 |
- 从第一行开始,每隔三行打印一次(即打印1 4 7这样的行)
1 |
- 统计UID和GID不相等的行数(不要用wc命令)
1 |
- 统计/etc目录下所有普通文件的总大小(M)
1 |
grep
grep家族
========================================================
grep: 在文件中全局查找指定的正则表达式,并打印所有包含该表达式的行
egrep: 扩展的egrep,支持更多的正则表达式元字符 // grep -E
九. expect 免交互
expect 把交互式操作变成非交互式
如何执行expect脚本
expect 脚本名称
chmod a+x 脚本名称
./脚本名称
1 | vim su.sh |
非交互生成密钥对及传递公钥
案例脚本
1 | !/bin/bash |
生成密钥对,单分支

生成密钥对,多分支

传递公钥
完整shell脚本
准备ip 用户名 密码文件
1 | cat ip.txt |
1 | !/bin/bash |