Shell脚本 - 高阶

Shell高级

正则表达式

  • 上一章说过通配符的功能,结合系统命令进行模糊查询,跟find结合用来查询指定名称的文件名
  • 而正则表达式是在文件中匹配符合条件的字符串,常跟grep命令结合进行精确匹配
  • 扩展正则是对基础正则的补充,结合grep时需要用-E选项,或直接用egrep命令
基础正则表达式
元字符 作用
. 匹配除换行符以外的任意一个字符
* 前一个字符匹配0或任意多次
^ 匹配行首
$ 匹配行尾
[] 匹配中括号中指定的任意一个字符
[^] 匹配除中括号的字符以外的任意一个字符
\ 转义符,用于取消特殊符号的含义
扩展正则表达式
扩展元字符 作用
+ 匹配除换行符以外的任意一个字符
前一个字符匹配0次或1次
{n} 表示其前面一个字符恰好出现n次
{n,} 表示其前面一个字符出现不小于n次
{n,m} 表示前面的一个字符至少出现n次,最多出现m次
{,m} 表示前面的一个字符最多出现m次
| 匹配两个或多个分支选择
() 匹配()内的整体,可以理解为由多个单个字符组成的大字符

准备测试数据

#grep命令复习
功能:在指定文件中过滤包含指定字符串的行
格式:grep  选项  ”关键词“  文件名
选项:
    -A 数字 #列出符合条件的行,并连续列出后续n行。
    -B 数字 #列出符合条件的行,并连续列出前面n行。
    -c #统计符合条件的字符串行数
    -i #忽略大小写
    -n #输出行号
    -v #反向查找(取反)
    -o #只列出关键字
    -w #强制匹配和字符串一样的
    -E #使用扩展正则表达式
    
#grep结合正则表达式
#准备测试数据
$ vim test.txt
ni
hao
ma?
123
abc123
aBc123
aBC123
123abc

a
bb
ab
abb
abbb
abcde
ccc
bbbddd

ni hao ma?
ni hen hao
ni bu hao
bu bu hao
index.php
index2php

字符匹配

🎈1,匹配具体的字符
grep "abc" test.txt
🎈2,匹配在或不在某几个字符中的一个,[]
#匹配有a或s或d的字符
grep "[asd]" test.txt
#匹配有除a或s或d之外的字符
grep "[^asd]" test.txt
区别于:
grep -v [asd] test.txt
#[]含义是匹配中括号中任意一个字符,注意只能匹配一个字符
#[^]含义是匹配不含有中括号里任意一个字符的行,注意也是一个一个字符匹配
[0-9]  [a-z]  [A-Z] [a-Z]或[a-zA-Z] 类似这样的都可以,指定匹配的范围

🎈3,匹配任意一个字符
#匹配长度是3位的字符串,h开头、o结尾
grep "h.o" test.txt

位置匹配

^ 匹配行首
$ 匹配行尾

#匹配以b开头的行
egrep "^b" test.txt
#匹配不是以小写字母开头的行
grep "^[^a-z]" test.txt
#匹配以o结尾的行
grep "o$" test.txt
#匹配以.php结尾的行
grep "\.php" test.txt
#匹配以a开头、以3结尾的行
grep "^a.*3$" test.txt
#匹配空白行
grep "^$" test.txt
#匹配数字行
grep "^[0-9]+$" test.txt

次数匹配

所有匹配次数的都是针对它前一个字符而言的
#*是匹配前一个字符出现任意多次(0次或任意多次)
grep "a*" test.txt
#则a*代表过滤a出现任意次的,即零次或任意次数,即把所有行拿出来
grep "a*" test.txt | wc -l  
wc -l test.txt  
=>
* 表示前面字符出现0次或者多次------ 
? 表示前面字符出现0次或者1次
+ 表示前面字符出现1次或任意次
{n} 表示前面的字符恰好出现n次
{n,} 表示其前面的字符出现不小于n次
{n,m} 匹配其前面的字符出现不小于n次,最多出现m次


#扩展正则生效的写法
grep "b\?" test.txt
或:
grep -E "b?" test.txt
或:
egrep "b?" test.txt

#匹配合适的行
[root@localhost ~]# egrep "ab?" test.txt
[root@localhost ~]# egrep "ab*" test.txt
[root@localhost ~]# egrep "ab+" test.txt


#{n} 表示前面的字符恰好出现n次
[root@localhost ~]# egrep "b{2}" test.txt
bb
abb
abbb    --看红色匹配到的部分
bbbddd  --看红色匹配到的部分
#{n,} 表示其前面的字符出现不小于n次
[root@localhost ~]# egrep "b{2,}" test.txt
bb
abb
abbb    --看红色匹配到的部分
bbbddd  --看红色匹配到的部分
#{n,m} 匹配其前面的字符出现不小于n次,最多出现m次
[root@localhost ~]# egrep "b{1,2}" test.txt
abc123
123abc
bb
ab
abb
abbb
abcde
bbbddd   --看红色匹配到的部分
ni bu hao
bu bu hao
[root@localhost ~]# egrep "^b{1,2}" test.txt
bb
bbbddd   --看红色匹配到的部分
bu bu hao

分支和整体匹配

#分支匹配
#同时匹配ni hen hao和ni bu hao
[root@localhost ~]# egrep "ni hen|bu" test.txt
ni hen hao
ni bu hao
bu bu hao

[root@localhost ~]# egrep "ni hen|ni bu" test.txt
ni hen hao
ni bu hao
或:
[root@localhost ~]# egrep "ni (hen|bu)" test.txt
ni hen hao
ni bu hao

#可以匹配一个整体的个数,类似匹配字符个数
[root@localhost ~]# egrep "(bu)+" test.txt
ni bu hao
bu bu hao

正则表达式练习

  • 写脚本:

    • 输入用户名、密码
    • 验证用户名格式:由4-8位的数字、字母、下划线组成,且数字不在开头
    • 判用户是root、密码是123,打印绿色登录成功、否则打印红色登录失败
  • 匹配ip地址:

    • 在ip addr中匹配正在使用的网卡的ip
  • 匹配电话号码:

    • 以区号025开头
    • 号码是5或8开头的八位数
    • 区号和号码间可以是空格、-或没有
#测试数据
$ cat telephones.txt
02588888888
025-5555555555
025 12345678
025 54321678
025ABC88888
025-85432109
028-85643210
0251-52765421

