# Shell 编程
# 正则表达式(基础)
正则表达式与通配符
- 正则表达式用来在文件中匹配符合条件的字符串,正则是包含匹配。grep、awk、sed 等命令可以支持正则表达式。
- 通配符用来匹配符合条件的文件名,通配符是完全匹配。ls、find、cp 这些命令不支持正则表达式,所以只能使用 shell 自己的通配符来进行匹配了。(通配符不多,有
* ? []
)
基础正则表达式(不包含扩展正则表达式)
| 元字符 | 作用 |
|---|---|
|*
| 前一个字符匹配 0 次或任意多次 |
|.
| 匹配除了换行符外任意一个字符 |
|^
| 匹配行首。例如:^hello 会匹配以 hello 开头的行 |
|$
| 匹配行尾。例如:hello$ 会匹配以 hello 结尾的行 |
|[]
| 匹配中括号中指定的任意一个字符,只匹配一个字符。例如:[aoeiu] 匹配任意一个元音字母,[0-9] 匹配任意一个数字,[a-z][0-9] 匹配小写字母和一位数字构成的两位字符。|
|[^]
| 匹配除中括号的字符以外的任意一个字符。例如:[0-9] 匹配任意一个非数字字符,[a-z] 匹配任意一位非小写字母。|
|\| 转义符。用于取消将特殊符号的含义取消。|
|\{n\}
| 表示其前面的字符恰好出现 n 次。例如:[0-9]{4} 匹配 4 位数字,[1][3-8][0-9]{9} 匹配手机号码 |
|\{n,\}
| 表示其前面的字符出现不小于 n 次。例如:[0-9]{2,} 表示两位及以上的数字 |
|{n,m\}
| 表示其前面的字符至少出现 n 次,最多出现 m 次。例如:[a-z]{6,8} 匹配 6 到 8 位的小写字母 |# "*" 前一个字符匹配 0 次,或任意多次 (其实相当于匹配整篇文档,没啥意义)
[root@core-pods-3 ~]# cat test_rule.txt
Jalen Chu
Jaalen Chu
Jaaalen Chu
Jblen Chu
[root@core-pods-3 ~]# grep "a*" test_rule.txt
Jalen Chu
Jaalen Chu
Jaaalen Chu
Jblen Chu
[root@core-pods-3 ~]# grep "aa*" test_rule.txt # 至少出现 1 个 a
Jalen Chu
Jaalen Chu
Jaaalen Chu
[root@core-pods-3 ~]# grep "aaa*" test_rule.txt # 匹配至少出现 2 个 a
Jaalen Chu
Jaaalen Chu
[root@core-pods-3 ~]# grep "aaaa*" test_rule.txt # 匹配至少出现 3 个 a
Jaaalen Chu
[root@core-pods-3 ~]# grep "aaaaa*" test_rule.txt # 匹配至少出现 4 个 a,无匹配结果
# “.” 匹配除了换行符外任意一个字符
[root@core-pods-3 ~]# cat test_rule.txt
Jalen Chu
Jaalen Chu
Jaaalen Chu
Jblen Chu
[root@core-pods-3 ~]# grep "J...n" test_rule.txt # 匹配 J 和 n 之间有 3 个字符的单词
Jalen Chu
Jblen Chu
[root@core-pods-3 ~]# grep "J.*n" test_rule.txt # 匹配 J 和 n 之间有任意字符
Jalen Chu
Jaalen Chu
Jaaalen Chu
Jblen Chu
[root@core-pods-3 ~]# grep ".*" test_rule.txt # 匹配所有内容
Jalen Chu
Jaalen Chu
Jaaalen Chu
Jblen Chu
[root@core-pods-3 ~]# grep "a.*n" test_rule.txt # 匹配 a 和 n 之间有任意字符
Jalen Chu
Jaalen Chu
Jaaalen Chu
# “^” 匹配行首,“$” 匹配行尾
[root@core-pods-3 ~]# cat test_rule.txt # 注意 5、6 行是空白行
Jalen Chu
Moe Zhou
Bella Wang
Icon Qian
[root@core-pods-3 ~]#
[root@core-pods-3 ~]# grep "^B" test_rule.txt # 匹配以大写 B 开头的行
Bella Wang
[root@core-pods-3 ~]# grep "n$" test_rule.txt # 匹配以小写 n 结尾的行
Icon Qian
[root@core-pods-3 ~]# grep -n "^$" test_rule.txt # 匹配空白行,空白行看不出来,加个 - n
5:
6:
[root@core-pods-3 ~]#
# “[]” 匹配中括号中指定的任意一个字符,只匹配一个字符
[root@core-pods-3 ~]# cat test_rule.txt
Jalen Chu
Moe Zhou
Bella Wang
Icon Qian
test123
test456
789
[root@core-pods-3 ~]# grep "M [aoe] e" test_rule.txt # 匹配 M 和 e 之间出现 a,o,e 其中 1 个字符的行
Moe Zhou
[root@core-pods-3 ~]# grep "e [lmn] la" test_rule.txt # 匹配 e 和 l 之间出现 l,m,n 其中一个字符的行
Bella Wang
[root@core-pods-3 ~]# grep "[0-9]" test_rule.txt # 匹配出现任意数字的行
test123
test456
789
[root@core-pods-3 ~]# grep "^[a-z]" test_rule.txt # 匹配小写字母开头的行
test123
test456
[root@core-pods-3 ~]# grep "^[0-9]" test_rule.txt # 匹配数字开头的行
789
[root@core-pods-3 ~]# grep "^[^0-9]" test_rule.txt # 匹配非数字开头的行
Jalen Chu
Moe Zhou
Bella Wang
Icon Qian
test123
test456
[root@core-pods-3 ~]# grep "^[^a-z]" test_rule.txt # 匹配非小写字母开头的行
Jalen Chu
Moe Zhou
Bella Wang
Icon Qian
789
[root@core-pods-3 ~]# grep "^[^a-zA-Z]" test_rule.txt # 匹配非字母开头的行
789
# “\” 转义符
[root@core-pods-3 ~]# cat test_rule.txt
I am a policeman, I will catch the criminal.
Do you know?
My name is Jalen.
[root@core-pods-3 ~]# grep ".$" test_rule.txt # 这里没将。转义,匹配所有了
I am a policeman, I will catch the criminal.
Do you know?
My name is Jalen.
[root@core-pods-3 ~]# grep "\.$" test_rule.txt # 匹配以。结尾的行
I am a policeman, I will catch the criminal.
My name is Jalen.
[root@core-pods-3 ~]#
[root@core-pods-3 ~]# cat test_rule.txt
jalen
jaalen
jaaalen
moe
mooe
moooe
[root@core-pods-3 ~]# grep "a\{3\}" test_rule.txt # 匹配包含 3 个 a 的行
jaaalen
[root@core-pods-3 ~]# grep "o\{2\}" test_rule.txt # 匹配包含 2 个 a 的行
mooe
moooe
[root@core-pods-3 ~]# cat test_rule.txt
my telephone is 1865666666.
and how about you?
let me see, it's 138enn, sorry, I don't know.
[root@core-pods-3 ~]# grep "[0-9]\{3,\}[a-z]" test_rule.txt # 至少 3 个数字
let me see, it's 138enn, sorry, I don't know.
[root@core-pods-3 ~]# grep "[0-9]\{2,\}[a-z]" test_rule.txt # 至少 2 个数字
let me see, it's 138enn, sorry, I don't know.
[root@core-pods-3 ~]# grep "[0-9]\{4,\}[a-z]" test_rule.txt # 至少 4 个数字
[root@core-pods-3 ~]# grep "6\{5\}" test_rule.txt # 6 至少出现 5 次
my telephone is 1865666666.
[root@core-pods-3 ~]# grep "6\{5,6\}" test_rule.txt # 6 出现至少 5 次,最多 6 次
my telephone is 1865666666.
[root@core-pods-3 ~]# grep "6\{7,9\}" test_rule.txt # 6 出现至少 7 次,最多 9 次
# 字符截取命令
# cut 字段提取命令
与 grep 提取符合条件的行相反,cut 命令提取列(制表符)。cut 一般不会独立使用,基本都死结合 grep 命令通过管道符连接使用。命令为 cut [选项] 文件名
。选项有:
- -f:列号,提取第几列
- -d:分隔符,按照指定分隔符分割列
# 测试命令 | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# cut -f 2 student.txt | |
Name | |
Moe | |
Jalen | |
Jones | |
[root@core-pods-3 ~]# cut -f 2,4 student.txt | |
Name Mark | |
Moe 90 | |
Jalen 80 | |
Jones 95 | |
[root@core-pods-3 ~]# cut -d ":" -f 1,3 /etc/passwd | |
root:0 | |
bin:1 | |
daemon:2 | |
adm:3 | |
lp:4 | |
sync:5 | |
shutdown:6 | |
halt:7 | |
mail:8 | |
operator:11 | |
games:12 | |
ftp:14 | |
nobody:99 | |
systemd-bus-proxy:999 | |
systemd-network:192 | |
dbus:81 | |
polkitd:998 | |
tss:59 | |
sshd:74 | |
postfix:89 | |
chrony:997 | |
nginx:996 | |
linzhiling:1000 | |
[root@core-pods-3 ~]# |
# 实际使用举例:提取 passwd 里普通用户并批量删除 | |
[root@core-pods-3 ~]# useradd user1 | |
[root@core-pods-3 ~]# useradd user2 | |
[root@core-pods-3 ~]# useradd user3 | |
[root@core-pods-3 ~]# cat /etc/passwd | |
... ... | |
user1:x:1001:1001::/home/user1:/bin/bash | |
user2:x:1002:1002::/home/user2:/bin/bash | |
user3:x:1003:1003::/home/user3:/bin/bash | |
[root@core-pods-3 ~]# | |
[root@core-pods-3 ~]# cat /etc/passwd | grep /bin/bash | |
root:x:0:0:root:/root:/bin/bash | |
linzhiling:x:1000:1000::/home/linzhiling:/bin/bash | |
user1:x:1001:1001::/home/user1:/bin/bash | |
user2:x:1002:1002::/home/user2:/bin/bash | |
user3:x:1003:1003::/home/user3:/bin/bash | |
[root@core-pods-3 ~]# cat /etc/passwd | grep /bin/bash | grep -v root | |
linzhiling:x:1000:1000::/home/linzhiling:/bin/bash | |
user1:x:1001:1001::/home/user1:/bin/bash | |
user2:x:1002:1002::/home/user2:/bin/bash | |
user3:x:1003:1003::/home/user3:/bin/bash | |
[root@core-pods-3 ~]# cat /etc/passwd | grep /bin/bash | grep -v root | cut -d ":" -f 1 | |
linzhiling | |
user1 | |
user2 | |
user3 | |
[root@core-pods-3 ~]# |
# cut 命令使用局限 | |
# 例子:判断磁盘根分区使用率,如果超过 80%,设置告警 | |
[root@core-pods-3 ~]# df -h | |
Filesystem Size Used Avail Use% Mounted on | |
devtmpfs 494M 0 494M 0% /dev | |
tmpfs 504M 0 504M 0% /dev/shm | |
tmpfs 504M 26M 479M 6% /run | |
tmpfs 504M 0 504M 0% /sys/fs/cgroup | |
/dev/sda2 22G 3.4G 17G 17% / | |
/dev/sda1 380M 257M 104M 72% /boot | |
tmpfs 101M 0 101M 0% /run/user/0 | |
[root@core-pods-3 ~]# df -h | grep "sda2" # 精确到 sda2 行 | |
/dev/sda2 22G 3.4G 17G 17% / | |
[root@core-pods-3 ~]# df -h | grep "sda2" | cut -f 5 # 因为每列之间不是 tab 分隔,都是空格分隔,所以下面输出失效 | |
/dev/sda2 22G 3.4G 17G 17% / | |
[root@core-pods-3 ~]# df -h | grep "sda2" | cut -d " " -f 5 # 即便定义了以空格分隔,也是有问题的,因为我们只定义了 1 个空格,所以 cut 会认为我们以 1 个空格分隔,而因为第五列是空格,所以输出空格 | |
[root@core-pods-3 ~]# | |
# 上面这种情况可以使用后面的 awk 命令,但是 awk 远比 cut 复杂,能实现优先使用 cut |
# printf 命令
printf 严格上来说不算是字符截取命令,因为这个是 awk 命令里最基本的输出指令,所以先介绍 printf 命令,printf 是格式化输出命令,命令是: printf '输出类型输出格式' 输出内容
:
- 输出类型:
- % ns:输出字符串。n 是数字指代输出几个字符
- % ni:输出整数。n 是数字指代输出几个数字
- %m.nf:输出浮点数。m 和 n 是数字,指代输出的整数位数和小数位数。如 %8.2f 代表共输出 8 位数,其中 2 位是小数,6 位是整数。
- 输出格式:
- \a:输出警告声音
- \b:输出退格键,也就是 Backspace 键
- \f:清除屏幕
- \n:换行
- \r:回车,也就是 Enter 键
- \t:水平输出退格键,也就是 Tab 键
- \v:垂直输出退格键,也就是 Tab 键
- 在 awk 命令的输出中支持 print 和 printf 命令
- print:print 会在每个输出之后自动加入一个换行符(Linux 默认没有 print 命令)
- printf:printf 是标准格式输出命令,并不会自动加入换行符,如果需要换行,需要手工加入换行符
# 练习 | |
[root@core-pods-3 ~]# printf %s 1 2 3 4 5 6 | |
123456[root@core-pods-3 ~]# | |
[root@core-pods-3 ~]# printf %s %s %s 1 2 3 4 5 6 | |
%s%s123456[root@core-pods-3 ~]# | |
[root@core-pods-3 ~]# printf '%s %s %s' 1 2 3 4 5 6 | |
1 2 34 5 6[root@core-pods-3 ~]# printf '%s %s %s\n' 1 2 3 4 5 6 | |
1 2 3 | |
4 5 6 | |
[root@core-pods-3 ~]# printf '%s' student.txt | |
student.txt[root@core-pods-3 ~]# | |
[root@core-pods-3 ~]# cat student.txt | printf '%s' | |
[root@core-pods-3 ~]# printf '%s' $(cat student.txt) | |
IDNamegenderMark1MoeF902JalenM803JonesM95[root@core-pods-3 ~]# | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# printf '%s\t %s\t %s\t %s\t \n' $(cat student.txt) | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# |
# awk 命令
# 说明
awk 比较复杂,其实 awk 的一些复杂命令都可以通过 shell 脚本实现,而且更简单。awk 叫做 awk 编程更贴切,awk 更像是一门语言,awk 也是做列截取,并且可以做到 cut 能做的所有事情,也可以做 cut 做不到事情,比如:
[root@core-pods-3 ~]# df -h | |
Filesystem Size Used Avail Use% Mounted on | |
devtmpfs 494M 0 494M 0% /dev | |
tmpfs 504M 0 504M 0% /dev/shm | |
tmpfs 504M 26M 479M 6% /run | |
tmpfs 504M 0 504M 0% /sys/fs/cgroup | |
/dev/sda2 22G 3.4G 17G 17% / | |
/dev/sda1 380M 257M 104M 72% /boot | |
tmpfs 101M 0 101M 0% /run/user/0 | |
[root@core-pods-3 ~]# df -h | cut -d " " -f 5 | |
[root@core-pods-3 ~]# |
# 命令格式
awk ' 条件 1 {动作 1} 条件 2 {动作 2}...' 文件名
- 条件(Pattern):一般使用关系表达式作为条件
- x>10:判断变量 x 是否大于 10
- x>=10:大于等于
- x<=10:小于等于
- 动作(Action):
- 格式化输出
- 流程控制语句
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# awk '{printf $2 "\t" $4 "\n"}' student.txt # 指定格式 | |
Name Mark | |
Moe 90 | |
Jalen 80 | |
Jones 95 | |
[root@core-pods-3 ~]# awk '{printf $2 $4}' student.txt # 不指定格式 | |
NameMarkMoe90Jalen80Jones95[root@core-pods-3 ~]# | |
[root@core-pods-3 ~]# | |
# 查看根分区占比 | |
[root@core-pods-3 ~]# df -h | awk '{print $1 "\t" $5 "\t" $6}' | |
Filesystem Use% Mounted | |
devtmpfs 0% /dev | |
tmpfs 0% /dev/shm | |
tmpfs 6% /run | |
tmpfs 0% /sys/fs/cgroup | |
/dev/sda2 17% / | |
/dev/sda1 72% /boot | |
tmpfs 0% /run/user/0 | |
[root@core-pods-3 ~]# df -h | awk '{print $1 "\t" $3}' | |
Filesystem Used | |
devtmpfs 0 | |
tmpfs 0 | |
tmpfs 26M | |
tmpfs 0 | |
/dev/sda2 3.4G | |
/dev/sda1 257M | |
tmpfs 0 | |
[root@core-pods-3 ~]# | |
# 提取 sda2 分区使用占比 | |
[root@core-pods-3 ~]# df -h | grep sda2 | |
/dev/sda2 22G 3.4G 17G 17% / | |
[root@core-pods-3 ~]# df -h | grep sda2 | awk '{print $5}' | |
17% | |
[root@core-pods-3 ~]# df -h | grep sda2 | awk '{print $5}' | cut -d "%" -f 1 | |
17 | |
[root@core-pods-3 ~]# | |
# BEGIN,在所有数据读取之前处理这个命令 | |
[root@core-pods-3 ~]# awk 'BEGIN {print "This is a test transcript!"} {print $2 "\t" $4}' student.txt | |
This is a test transcript! # 这里输出开始打印语句 | |
Name Mark | |
Moe 90 | |
Jalen 80 | |
Jones 95 | |
[root@core-pods-3 ~]# cat /etc/passwd | grep "/bin/bash" | awk '{FS=":"} {print $1 "\t" $3}' | |
root:x:0:0:root:/root:/bin/bash # 这里可以发现第一行未处理,因为 awk 从第 2 行开始执行 | |
linzhiling 1000 | |
user1 1001 | |
user2 1002 | |
user3 1003 | |
[root@core-pods-3 ~]# | |
[root@core-pods-3 ~]# cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN {FS=":"} {print $1 "\t" $3}' | |
root 0 # 添加 BEGIN 命令后发现第 1 行执行了 | |
linzhiling 1000 | |
user1 1001 | |
user2 1002 | |
user3 1003 | |
[root@core-pods-3 ~]# | |
# END 使用和 BEGIN 相反,在末尾操作 | |
[root@core-pods-3 ~]# cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN {FS=":"} END{print "This is the end!"} {print $1 "\t" $3}' | |
root 0 | |
linzhiling 1000 | |
user1 1001 | |
user2 1002 | |
user3 1003 | |
This is the end! # 这里输出结尾打印语句 | |
[root@core-pods-3 ~]# | |
# 读取 student 文件,统计分数大于等于 90 的同学姓名 | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# cat student.txt | grep -v Name | awk '$4 >= 90 {printf $2 "\n"}' | |
Moe | |
Jones | |
[root@core-pods-3 ~]# |
# sed 命令
# 说明
其实 sed 命令并不是一个截取命令,它是一种几乎包括在所有 unix 平台(包括 linux)的轻量级流编辑器。sed 主要是用来将数据进行选取、替换、删除、新增的命令。这有点类似 vi 编辑,但是 vi 不能直接得到命令的最终结果,而是要将命令的结果先输出到一个文件里,才能使用 vi 进行二次编辑。而 sed 不仅可以修改文件,还可以从管道符接收数据源,即支持管道符操作。写 shell 脚本时候 sed 是一个比较重要的命令,但是 sed 远没有 awk 强大。
# 格式
sed [选项] '[动作]' 文件名
- 选项
- -n:一般 sed 命令会把所有数据都输出到屏幕,如果加入此选择,则只会把经过 sed 命令处理的行输出到屏幕
- -e:允许对输入数据应用多条 sed 命令编辑
- -i:用 sed 的修改结果直接修改读取数据的文件,而不是由屏幕输出
- 动作
- a \:追加,在当前行后添加一行或多行。添加多行时,除最后一行外,每行末尾需要用 “\” 代表数据未完结
- c \:行替换,用 c 后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需用 “\” 代表数据未完结。
- i \:插入,在当前行前插入一行或多行。插入多行时,除最后一行外,每行末尾需要用 “\” 代表数据未完结
- d:删除,删除指定的行
- p:打印,输出指定的行
- s:字串替换,用一个字符串替换另外一个字符串。格式为 “行范围 s / 旧字串 / 新字串 /g” (和 vim 中的替换格式类似)
[root@core-pods-3 ~]# ls \ # 反斜杠代表命令未完结 | |
> /root | |
student.txt | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed '2p' student.txt # 发现输出第 2 行后又重复打印了表格 | |
ID Name gender Mark | |
1 Moe F 90 | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed -n '2p' student.txt # 使用 - n 只输出经过 sed 处理过的行 | |
1 Moe F 90 | |
[root@core-pods-3 ~]# df -h | sed -n '2p' # sed 可以配合管道符一起使用 | |
devtmpfs 494M 0 494M 0% /dev | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed '2,4d' student.txt # 删除第 2 行到第 4 行,输出剩余行 | |
ID Name gender Mark | |
[root@core-pods-3 ~]# cat student.txt # 上述不改变文件内容 | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed '2,3d' student.txt # 删除第 2 行到第 3 行,输出剩余行 | |
ID Name gender Mark | |
3 Jones M 95 | |
[root@core-pods-3 ~]# cat student.txt # 上述不改变文件内容 | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed '2a hello' student.txt # 在第 2 行后加 hello | |
ID Name gender Mark | |
1 Moe F 90 | |
hello | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed '2i hello world' student.txt # 在第 2 行前加 hello world | |
ID Name gender Mark | |
hello | |
world | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed '2c No such person' student.txt # 替换第 2 行 | |
ID Name gender Mark | |
No such person | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed '3s/80/60/g' student.txt # 第 3 行 80 替换为 60 | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 60 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 80 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed -i '3s/80/65/g' student.txt # 第 3 行 80 替换为 65 并写入文件 | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 65 | |
3 Jones M 95 | |
[root@core-pods-3 ~]# sed -e's/Moe/Moe1/g;s/Jalen//g;s/Jones/Bella/g' student.txt # 多条 sed 替换命令 | |
ID Name gender Mark | |
1 Moe1 F 90 | |
2 M 65 | |
3 Bella M 95 | |
[root@core-pods-3 ~]# cat student.txt | |
ID Name gender Mark | |
1 Moe F 90 | |
2 Jalen M 65 | |
3 Jones M 95 |
# 字符处理命令
# 排序命令 sort
sort [选项] 文件名
- 选项
- -f:忽略大小写
- -n:以数值型进行排序。(因为默认使用字符串型排序)
- -r:反向排序
- -t:指定分隔符,默认的分隔符是制表符
- -k n [,m]:按照指定的字段范围排序。从第 n 个字段开始,到第 m 个字段结束(默认到行尾)
# 练习 | |
[root@core-pods-3 ~]# sort /etc/passwd # 按字母顺序排序 | |
[root@core-pods-3 ~]# sort -r /etc/passwd # 按字母顺序倒序排序 | |
[root@core-pods-3 ~]# sort -t ":" -k 3,3 /etc/passwd # 指定分隔符是:,用第 3 个字段开头,第 3 个字段结尾排序,就是只用第 3 个字段排序 (注意这里没有把 uid 当成数字来排序,而是当成字符串来排序的) | |
[root@core-pods-3 ~]# sort -n -t ":" -k 3,3 /etc/passwd # 和上面类似,这里当成数值来排序 |
# 统计命令 wc
wc [选项] 文件名
- 选项
- -l:只统计行数
- -w:只统计单词数
- -m:只统计字符数
# 练习 | |
[root@core-pods-3 ~]# wc /etc/passwd | |
26 48 1244 /etc/passwd | |
[root@core-pods-3 ~]# wc -l /etc/passwd | |
26 /etc/passwd | |
[root@core-pods-3 ~]# df -h | wc -l | |
8 | |
[root@core-pods-3 ~]# |
# 条件判断
- 按照文件类型进行判断
测试选项 | 作用 |
---|---|
-b 文件 | 判断该文件是否存在,并且是否为块设备文件(是块设备文件为真) |
-c 文件 | 判断该文件是否存在,并且是否为字符设备文件(是字符设备文件为真) |
-d 文件 | 判断该文件是否存在,并且是否为目录文件(是目录为真) |
-e 文件 | 判断文件是否存在(存在为真) |
-f 文件 | 判断该文件是否存在,并且是否为普通文件(是普通文件为真) |
-L 文件 | 判断该文件是否存在,并且是否为符号链接文件(是符号链接文件为真) |
-p 文件 | 判断该文件是否存在,并且是否为管道文件(是管道文件为真) |
-s 文件 | 判断该文件是否存在,并且是否为非空(非空为真) |
-S 文件 | 判断该文件是否存在,并且是否为套接字文件(是套接字文件为真) |
两种判断格式
[root@core-pods-3 ~]# ls | |
student.txt | |
[root@core-pods-3 ~]# test -e student.txt # 方式 1 | |
[root@core-pods-3 ~]# echo $? | |
0 | |
[root@core-pods-3 ~]# test -e xxx.txt | |
[root@core-pods-3 ~]# echo $? | |
1 | |
[root@core-pods-3 ~]# [-e student.txt] # 方式 2,脚本中常用方式 | |
[root@core-pods-3 ~]# echo $? | |
0 | |
[root@core-pods-3 ~]# [ -e xxx.txt ] | |
[root@core-pods-3 ~]# echo $? | |
1 | |
# 判断目录并输出 | |
[root@core-pods-3 ~]# [ -d /root ] && echo "yes" || echo "no" | |
yes | |
[root@core-pods-3 ~]# [ -d /jalen ] && echo "yes" || echo "no" | |
no | |
[root@core-pods-3 ~]# [ -f /root/student.txt ] && echo 'yes' || echo 'no' | |
yes |
- 按照文件权限进行判断
测试选项 | 作用 |
---|---|
-r 文件 | 判断该文件是否存在,并且是否该文件拥有读权限(有读权限为真) |
-w 文件 | 判断该文件是否存在,并且是否该文件拥有写权限(有写权限为真) |
-x 文件 | 判断该文件是否存在,并且是否该文件拥有执行权限(有执行权限为真) |
-u 文件 | 判断该文件是否存在,并且是否该文件拥有 SUID 权限(有 SUID 权限为真) |
-g 文件 | 判断该文件是否存在,并且是否该文件拥有 SGID 权限(有 SGID 权限为真) |
-k 文件 | 判断该文件是否存在,并且是否该文件拥有 SBit 权限(有 SBit 权限为真) |
# 注意 u,g,o 三个里面只要有一个有 w 权限,就会返回 yes,r 和 x 类似,不会细分 | |
[root@core-pods-3 ~]# ll | |
total 4 | |
-rw-r--r-- 1 root root 57 Aug 2 03:14 student.txt | |
[root@core-pods-3 ~]# [ -w student.txt ] && echo "yes" || echo "no" | |
yes | |
[root@core-pods-3 ~]# [ -r student.txt ] && echo "yes" || echo "no" | |
yes | |
[root@core-pods-3 ~]# [ -x student.txt ] && echo "yes" || echo "no" | |
no | |
[root@core-pods-3 ~]# |
- 两个文件之间进行比较
测试选项 | 作用 |
---|---|
文件 1 -nt 文件 2 | 判断文件 1 的修改时间是否比文件 2 的新(如果新则为真) |
文件 1 -ot 文件 2 | 判断文件 1 的修改时间是否比文件 2 的老(如果老则为真) |
文件 1 -ef 文件 2 | 判断文件 1 是否和文件 2 的 Inode 号一致,可以理解为两个文件是否为同一个文件。这个判断用于判断硬链接是很好的方法 |
[root@core-pods-3 ~]# ll | |
total 4 | |
-rw-r--r-- 1 root root 57 Aug 2 03:14 student.txt | |
-rw-r--r-- 1 root root 0 Aug 2 04:06 teacher.txt | |
[root@core-pods-3 ~]# ln /root/student.txt/tmp/stu.txt # 创建硬链接 | |
# 硬链接直接通过 ll 命令看不出来 | |
[root@core-pods-3 ~]# ll | |
total 4 | |
-rw-r--r-- 2 root root 57 Aug 2 03:14 student.txt | |
-rw-r--r-- 1 root root 0 Aug 2 04:06 teacher.txt | |
[root@core-pods-3 tmp]# ll /tmp/ | |
total 4 | |
-rw-r--r-- 2 root root 57 Aug 2 03:14 stu.txt | |
# 相反,软连接通过 ll 可以看出来 | |
[root@core-pods-3 tmp]# ll /etc/ | |
total 1448 | |
... ... | |
lrwxrwxrwx 1 root root 22 Jul 7 03:03 grub2.cfg -> ../boot/grub2/grub.cfg | |
lrwxrwxrwx 1 root root 11 Jul 7 03:02 init.d -> rc.d/init.d | |
lrwxrwxrwx 1 root root 21 Jul 7 03:02 os-release -> ../usr/lib/os-release | |
... ... | |
# 硬链接通过查 Inode 确认,如下,都是 286 | |
[root@core-pods-3 ~]# ll -i student.txt | |
286 -rw-r--r-- 2 root root 57 Aug 2 03:14 student.txt | |
[root@core-pods-3 ~]# ll -i /tmp/stu.txt | |
286 -rw-r--r-- 2 root root 57 Aug 2 03:14 /tmp/stu.txt | |
# 程序里判断 Inode 号 | |
[root@core-pods-3 ~]# [ /root/student.txt -ef /tmp/stu.txt ] && echo "yes" || echo "no" | |
yes |
- 两个整数之间比较
测试选项 | 作用 |
---|---|
整数 1 -eq 整数 2 | 判断整数 1 和整数 2 相等(相等为真) |
整数 1 -ne 整数 2 | 判断整数 1 和整数 2 是否不相等(不相等为真) |
整数 1 -gt 整数 2 | 判断整数 1 是否大于整数 2(大于为真) |
整数 1 -lt 整数 2 | 判断整数 1 是否小于整数 2(小于为真) |
整数 1 -ge 整数 2 | 判断整数 1 是否大于等于整数 2(大于等于为真) |
整数 1 -le 整数 2 | 判断整数 1 是否小于等于整数 2(小于等于为真) |
# 练习 | |
[root@core-pods-3 ~]# [ 10 -eq 10 ] && echo "yes" || echo "no" | |
yes | |
[root@core-pods-3 ~]# [ 10 -eq 11 ] && echo "yes" || echo "no" | |
no | |
[root@core-pods-3 ~]# [ 10 -gt 11 ] && echo "yes" || echo "no" | |
no | |
[root@core-pods-3 ~]# [ 10 -gt 9 ] && echo "yes" || echo "no" | |
yes |
- 字符串的判断
测试选项 | 作用 |
---|---|
-z 字符串 | 判断字符串是否为空(为空返回真) |
-n 字符串 | 判断字符串是否为非空(非空返回真) |
字串 1== 字串 2 | 判断字符串 1 是否和字符串 2 相等(相等返回真),单等于号其实在 shell 中也可以,但是为了不和赋值操作搞混淆,推荐使用双等于号 |
字串 1!= 字串 2 | 判断字符串 1 是否和字符串 2 不相等(不相等返回真) |
[root@core-pods-3 ~]# name=jalen | |
[root@core-pods-3 ~]# echo $name | |
jalen | |
[root@core-pods-3 ~]# [ -z "$name" ] && echo "yes" || echo "no" | |
no | |
[root@core-pods-3 ~]# [ -n "$name" ] && echo "yes" || echo "no" | |
yes | |
[root@core-pods-3 ~]# name1=jalen | |
[root@core-pods-3 ~]# echo $name1 | |
jalen | |
[root@core-pods-3 ~]# [ "$name" != "$name1" ] && echo "yes" || echo "no" | |
no | |
[root@core-pods-3 ~]# [ "$name" == "$name1" ] && echo "yes" || echo "no" | |
yes | |
[root@core-pods-3 ~]# |
- 多重条件判断
测试选项 | 作用 |
---|---|
判断 1 -a 判断 2 | 逻辑与,判断 1 和判断 2 都成立,最终的结果才为真 |
判断 1 -o 判断 2 | 逻辑或,判断 1 和判断 2 有一个成立,最终的结果就为真 |
! 判断 | 逻辑非,使原始的判断式取反 |
[root@core-pods-3 ~]# aa=11 | |
[root@core-pods-3 ~]# [ -n "$aa" -a "$aa" -gt 23 ] && echo "yes" || echo "no" | |
no | |
[root@core-pods-3 ~]# aa=24 | |
[root@core-pods-3 ~]# [ -n "$aa" -a "$aa" -gt 23 ] && echo "yes" || echo "no" | |
yes |
# 流程控制
# if 语句
单分支 if 条件语句
if [ 条件判断式 ];then
程序
fi
或者
if [ 条件判断式 ]
then
程序
fi
单分支条件语句注意以下几点:
- if 语句使用 fi 结尾,和一般语言使用大括号结尾不同
- [条件判断式] 就是使用 test 命令判断,所以中括号和条件判断式之间必须有空格
- then 后面跟符合条件之后执行的程序,可以放在
[之后]
,用 “;” 分割。也可以换行写入,就不需要 “;” 了
例子:判断分区使用率
[root@core-pods-3 ~]# vi test.sh
#!/bin/bash
# Statistics on root partition usage
# Author: Jalen Chu
rate=$(df -h | grep "/dev/sda2" | awk '{print $5}' | cut -d "%" -f 1)
if [ $rate -ge 15 ]
then
echo "Warning! /dev/sda2 is full! now is ${rate}%"
fi
[root@core-pods-3 ~]# chmod u+x test.sh
[root@core-pods-3 ~]# ./test.sh
Warning! /dev/sda2 is full! now is 17%
双分支 if 条件语句
if [ 条件判断式 ]
then
条件成立时,执行的程序
else
条件不成立时,执行的另一个程序
fi
例子 1:备份 mysql 数据库
# 安装 mariadb
[root@core-pods-3 ~]# yum install -y mysql
[root@core-pods-3 ~]# yum install -y mariadb-server
[root@core-pods-3 ~]# yum list installed | grep mariadb
[root@core-pods-3 ~]# systemctl start mariadb
[root@core-pods-3 ~]# mysql -u root -p # 默认 mariadb root 账号无密码,直接回车登录
MariaDB [(none)]> show databases;
MariaDB [(none)]> create database demo;
MariaDB [(none)]> use demo;
MariaDB [demo]> create table test(id integer , name char(64), age integer);
MariaDB [demo]> insert test values(1, "Jalen Chu", 18);
MariaDB [demo]> select * from test;
MariaDB [demo]> exit
[root@core-pods-3 ~]# ls /var/lib/mysql/demo/ # mysql 数据存储位置 - 刚刚建的 demo 数据库
[root@core-pods-3 ~]# vi test.sh # 写备份脚本
#!/bin/bash
# Description: mysql database backup
# Author: Jalen Chu
ntpdate asia.pool.ntp.org &>/dev/null
date=$(date +%y%m%d)
size=$(du -sh /var/lib/mysql)
if [ -d /tmp/dbbak ]
then
echo "Date: $date!" > /tmp/dbbak/dbinfo.txt
echo "Data size: $size" >> /tmp/dbbak/dbinfo.txt
cd /tmp/dbbak
tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &>/dev/null
rm -rf /tmp/dbbak/dbinfo.txt
else
mkdir /tmp/dbbak
echo "Date: $date!" > /tmp/dbbak/dbinfo.txt
echo "Data size: $size" >> /tmp/dbbak/dbinfo.txt
cd /tmp/dbbak
tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &>/dev/null
rm -rf /tmp/dbbak/dbinfo.txt
fi
[root@core-pods-3 ~]# chmod u+x test.sh
[root@core-pods-3 ~]# ./test.sh
[root@core-pods-3 ~]# ls /tmp/dbbak/
mysql-lib-220804.tar.gz
例子 2:判断 apache 是否启动
# 注意这里没有用 ps aux|grep httpd,是因为有时候 apache 进程还在但是不能访问了,比如恶意流量攻击
# 这里也没有用 netstat -tlun,和上面同样问题,不能确定你的 apache 是否能够正常连接,只能确认 apache 启动,并且也可能其他服务占用了 80 端口
# 最有效的是通过 nmap,远程扫描工具,扫描指定服务器开启的端口,如果能正确连接 apache,会返回 open
[root@core-pods-3 ~]# yum install nmap
[root@core-pods-3 ~]# yum install -y httpd
[root@core-pods-3 ~]# systemctl start httpd
[root@core-pods-3 ~]# service httpd status
[root@core-pods-3 ~]# /usr/sbin/httpd -k start # 测试启动
[root@core-pods-3 ~]# nmap -sT 104.225.149.222 | grep tcp | grep http | awk '{print $2}' # 80 服务处于 open 状态
[root@core-pods-3 ~]# vi test.sh
#!/bin/bash
# Description: Check apache process start
# Author: Jalen Chu
port=$(nmap -sT 104.225.149.222 | grep tcp | grep http | awk '{print $2}' )
if [ "$port" == "open" ]
then
echo "$(date) httpd is ok!" >> /tmp/autostart-apache.log
else
/usr/sbin/httpd -k start &>/dev/null
echo "$(date) restart httpd!!" >> /tmp/autostart-apache.log
fi
[root@core-pods-3 ~]# ./test.sh
[root@core-pods-3 ~]# cat /tmp/autostart-apache.log
Thu Aug 4 02:39:02 EDT 2022 httpd is ok!
[root@core-pods-3 ~]# /usr/sbin/httpd -k stop # 关闭
[root@core-pods-3 ~]# nmap -sT 104.225.149.222 # 看不到 80 端口 open 了
[root@core-pods-3 ~]# ./test.sh
[root@core-pods-3 ~]# cat /tmp/autostart-apache.log
Thu Aug 4 02:39:02 EDT 2022 httpd is ok!
Thu Aug 4 02:42:24 EDT 2022 restart httpd!!
多分支 if 条件语句
if [ 条件判断式1 ]
then
当条件判断式1成立时,执行程序1
elif [ 条件判断式2 ]
then
当条件判断式2成立时,执行程序2
... ...
else
当所有条件都不成立时,最后执行此程序
fi
例子:判断输入的文件类型
[root@core-pods-3 ~]# vi test.sh
#!/bin/bash
# Description: Check user input
# Author: Jalen Chu
read -p "Please input a filename: " file
if [ -z "$file" ]
then
echo "Error, please input a filename."
exit 1
elif [ ! -e "$file" ]
then
echo "Your input is not a file!"
exit 2
elif [ -f "$file" ]
then
echo "$file is a regulare file!"
elif [ -d "$file" ]
then
echo "$file is a directory!"
else
echo "$file is an other file!"
fi
[root@core-pods-3 ~]# ./test.sh
Please input a filename:
Error, please input a filename.
[root@core-pods-3 ~]# ./test.sh
Please input a filename: xxxx
Your input is not a file!
[root@core-pods-3 ~]# ./test.sh
Please input a filename: /root
/root is a directory!
[root@core-pods-3 ~]# ./test.sh
Please input a filename: /etc/passwd
/etc/passwd is a regulare file!
# case 语句
多分支 case 条件语句,case 语句和 if...elif...else 语句一样都是多分支条件语句,不过和 if 多分支条件语句不同的是,case 语句只能判断一种条件关系,而 if 语句可以判断多种条件关系。格式:
case $变量名 in | |
"值1") | |
如果变量的值等于值1,则执行顺序1 | |
;; | |
"值2") | |
如果变量的值等于值2,则执行顺序2 | |
;; | |
... 省略其他分支 ... | |
*) | |
如果变量的值都不是以上的值,则执行此程序 | |
;; | |
esac |
例子:检查用户输入
[root@core-pods-3 ~]# vi test.sh | |
#!/bin/bash | |
# Description: Check user input | |
# Author: Jalen Chu | |
read -p "Please choose yes/no:" -t 30 cho | |
case $cho in | |
"yes") | |
echo "Your choose is yes!" | |
;; | |
"no") | |
echo "Your choose is no!" | |
;; | |
*) | |
echo "Your choose is error!" | |
;; | |
esac | |
[root@core-pods-3 ~]# ./test.sh | |
Please choose yes/no:xxx | |
Your choose is error! | |
[root@core-pods-3 ~]# ./test.sh | |
Please choose yes/no:yes | |
Your choose is yes! | |
[root@core-pods-3 ~]# ./test.sh | |
Please choose yes/no:no | |
Your choose is no! |
# for 循环
语法一
for 变量 in 值1 值2 值3...
do
程序
done
例子 1:打印时间
[root@core-pods-3 ~]# vi test.sh
#!/bin/bash
# Description: Print time
# Author: Jalen Chu
for time in morning noon afternoon evening
do
echo "This time is $time!"
done
[root@core-pods-3 ~]# ./test.sh
This time is morning!
This time is noon!
This time is afternoon!
This time is evening!
[root@core-pods-3 ~]#
例子 2:批量解压文件
[root@core-pods-3 ~]# vi test.sh
#!/bin/bash
# Description: De compress files
# Author: Jalen Chu
cd /lamp
ls *.tar.gz > ls.log
for i in $(cat ls.log)
do
tar -zxf $i &>/dev/null
done
rm -rf /lamp/ls.log
语法二
for((初始值;循环控制条件;变量变化))
do
程序
done
例子 1:从 1 加到 100
#!/bin/bash
# Description: 1+2+3+...+99+100
# Author: Jalen Chu
sum=0
for((i=1;i<=100;i=i+1))
do
sum=$(($sum+$i))
done
echo "The sum of 1+2+3+...+100 is $sum"
[root@core-pods-3 ~]# ./test.sh
The sum of 1+2+3+...+100 is 5050
[root@core-pods-3 ~]#
例子 2:批量添加指定数量的用户
[root@core-pods-3 ~]# vi test.sh
#!/bin/bash
# Description: Batch add users
# Author: Jalen Chu
read -p "Please input username: " -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
[root@core-pods-3 ~]# ./test.sh
Please input username: Jalen
Please input the number of users: 5
Please input the password of users: 123456
[root@core-pods-3 ~]# cat /etc/passwd | grep Jalen
Jalen1:x:1004:1004::/home/Jalen1:/bin/bash
Jalen2:x:1005:1005::/home/Jalen2:/bin/bash
Jalen3:x:1006:1006::/home/Jalen3:/bin/bash
Jalen4:x:1007:1007::/home/Jalen4:/bin/bash
Jalen5:x:1008:1008::/home/Jalen5:/bin/bash
[root@core-pods-3 ~]#
# while 循环和 until 循环
# while 循环
while 循环是不定循环,也称作条件循环。只要条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。这就和 for 的固定循环不一样了。格式:
while [ 条件判断式 ] | |
do | |
程序 | |
done |
例子 1:从 1 加到 100
[root@core-pods-3 ~]# vi test.sh | |
#!/bin/bash | |
# Description: 1+2+3+...+100 | |
# Author: Jalen Chu | |
i=1 | |
s=0 | |
while [ $i -le 100 ] | |
do | |
s=$(($s+$i)) | |
i=$(($i+1)) | |
done | |
echo $s | |
[root@core-pods-3 ~]# ./test.sh | |
5050 |
# until 循环
until 循环,和 while 循环相反,until 循环时只要条件判断式不成立则进行循环,并执行循环程序。一旦循环条件成立,则终止循环。
[root@core-pods-3 ~]# vi test.sh | |
#!/bin/bash | |
# Description: 1+2+3+...+100 | |
# Author: Jalen Chu | |
i=1 | |
s=0 | |
until [ $i -gt 100 ] | |
do | |
s=$(($s+$i)) | |
i=$(($i+1)) | |
done | |
echo $s | |
[root@core-pods-3 ~]# ./test.sh | |
5050 |
# 结束语
整体大概 10 节课左右,总算看完了,虽然有些本来就会一些,但也有许多新 get 的知识点,谢谢沈超老师~
Shell 的学习主要还是多练习,包括它的语法结构,以及一些变量、程序语句的使用,不过目前实际工作中用到的场景不多,也许后面会忘记,暂且记录下,方便若干时间后查缺补漏。
# 课程视频地址
请点击此处