#可以的正则表达式:
$ egrep "^(025)[ -]?[58][0-9]{7}$" telephones.txt
------
#手机号匹配需求:
长度11位,
第一位是1,
第二三位是:130、131、132、145、155、156、185、186,
剩余位:不限制
  • 匹配邮箱

    • 邮箱格式:用户名@二级域名.顶级域名
    • 用户名:字符长度在5-17位,是除了@和空格以外的任意字符,开头只能是字母或者_
    • 二级域名:长度不限,符号为数字、小写字母、中横线-(不能连续、不在行尾)
    • 顶级域:域名后缀范围在.com、.cn、.com.cn中
#匹配邮箱:
$ cat mail.txt
zhangsan123@qq.com
li si@163.com
wang@wu@sina.com
zhao liu@126.com
qianqi@sina.com.cn
tester_ni@-sina.com.cn
wangwu@sina.2com.cn
_user1@@jd-1.com
#可以的正则表达式:
$ egrep "^[a-zA-Z_][^@ ]{4,16}@(-?[0-9a-z])+(\.com|\.cn|\.com\.cn)$" mail.txt
或:
$ egrep "^[a-zA-Z_][^@ ]{4,16}@(-[0-9a-z]|[0-9a-z])+(\.com|\.cn|\.com\.cn)$" 
mail.txt

字符截取和替换命令

cut命令

功能:用于从文件或标准输入中提取指定字段或列
语法:cut [选项] 文件名
选项:

-f   列号:提取第几列,默认识别制表符分割出来的列
-d   分隔符:按照指定的分割符进行分割,然后结合-f提取指定列
-c   字符范围:不依赖分割符来分割,而是通过字符范围进行提取
        n-m     表示从第n提取到第m个字符
        n-      表示从第n个字符开始提取到结尾
        -m     表示从第一个字符提取到第m个
#准备测试数据:添加以下内容,列之间用制表符分割(tab)
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63

#-f选项 过滤指定列的内容(多列用逗号分割)
$ cut -f 2,5 cut.txt
NAME    DOCKER
xzhao   78
xqian   93
xsun    63

#cut只能分割用制表符分开列的文件,若列是用其他符号分割的,需要用-d选项指定
#通过指定分割符的方式来确定如何进行分割,分开后就有列,截取第一个和第七个列
$ cut -d ":" -f 1,7 /etc/passwd
root:/bin/bash
bin:/sbin/nologin
daemon:/sbin/nologin
┊

#获取当前所在路径的最后一个层级目录
$ cd /home/jimmy
$ pwd | cut -d/ -f3   

#-c 可以按照字符数量进行截取,按照对应格式可以取到对应位置的字符
#截取多个范围的字符时用,隔开
格式:[n-m]、[n-]、[-m]
$ head -5 anaconda-ks.cfg
# Generated by Anaconda 34.25.4.9
# Generated by pykickstart v3.32
# version=RHEL9
# Use graphical install
graphical

$ head -5 anaconda-ks.cfg | cut -c 1-5
# Gen
# Gen
#vers
# Use
graph

$ head -5 anaconda-ks.cfg | cut -c 1-5,10-15
# Gened by 
# Gened by 
#versRHEL9
# Usephical
graph

------------------------
#练习:使用cut将磁盘的使用率截取出来
$ df -h | cut -d"%" -f1 | cut -c 1-20,40-43
文件系统        容量  已用  # 中文字符和英文字符长度不一致
devtmpfs        4.0M
tmpfs           968M
tmpfs           388M
/dev/nvme0n1p3   15G
/dev/nvme0n1p1  960M
/dev/nvme0n3p1   40M
/dev/sr0         11G
tmpfs           194M

[!NOTE]

shell三剑客grep、awk、sed

awk命令

功能:awk是一种编程语言,用于在linux/unix中对文本和数据进行处理,awk既可以实现对文件的行提取,也可以实现对文件的列提取,之所以叫 awk 是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符,awk是一个很全面的工具,此处我们只讲在Linux中如何利用awk进行字符串过滤,不讲过多关于awk编程的内容

格式:awk '条件{动作}' 文件名
1,条件的作用:通过指定条件过滤出符合条件的行,没有指定则是操作所有行
2,动作的作用:通过动作将符合条件的行打印出来,并且在打印时我们可以选择打印该行中的哪些列

#不指定任何条件,直接执行动作,并选择输出哪些列,常用printf来打印
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63

$ cut -f 2,5 cut.txt
NAME    DOCKER
xzhao   78
xqian   93
xsun    63

#等价cut,截取第2列和第5列的值
$ awk '{printf $2"\t"$5"\n"}' cut.txt
或:
$ awk '{print $2"\t"$5}' cut.txt
NAME    DOCKER
xzhao   78
xqian   93
xsun    63
内置作用
$0代表awk读入当前行的整行数据
$n代表awk读入当前行的第n列数据
NR代表当前awk正在处理的行的行号
NF代表当前awk读取数据总字段数(总列数)
FS用来声明awk的分隔符,如BEGIN {FS=":"}
#读取文件中任意一列的数据
$ awk '{printf $1"\t" $2"\t" $3"\n"}' cut.txt
$ awk '{printf $0"\n"}' cut.txt
#NF
#读取文件的每行的总列数
$ awk '{printf NF "\n"}' cut.txt
#获取文件的最后一列
$ awk '{printf $NF "\n"}' cut.txt
#获取文件的倒数第二列
$ awk '{printf $(NF-1) "\n"}' cut.txt
#读取文件的第一列和最后一列
$ awk '{printf $1"\t" $NF"\n"}' cut.txt
#FS或-F
#awk默认可以识别的分隔符:空白(即空格、tab、连续的空格、连续的tab)
#读取/etc/passwd中第一列和最后一列
$ awk 'BEGIN {FS=":"}{printf $1"\t"$NF"\n"}' /etc/passwd
或:
$ awk -F: '{printf $1"\t"$NF"\n"}' /etc/passwd
#获取当前所在路径的最后一个层级目录
$ cd /home/jimmy
$ pwd | awk  -F/ '{printf $NF "\n"}'
#NR条件
#截取磁盘的使用率
$ df -h | awk '{printf $5"\n"}'
#截取磁盘使用率的数据部分
$ df -h | awk 'NR>1{printf $5"\n"}'
#截取根分区的磁盘使用率
$ df -h | awk 'NR==5{printf $5"\n"}' 
#截取根分区的磁盘使用率的数值
$ df -h | awk 'NR==5{printf $5"\n"}' | cut -d% -f1

格式:awk '条件{动作}' 文件名

awk的条件

  • 自定义条件
    • 关系运算条件(>、<、>=、<=、==、!=),用来判断左右两侧的关系
    • 包含匹配条件(~、!~、~//、!~//),用来进行匹配包含关系的
  • 预定义条件(保留字)
    • BEGIN:在awk未读取数据前声明的条件,该条件后的动作仅在程序开始时执行一次,不会重复执行
    • END:类似于BEGIN,在awk处理完所有数据后声明的条件,在该条件后的程序仅在程序结束前执行一次

[!NOTE]

注:若有多个 条件{动作} 可以用空格分割
关系运算条件(>、<、>=、<=、==、!=),用来判断左右两侧的关系,一般左侧为变量,右侧为参考值

#列出行号大于1的行的所有列信息
$ awk 'NR>1{printf $0"\n"}' cut.txt
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63

#列出学号为2号的各科成绩单
$ awk '$1==2{printf $0"\n"}' cut.txt
2   xsun    74  96  63

#列出Linux成绩大于等于80分的成绩单
$ awk '$3>=80{printf $2"\t"$3"\n"}' cut.txt
NAME    LINUX
xzhao   95
xqian   83
$ awk '$3==83{printf $2"\t"$3"\n"}' cut.txt
xqian   83
$ awk '$3<=83{printf $2"\t"$3"\n"}' cut.txt
xqian   83
xsun    74
#第一行表头只会出现在大于号的结果中


#获取当前用户使用的网卡名
$ ip addr | grep 'inet ' | grep -v '127.0.0.1' | awk 'NR==1{printf $NF "\n"}'

$ ip addr | awk '/ens/{print $0}' | grep -v ":" | awk '{print $NF}'

[!NOTE]

包含匹配条件(~、!~、~//、!~//),用来进行匹配包含关系的,判断左侧变量中是否包含右侧的字符串,当右侧字符串中包含一些特殊符号时,需要使用//然后在里面使用\转义符将符号转义为普通字符

> #列出第二列包含字符串q的数据
> $ awk '$2~"q"{printf $0"\n"}' cut.txt
> 1   xqian   83  75  93
> #列出名字以n结尾的数据
> $ awk '$2~"n$"{printf $0"\n"}' cut.txt
> $ awk '$2~/n$/{printf $0"\n"}' cut.txt
> 1   xqian   83  75  93
> 2   xsun    74  96  63
> #列出包含数字3的数据
> $ awk '$0~"3"{printf $0"\n"}' cut.txt
> $ awk '$0~/3/{printf $0"\n"}' cut.txt
> $ awk '/3/{printf $0"\n"}' cut.txt
> 1   xqian   83  75  e93
> 2   xsun    74  96  63   
> #列出包含以.com结尾的数据
> $ awk '/\.com$/{printf $0"\n"}' cut.txt
> #列出指定设备的磁盘使用率
> $ df -h | awk '/(sd|sr)[a-z]?[0-9]/{printf $1"\t"$5"\n"}'
> /dev/sr0      100%
> /dev/sda1     30%
> #列出包含xsun的数据
> $ awk '$2~"xsun"{printf $0"\n"}' cut.txt
> $ awk '/xsun/{printf $0"\n"}' cut.txt
> 2   xsun    74  96  63
> #使用外部变量
> $ name=xsun
> $ awk '/'$name'/{printf $0"\n"}' cut.txt
> 或:
> $ awk '/'''$name'''/{printf $0"\n"}' cut.txt
> 2   xsun    74  96  63
#预定义条件:BEGIN、END
#在处理数据前执行BEGIN动作,若有多个 条件{动作} 可以用空格分割
$ awk 'BEGIN{printf "MYSQL成绩单:\n"} {printf $2"\t"$4"\n"}' cut.txt
MYSQL成绩单:
NAME     MYSQL
xzhao   59
xqian   75
xsun    96
#在操作完数据后执行END动作
$ awk '{printf $2"\t"$5"\n"}END{printf "以上显示的是所有人的DOCKER成绩\n"}' cut.txt
NAME    DOCKER
xzhao   78
xqian   93
xsun    63
以上显示的是所有人的DOCKER成绩

[!IMPORTANT]

awk的工作原理

  1. 先查看是否有BEGIN条件,有则先执行BEGIN后面的动作;
  2. 然后读入第一行数据,使用分隔符分隔好列之后依次赋值给变量$0、$1、$2、$3 ...等,$0代表整行数据,$1为第一列数据,依次类推。第一行将所有内容赋值完成后,进行条件判断,按照符合条件的动作执行,不满足不执行;
  3. 处理完第一行之后,将第二行数据重复第一行的所有步骤,依次处理每行数据直到处理完整个文本
  4. 最后看是否有END条件,有则执行一次END后面的动作;
动作{printf}
功能:printf是标准的格式化输出,取消所有默认格式,然后手动指定输出内容的类型和输出时的格式
格式:printf '类型/格式' 字符串
输出类型:
    %s    将内容按照字符串类型输出,默认类型
        (如:ns:代表输出宽度是n,默认右对齐,-ns左对齐)
    %i    将内容按照整数类型输出(=%d)
        (如:ni:代表输出宽度是n,默认右对齐,-ns左对齐)
    %f    将内容按浮点数类型输出
        (如:%.2f:代表输出小数点数值时保留两位小数点,会进行四舍五入)
输出格式:
    \t:字符之间用制表符分割,即tab键
    \n:字符之间用换行符分割,即enter键
    注:输出格式需要加双引号
    
#使用printf输出表格文件
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63

$ printf '%s' $(cat cut.txt)
IDNAMELINUXMYSQLDOCKER0xzhao9559781xqian8375932xsun749663[root@localhost ~]# 

#在使用printf输出时,如果仅指定输出类型,而不指定输出格式,则会把所有要输出内容连在一起输出,变为一整行
$ printf '%s\t%s\t%s\t%s\t%s\n' $(cat cut.txt)
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63

#在输出时,想让第1列使用整数类型输出,3,4,5列使用浮点类型输出
$ printf '%i\t%s\t%.2f\t%.2f\t%.2f\n' $(cat cut.txt)
-bash: printf: ID: invalid number
-bash: printf: LINUX: invalid number
-bash: printf: MYSQL: invalid number
-bash: printf: DOCKER: invalid number
0   NAME     0.00    0.00    0.00
0    xzhao   95.00   59.00   78.00
1   xqian   83.00   75.00   93.00
2   xsun    74.00   96.00   63.00

#取消对表格首行的指定格式输出
$ printf '%i\t%s\t%.2f\t%.2f\t%.2f\n' $(cat cut.txt|grep -v ID)
0   xzhao   95.00   59.00   78.00
1   xqian   83.00   75.00   93.00
2   xsun    74.00   96.00   63.00

#更改cut内容,变成小数
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    59.996  96  63

#打印名字和Linux成绩,且成绩保留2位小数
$  printf '%s\t%.2f\n' $(awk 'NR>1{printf $2"\t"$3"\n"}' cut.txt) 
xzhao   95.00
xqian   83.00
xsun    60.00

#指定数字的输出长度及对齐方式
$ printf '%-20i\t%20i\n' $(awk 'NR>1{printf $4"\t"$5"\n"}' cut.txt)

#指定字符串输出长度及对齐方式
$ printf '%-23s\t%4s\n' $(df -h | awk '{printf $1"\t"$5"\n"}')

----
#awk中默认支持数值运算,并且整数、浮点数运算都支持
#计算每个人的平均值
$ awk 'NR>1{printf $2"的平均分是\t"($3+$4+$5)/3"\n"}' cut.txt
xcang的平均分是    77.3333
xbo的平均分是      83.6667
xlong的平均分是    77.6667
#浮点型截取指定位数
$ printf '%s\t%.2f\n' $(awk 'NR>1{printf $2"的平均分是\t"($3+$4+$5)/3"\n"}' 
cut.txt)
xcang的平均分是        77.33
xbo的平均分是        83.67
xlong的平均分是        77.67

sed命令

sed是一种(stream editor)流编辑器,适合对文件内容进行简单的替换、删除等操作

功能:实现非交互式对文件数据进行选取、替换、删除、新增等操作的命令,即不进入文本内对其内容进行修
改;主要包括读取、执行和显示三个过程

格式:sed [选项] '动作' 文件名
选项:

-n    将经过处理后的数据输出到控制台上;不加-n输出全文+指定行
-i    直接修改文件内容;默认下sed不会对文件直接进行修改,而是在内存中修改并将结果效果显示在控制台上

动作:

p    print    打印,输出指定的行
a    append    追加,在当前行后追加一行或多行
i    insert    插入,在当前行前插入一行或多行
d    delete    删除,删除指定的一行或多行
c    character    整行替换,用c后面的字符串替换原数据指定行的数据
s    string    字串替换,用一个字符串替换另外一个字符串,格式“行范围s/旧字串/新字串/g”
----数据准备
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63

-----查看
#显示cut.txt中第三行的信息:
$ sed '3p' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93   --
1   xqian   83  75  93   --
2   xsun    74  96  63

$ sed -n '2p' cut.txt
1   xqian   83  75  93
#查看连续的多行
$ sed -n '2,4p' cut.txt
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63
#查看不连续的多行
$ sed -n '2p;4p' cut.txt
0   xzhao   95  59  78
2   xsun    74  96  63
#查看符合条件的行,支持正则,扩展正则需要加转义符
$ sed -n '/a/p' cut.txt
0   xzhao   95  59  78
1   xqian   83  75  93
$ sed -n '/[23]/p' cut.txt
1   xqian   83  75  93
2   xsun    74  96  63
$ sed -n '/[23]\?/p' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63


----追加、不加-i不改变源数据
#a在指定行后面追加
$ sed '3a 12 xli 80 90 100' cut.txt       
$ sed '3a 12    -添加行的数据间用空格
xli 80  90  100' cut.txt  -添加行的数据间用tab
$ sed '3a 12\txli\t80\t90\t100\t' cut.txt -添加行的数据间用\t
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
12  xli 80  90  100 
2   xsun    74  96  63
#i在指定行前面插入
$ sed '3i 12\txli\t80\t90\t100\t' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
12  xli 80  90  100 
1   xqian   83  75  93
2   xsun    74  96  63
#-a和-i可以在指定位置上追加多行
$ sed '3a 100 zhangsan \n200 lisi' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
100 zhangsan 
200 lisi
2   xsun    74  96  63
#加上-i选项才能更改源数据
$ sed -i '3i 12\txli\t80\t90\t100\t' cut.txt
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
12  xli 80  90  100 
1   xqian   83  75  93
2   xsun    74  96  63


----删除、不加-i不改变源数据    
#删除cut.txt中的第3行数据
$ sed '3d' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63
#删除cut.txt中的第2行到第4行数据
$ sed '2,4d' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
#删除cut.txt中的第2行和第4行数据
$ sed '2d;4d' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
1   xqian   83  75  93
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63--
#加上-i选项才能更改源数据
$ sed -i '3d' cut.txt
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63


----替换、不加-i不改变源数据
#整行替换数据:将cut.txt中第3行内容换成'No such person'
$ sed '3c No such person' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
No such person
2   xsun    74  96  63
#字符串替换:将cut.txt中第3行的'x'换成'xiao'
$ sed '3s/x/xiao/g' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xiaoqian    83  75  93
2   xsun    74  96  63
#字符串替换:将cut.txt中所有行的'x'换成'xiao'
$ sed 's/x/xiao/g' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xiaozhao    95  59  78
1   xiaoqian    83  75  93
2   xiaosun 74  96  63
#字符串替换:将指定的一行内容替换成空
$ sed '3s/[0-9]//g' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
    xqian           
2   sun    74  96  63      
#字符串替换:同时替换多行,用;分隔
$ sed '3s/^/#/g;4s/[0-9]//g' cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
#1  xqian   83  75  93
    xsun            
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
1   xqian   83  75  93
2   xsun    74  96  63
------------
#加上-i选项才能更改源数据
$ sed -i '3s/^/#/g;4s/[0-9]//g' cut.txt
$ cat cut.txt
ID  NAME    LINUX   MYSQL   DOCKER
0   xzhao   95  59  78
#1  xqian   83  75  93
    xsun   


----
#练习:想关掉SELiunx,用sed命令更改配置文件
$ sed -i '22s/disabled/enforcing/g' /etc/selinux/config 
#练习:用sed命令修改网卡配置文件,进行IP地址获取方式的切换
#获取当前使用的网卡名
$ interface=$(ip addr | grep -e 'inet ' | grep -v '127.0.0.1' | awk 'NR==1{printf $NF "\n"}')
#将较长的配置文件名赋予变量
$ cf="/etc/NetworkManager/system-connections/$interface.nmconnection"
#查看当前网卡配置,并显示行号
$ cat -n $cf
1 [connection]
2  id=ens160
3  uuid=942f40a1-09af-347e-bc1e-6ad72c53b39f
4  type=ethernet
5  autoconnect-priority=-999
6  interface-name=ens160
7  timestamp=1733853830
8  
9  [ethernet]
10  
11  [ipv4]
12  method=manual
13  address=192.168.66.191/24,192.168.66.195
14  
15  [ipv6]
16  addr-gen-mode=eui64
17  method=auto
18  
19  [proxy]
#将静态配IP改为dhcp获取
$ sed '12s/manual/auto/g;13s/^/#/g' $cf

[!TIP]

tr命令

#替换大小写:
$ echo "test 123" | tr 'a-z' 'A-Z'
#删除空格:
$ echo "a    ff  xx 123   " | tr -d ' '
或:
$ echo "a    ff  xx 123   " | sed 's/ //g'

tr --help
用法:tr [选项]... SET1 [SET2]
Translate, squeeze, and/or delete characters from standard input,
writing to standard output.

-c, -C, --complement    use the complement of SET1
-d, --delete            delete characters in SET1, do not translate
-s, --squeeze-repeats   replace each sequence of a repeated character
that is listed in the last specified SET,
with a single occurrence of that character
-t, --truncate-set1     first truncate SET1 to length of SET2
--help        显示此帮助信息并退出
--version        显示版本信息并退出

SET 是一组字符串,一般都可按照字面含义理解。解析序列如下:
\\        反斜杠
\a        终端鸣响
\b        退格
\f        换页
\n        换行
\r        回车
\t        水平制表符
\v        垂直制表符
字符1-字符2    从字符1 到字符2 的升序递增过程中经历的所有字符
[字符*]    在SET2 中适用,指定字符会被连续复制直到吻合设置1 的长度
[字符*次数]    对字符执行指定次数的复制,若次数以 0 开头则被视为八进制数
[:alnum:]    所有的字母和数字
[:alpha:]    所有的字母
[:lower:]    所有的小写字母
[:upper:]    所有的大写字母
[:digit:]    所有的数字
[:graph:]    所有的可打印字符,不包括空格
[:print:]    所有的可打印字符,包括空格
[:punct:]    所有的标点字符
[=字符=]    所有和指定字符相等的字符

#拓展# 该命令将从 /dev/urandom 设备读取随机数据,并使用 tr 命令过滤和截取所需长度的字符 
pw=`tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 10`
'A-Za-z0-9' == [:alnum:]
随机生成10位字符包含大小写字母和数字

sort命令

功能:对指定文件里的行进行排序,默认使用每行开头第一个字符进行排序
语法:sort [选项] 文件名
选项:

​ -f 忽略大小写

​ -b 忽略每行前的空白部分

​ -n 以数值型进行排序,默认使用字符串类型排序

​ -r 反向排序

​ -u 删除重复行(=下面的uniq)

​ -t 指定分隔符,默认分割符是制表符

​ -k n 指定使用第几列的内容进行排序,一般和-t结合使用

#测试数据
$ cat sort.txt
Cdm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
root:x:0:0:root:/root:/bin/bash
#忽略大小写
$ sort -f sort.txt                    
daemon:x:2:2:daemon:/sbin:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
Cdm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
root:x:0:0:root:/root:/bin/bash
#忽略大小写和行前空白
$ sort -bf sort.txt                   
bin:x:1:1:bin:/bin:/sbin/nologin
Cdm:x:3:4:adm:/var/adm:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
root:x:0:0:root:/root:/bin/bash
#去掉重复行
$ sort -bfu sort.txt
bin:x:1:1:bin:/bin:/sbin/nologin
Cdm:x:3:4:adm:/var/adm:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
root:x:0:0:root:/root:/bin/bash
#冒号分隔符,第三列,从小到大
$ sort -t: -k 3 -n  sort.txt          
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
Cdm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
#以数值方式反向排序,从大到小
$ sort -t: -k 3 -nru  sort.txt         
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
Cdm:x:3:4:adm:/var/adm:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
root:x:0:0:root:/root:/bin/bash

uniq命令

功能:用来取消重复行,与sort -u 功能是一样的
格式:uniq [选项] 文件名
选项:

​ -i 忽略大小写

​ -c 在关键词旁显示该关键词出现的次数(一般针对行)

[!CAUTION]

注意:当重复行不连续时,uniq是不生效的,需要先排序,再执行

#准备数据
$ cat uniq.txt
linux 30
unix 20
windows 50
windows 50
Windows 50
Linux 30
windows 50
#去重
$ uniq uniq.txt
linux 30
unix 20
windows 50
Windows 50
Linux 30
windows 50
#比原来少了一样,只去掉了相邻重复的一行
$ uniq uniq.txt |wc -l
6
#想去除所有重复行,需要先排序
$ sort uniq.txt | uniq
linux 30
Linux 30
unix 20
windows 50
Windows 50
$ sort uniq.txt | uniq -c
1 linux 30
1 Linux 30
1 unix 20
3 windows 50
1 Windows 50
$ sort uniq.txt | uniq -ci | sort -rn
4 windows 50
2 linux 30
1 unix 20--------
#获取当前系统用户的shell类型、并分类统计、按降序排序
$ cut -d: -f7 /etc/passwd | sort | uniq -c | sort -nr

条件判断

按照文件类型进行判断


测试选项作用
-b 文件判断该文件是否存在、且为块设备文件
-c 文件判断该文件是否存在、且为字符设备文件
-d 文件判断该文件是否存在、且为目录
-e 文件判断该文件是否存在
-f 文件判断该文件是否存在、且为普通文件
-L 文件判断该文件是否存在、且为符号链接文件
-p 文件判断该文件是否存在、且为管道文件
-s 文件判断该文件是否存在、且内容非空(有内容为真)
-S 文件判断该文件是否存在、且为套接字文件
#使用[]或test进行判断,使用[]时与内容左右都需要有一个空格
#可以使用echo $?来检测判断的结果,0为真、1为假
[ -e /etc/passwd ]
echo $?         -- 0
test -e /etc/new 
echo $?         -- 1
#也可以结合 && 和 || 来测试。
[ -e /etc/passwd ] && echo yes || echo no 
[ -e /etc/new ] && echo yes || echo no 
----------------
#判断是文件还是目录
[ -f  /root ] && echo yes || echo no
[ -d  /root ] && echo yes || echo no
#判断文件是否为空
touch test.txt
[ -s  test.txt ] && echo yes || echo no
echo "test data" > test.txt
[ -s  test.txt ] && echo yes || echo no
#是软链接文件
[ -L /etc/rc.local ] && echo yes || echo no
cd /dev
#块设备是I/O设备中的一类,是将信息存储在固定大小的块中,每个块都有自己的地址,还可以在设备的任意
位置读取一定长度的数据,例如U盘,SD卡
[ -b /dev/sr0 ] && echo yes || echo no
[ -b /dev/nvme0n1p1 ] && echo yes || echo no
#字符设备是指在I/O传输过程中以字符为单位进行传输的设备例如键盘,打印机
[ -c /dev/stdin ] && echo yes || echo no
[ -c /dev/stdout ] && echo yes || echo no
[ -c /dev/zero ] && echo yes || echo no
[ -c /dev/null ] && echo yes || echo no
cd /run
#是套接字文件
[ -S /run/mcelog-client ] && echo yes || echo no
#是管道文件
[ -p /run/initctl ] && echo yes || echo no
--------------------------
=>同系统命令ll(ls -l)-:普通文件
d:目录文件
b:块设备文件
c:字符设备文件
l:链接文件
p:管道文件套接字文件
s:套接字文件
----------
#上面所有的选项都是判2个条件,文件存在且为什么类型
即:判断一个文件是不是普通文件,赋值不同得到结果不同
$ filename=/etc/passwd
$ [ -f "$filename" ] && echo "是普通文件" || ( [ -e "$filename" ] && echo "不是普通
文件" || echo "不存在" )
#多层条件判断时结构复杂,可以换if结构进行判断

字符串的判断

测试项目作用
-z 字符串判断字符串是否为空(为空返回TRUE)
-n 字符串判断字符串是否为非空(非空返回TRUE)
字符串1 == 字符串2判断字符串1是否和字符串2相等(相等返回真)
字符串1 != 字符串2判断字符串1是否和字符串2不相等(不相等返回真)
#一个字符串进行判断时
name=jimmy
age=""
unset sex
[ -z "$name" ] && echo yes || echo no    
[ -z "$age" ] && echo yes || echo no     
[ -z "$sex" ] && echo yes || echo no     
=> 字符串没有定义和没有赋值结果都为空----
#多个字符串进行比较时:
name=jimmy
age=""--no--yes   --yes
[ "$name" == $age ] && echo yes || echo no-bash: [: jimmy:需要一元表达式
#每个变量要加引号,不然没定义或没赋值时报语法错
[ "$name" == "$age" ] && echo yes || echo no   --yes
或:
[[ "$name" == $age ]] && echo yes || echo no
=>[]和[[]]的区别:
1、[[]]会识别比较符号,若是二元比较符,即使一个值没定义或没赋值,也会自动转为空字符串;
2、[[]]支持的表达式比[]更多,如:
在数值比较时支持>、<等符号;
有符号=~判断包含,参见cat .bashrc
name=jimmy;[[ $name =~ "y" ]] && echo yes || echo no

按照文件权限进行判断

测试选项作用
-r 文件判断该文件是否存在、且拥有读权限
-w 文件判断该文件是否存在、且拥有写权限
-x 文件判断该文件是否存在、且拥有执行权限
-u 文件判断该文件是否存在、且拥有SUID权限
-g 文件判断该文件是否存在、且拥有SGID权限
-k 文件判断该文件是否存在、且拥有SBit权
ll -d /etc/passwd     
=>-rw-r--r--
[ -r /etc/passwd ] && echo yes || echo no
[ -w /etc/passwd ] && echo yes || echo no    
[ -x /etc/passwd ] && echo yes || echo no
ll -d /usr/bin/passwd    --跟当前登录人权限有关
=>-rwsr-xr-x.
[ -u /usr/bin/passwd ] && echo yes || echo no  --yes
[ -g /usr/bin/passwd ] && echo yes || echo no   
[ -k /usr/bin/passwd ] && echo yes || echo no

#特殊权限的文件
#含有SUID权限,即当用户执行文件时会以用户所有者的身份进行执行
ll -d /usr/bin/passwd  =>-rwsr-xr-x.    u+s
#含有SUID权限,即当用户执行文件时会以用户所有者的身份进行执行
ll -d /usr/bin/locate  =>-rwx--s--x.    g+s
#含有SBIT权限,即设置该权限的目录,只有文件的创建者、目录所有者和root可以删除自己文件
ll -d /tmp             =>drwxrwxrwt.    O+t

两个文件间进行比较

测试项目作用
文件1 -nt 文件2判断文件1的修改时间是否比文件2的新
文件1 -ot 文件2判断文件1的修改时间是否比文件2的旧
文件1 -ef 文件2判断文件1是否和文件2的Inode号一致,
即判两个文件是否为同一个文件(常用于判断硬链接)
#判断是否为硬链接
cd
echo "ying test" > ying.txt
ln ying.txt /tmp/
[ /root/ying.txt -ef /tmp/ying.txt ] && echo yes || echo no

两个整数之间的比较

测试选项作用
整数1 -eq 整数2判断整数1是否和整数2相等(equal )
整数1 -gt 整数2判断整数1是否大于整数2(greater than)
整数1 -lt 整数2判断整数1是否小于整数2(less than)
整数1 -ge 整数2判断整数1是否大于等于整数2(greater than or equal to)
整数1 -le 整数2判断整数1是否小于等于整数2(less than or equal to)
[ 11 -ge 22 ] && echo yes || echo no
a=123
b=124
[ $a -eq $b ] && echo yes || echo no
[ $a -gt $b ] && echo yes || echo no
[ $a -lt $b ] && echo yes || echo no

多重条件判断

测试选项作用
判断1 -a 判断2逻辑与,判断1和判断2都成立,最终结果才为真
判断1 -o 判断2逻辑非,判断1和判断2有一个成立,最终结果就为真
!判断逻辑非,对判断结果取反,! 和 判断条件之间有个空格
#逻辑与
a=100
[ -n "$a" -a "$a" -gt 150 ] && echo yes || echo no
#逻辑或
b=150
[ "$b" -gt 150 -o "$b" -eq 150 ] && echo yes || echo no
#逻辑非
c=200
[ ! -n "$c" ] && echo yes || echo no

[!CAUTION]

\#[[]]和[]区别:

1、若进行字符串比较,使用到的变量可以不用写双引号,会自动识别变量个数

2、若进行数值比较,在[]支持的表达式(如:-gt)外,还支持>、<、==、!=等

3、若进行逻辑关系比较,与[]不同(-a、-o、!),而是(&&、||、 !)

4、进行字符串比较时,可进行模式匹配,支持通配符(如:[[ 3a == 3* ]])

 可进行包含比较,支持正则(如:[[ "nihao" =~ "ni" ]])

5、其他表达式含义同,如:文件类型判断、文件权限判断、多文件判断等

流程控制

编程中流程控制分为三类:顺序结构、分支结构、循环结构

if条件判断

单分支if条件语句

  • 单分支条件语句比较简单,只需一个判断条件,符合则执行,不符合则直接退出。
  • 判断条件符合的情况:

    • true
    • test或中括号条件判断式的结果为0的
    • 命令执行的结果为0的
  • 格式:

    • if [ 条件判断式 ]
    • then

      • 程序
    • fi
  • 或:

    • if [ 条件判断式 ];then

      • 程序
    • fi
#监控根分区的使用率:
$ cat check_disk_usage.sh
#!/bin/bash
#设置阈值,即实际使用超过这个值进行报警
threshold=70
#获取df -h中以/结尾的设备(根分区)
rate=$(df -h | awk '/\/$/{printf $5"\n"}'|cut -d% -f1)
err_msg="Warning! root usered rate is >=  $threshold; used:$rate%"
if [ $rate -gt $threshold ]
then
    #若超过阈值则打印,并红色字体醒目显示
    echo -e "\e[31m Warning! $err_msg \e[0m"
    #安装邮件软件包
    dnf -y install postfix s-nail
    #或者发邮件,mail -s 邮件标题  用户名  <  包含文件内容的文件
    echo $err_msg  | mail -s "Disk Usage Warning" root        
fi

----------------------------------
#监控内存使用率
$ free
total        
used        
Mem:        
Swap:       
1863252      
2097148           
164208     
0     
free      
1004808       
2097148
shared  buff/cache   available
10184      
694236     
#获取使用率,小数的
$ free | awk '/Mem/{printf $3/$2*100 "\n"}'
#对结果取整
$ printf '%.f\n' $(free | awk '/Mem/{printf $3/$2*100 "\n"}')
或:
$ free | awk '/Mem/{printf "%.f \n",$3/$2*100 }'
1490260
----------
#监控CPU使用率
$ top
$ top -n 1
#结果含义
第一行:当前时间;系统持续运行时间(up);登录用户数量;系统平均负载(一分钟、五分钟、十五分钟)
第二行:Tasks,进程数量的统计(总数、正在运行、正在睡眠、停止状态、僵死状态)
第三行:CPU使用情况
us:用户执行的进程所占用的CPU资源的百分比(用户占用)
sy:内核本身运行时所占用的CPU资源的百分比(系统占用)
ni:修改过进程优先级的进程,所占用的CPU资源百分比(us的占比包括ni占比)
id:CPU处于空闲状态的百分比
第四行:物理内存使用情况
total:总内存量
free:完全空闲的内存(所有空闲的内存的总和)
used:真正被利用和使用了的内存
buff/cache:被使用的内存当中,有一部分内存做了缓存(缓存区)
第五行:虚拟内存使用情况
#获取CPU使用率,仍需要小数变整数、进而进行阈值比较
$  top -n1 | grep "Cpu" | awk '{print $2+$4}'

双分支if条件语句

if [ 条件判断式 ]

then

​ 条件成立时,执行的程序

else

​ 条件不成立时,执行的另一个程序

fi

#判断文件是否存在
#!/bin/bash
#接受客户端输入一个文件名
read -p "Please input a file:" FILE
#检测是否有FILE文件
if [ -e $FILE ] 
then
    #有文件输出exists
    echo "$FILE exists" 
else
    #没有文件输出not exists
    echo "$FILE not exists" 
fi

--------------------------
#监控服务,监控另一台机器上的httpd服务的状态
#nmap
功能:端口扫描命令,结果显示的端口一定是存活的
格式:nmap -sT 域名或IP
选项:
    -s 扫描
    -T 扫描所有开启的TCP端口
    
#脚本运行可以安装 yum -y install nmap httpd
#在本机上
$ dnf -y install nmap
#在被监控的机器上
$ dnf -y install httpd
$ systemctl restart httpd

----
$ vim jiankong_httpd.sh
#!/bin/bash

kai=$(nmap -sT 192.168.66.192 | grep "http$" )
if [ -n "$kai" ]
then
    echo -e "\e[32m $(date) httpd is ok! \e[0m" 
else
    echo -e "\e[31m $(date) httpd is down!! \e[0m" 
#远程重启需要学习了SSH服务才能完成
fi

多分支if条件语句

if [条件判断式1]
then

当条件判断式1成立时,执行程序1

elif [条件判断式2]
then

当条件判断式2成立时,执行程序2
......(可加入更多elif条件)

else

当所有条件不成立时,最后执行此程序

fi

#需求:判断一个文件,是否有输入,是否存在
#判断用户输入的文件的属性
#!/bin/bash
#接收键盘输入并赋予变量file
read -p "Please input you filename:" file
#判断file变量是否为空
if [ -z "$file" ] 
then
    echo "Error,please input a filename" 
#判断file的值是否存在   
elif [ ! -e "$file" ]
then
    echo "your input is not exist" 
#判断file的值是否为普通文件
elif [ -f "$file" ] 
then
    echo "$file is a regulare file" 
#判断file是否是目录文件
elif [ -d "$file" ] 
then
    echo "$file is a directory!" 
#如果都不是则执行本程序。   
else
    echo "$file is an other file!"
fi

多分支case条件语句

语法:
case $变量名 in
“值1”)

如果$变量等于值1,则执行程序1

;;
“值2”)

如果$变量等于值2,则执行程序2

;;
....省略...
*)

如果$变量的值不是以上值,则执行此程序

;;
esac

#只能进行一种条件的判断,会列出所有可能的值;
#case列出的值,可以是一个数字、一个字符串,或是一个简单的正则表达式--------------
#练习:写一个源码apache的启动管理脚本
#!/bin/bash
read -t 5 -p "请输入你的操作:" x
case "$x" in
start)
    echo "正在启动服务。。"
;;
stop)
    echo "正在停止服务。。"
;;
restart)
    echo "正在重启服务。。"
;;
*)
    echo "输入错误,请输入start|stop|restart"
;;
esac

--------------
#用户输入yes或no
#!/bin/bash
read -p "please enter choose: (yes|no)" choose
case "$choose" in
[yY]|[yY][eE][sS])
    echo "执行"
;;
[nN]|[nN][oO])
    echo "不执行"
;;
*)
    echo "buzhidao"
esac

--------
#输入分数判断等级
#!/bin/bash
read -p "please enter your score: " score
case $score in
100|9[0-9]|8[5-9])
    echo "优"
;;
7[5-9]|8[0-5])
    echo "良"
;;
7[0-4])
    echo "好"
;;
*)
    echo "不及格"
;;
esac
----
#判断输入是否为数字
$ echo $input | sed 's/[0-9]//g'
$ [ -z "$guo" ] && echo "是数字" || echo "不是数字"

for循环

语法一:
for 变量 in 值1 值2 值3 ...
do

执行程序

done
语法二:
for ((初始值;循环控制条件;变量变化))
do

执行程序

done

[!CAUTION]

注:初始值:在循环开始时,需要给某个变量赋予初始值,如i=1;循环控制条件:用于指定变量循环的次数,如i<=100,则只要i的值小于等于100,循环就会继续;变量变化:每次循环之后,变量该如何变化,如i=i+1。代表每次循环之后,变量i的值都加1。

#!/bin/bash
#for的取值范围的几种写法
#for i in 1 2 3 4 5 6 7 8 9 10
#for i in {1..10} 
#for i in {10..1} 
#for i in {1..10..2} 
#for i in {10..2..2} 
#for i in  $(seq 10)
#for i in  $(seq 1 2 10)
for i in $(ls $PWD) 
do
    echo $i
done
------------
#类C结构的for循环
#!/bin/bash
sum=0
for ((i=1; i<=100; i=i+1))
do
    sum=$(($sum+$i))
#或:let sum+=$i
done
echo "The sum of 1+2+....+100 is :$sum"
--------------
#自动解压
#!/bin/bash
pos=/lamp
for i in $(ls $pos/*.tar.gz)
do
    echo $i
    tar -xf $i -C $pos
done
----------------------------
#批量创建用户(默认用户名、默认密码)
#!/bin/bash
read -p "Please input user name:" -t 30 name
read -p "Please input the number of users:" -t 30 num
read -p "Please input the password of users:" -t 30 pass
if [ ! -z "$name" -a ! -z "$num" -a ! -z "$pass" ]
then
    y=$(echo $num | sed 's/[0-9]//g')
    if [ -z "$y"]
    then
        for (( i=1;i<=$num;i=i+1))
        do
            /usr/sbin/useradd $name$i &> /dev/null  
            echo $pass | /usr/bin/passwd --stdin $name$i &> /dev/null
        done
    fi
fi

while循环

只要条件判断式成立,循环就会一直继续,直到条件不成立,循环才停止
语法:
while [ 条件判断式 ]
do

程序

done

#练习:计算1+2+...+100的值
#!/bin/bash
i=1
sum=0
while [ $i -le 100 ]
do
    sum=$(( $sum+$i))
    i=$(( $i+1))
done
echo "the sum is:$sum"
----------------
#while true进行无限(死)循环:
#!/bin/bash
while true
do
    kai=$(nmap -sT 192.168.66.192 | grep "http$" )
    if [ -n "$kai" ]
    then
        echo -e "\e[32m $(date) httpd is ok! \e[0m" 
    else
        echo -e "\e[31m $(date) httpd is down!! \e[0m" 
        #远程重启需要学习了SSH服务才能完成
    fi
    #检测的间隔时间为10秒
    sleep 10
done

until循环

until循环和while循环相反,只要条件判断式不成立,则一直循环;直到条件成立则结束循环
格式:
until [ 条件判断式 ]
do

程序

done

#!/bin/bash
i=1
sum=0
until [ $i -gt 100 ]
do
    let sum+=$i
    let i++
done
echo "the sum is:$sum"

特殊流程控制

exit

系统中的exit是退出当前登录,但在shell中是退出脚本,回到Linux命令行

  • 格式:exit [ 值 ]
  • exit 退出时如果定义好了返回值,那么我们可以通过“$?”来查看。如果exit命令之后定义了返回值,那么这个脚本执行之后的返回值就是我们自己定义的返回值。可以通过$?查询,来查看返回值,范围是0-255。如果exit之后没有定义返回值,脚本执行之后的返回值是执行exit之前,最后执行一条命令的返回值
$ cat exit.sh
#判断用户输入的文件是否存在
#!/bin/bash
read -p "please enter a filename:" file
if [ -e "$file" ]
then
    echo "文件存在"
else
    echo "文件不存在"
    #定义异常退出码
    exit 8
fi
echo "byebye"
----------
#调用之后可以用$?查看结果
#文件存在,打印存在,并执行if之后的语句
#echo $?的结果是0
[root@localhost ~]# ./exit.sh 
nihao
please enter a filename:/etc/passwd
文件存在
byebye
[root@localhost ~]# echo $?
0
#文件不存在,打印不存在,并退出脚本、不执行if之后的语句;
#echo $?的结果是自定义的8
[root@localhost ~]# ./exit.sh 
nihao
please enter a filename:/etc/haha
文件不存在
[root@localhost ~]# echo $?
8

break

  • break概述:跳出当前整个循环或结束当前循环,在for、while等循环语句中,用于跳出当前所在 的循环体,执行循环体之后的语句;
  • 后面如果什么也不加,表示跳出当前循环等价于break 1,也可以在后面加数字,假设break 3表示 跳出第三层循环
#!/bin/bash
for ((i=1; i<=10; i=i+1 ))
do
if [ "$i" -eq 4 ]
then
break
fi
echo $i
done
----
#猜数字游戏
[root@localhost ~]# cat guess.sh 
#!/bin/bash
read -sp "please enter the answer:" num
echo
while true
do
    read -p "please guess a num:" num1
    if [ "$num1" -gt "$num" ]
    then
        echo "大了"
    elif [ $num1 -eq $num ]
    then
        echo "对了"
           break
    else
        echo "小了"
    fi
done

continue

  • continue概述:忽略本次循环剩余的代码,直接进行下一次循环;在for、while等循环语句中,用 于跳出当次所在的循环,不终止循环,继续执行剩余循环
#还用刚才的脚本,把break换成continue
#!/bin/bash
for ((i=1; i<=10; i=i+1 ))
do
    if [ "$i" -eq 4 ]
    then
        continue 
    fi
    echo $i
done

函数

  • 在编写脚本时,有些语句会被重复使用多次。把这些可能重复使用的代码写成函数,这样我们通过函数名称可以更高效的重复利用他们。如果想让自己写的脚本代码可以为别人所使用,同样需要函数功能。
  • 格式:
    function 函数名() {

    程序

    }

#!/bin/bash
function demoFun(){
echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"

----------------------
#定义一个求和函数,计算从1到用户输入数字的和
$ vim function.sh
#!/bin/bash
#定义函数
function sum () {
s=0
for (( i=0;i<=$1;i=i+1 ))
do
    s=$(( $i+$s ))
done
echo "the sum of 1+2+3+..+$1 is :$s"
}
-----------------
#本脚本直接调用该函数,继续写
$ vim function.sh
read -p "please input a number:" num
y=$(echo $num | sed 's/[0-9]//g')
if [ -z "$y" ]
then
    sum $num
else
    echo "error!!please input a number!"
fi
-------------------
#其他脚本调用
$ cat diaoyong.sh
#!/bin/bash
#先用.或sourceh引入函数所在的脚本
. /root/function.sh
#再用函数名调用
read -p "please input a number:" num
y=$(echo $num | sed 's/[0-9]//g')
if [ -z "$y" ]
then
    sum $num
else
    echo "error!!please input a number!"
fi
Shell脚本 - 高阶
https://reeskysui.xyz/index.php/archives/8/
本文作者 明关
发布时间 2025-04-07
许可协议 CC BY-NC-SA 4.0
发表新评论