该系列是基于牛客Shell题库,针对具体题目进行查漏补缺,学习相应的命令。
刷题链接:牛客题霸-Shell篇。
该系列文章都放到专栏下,专栏链接为:《专栏:Shell》。欢迎关注专栏~
本文知识预告:
- 本文复习了
awk
、grep
、wc
、echo
命令的相关用法; - 然后给出了四种题目的解决方案。
SHELL32 netstat练习4-输出和3306端口建立连接总的各个状态的数目
假设netstat命令运行的结果我们存储在nowcoder.txt里,格式如下:
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:6160 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 172.16.56.200:41856 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:49822 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:49674 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:42316 172.16.34.143:3306 ESTABLISHED
tcp 0 0 172.16.56.200:44076 172.16.240.74:6379 ESTABLISHED
tcp 0 0 172.16.56.200:49656 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:58248 100.100.142.4:80 TIME_WAIT
tcp 0 0 172.16.56.200:50108 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:41944 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:35548 100.100.32.118:80 TIME_WAIT
tcp 0 0 172.16.56.200:39024 100.100.45.106:443 TIME_WAIT
tcp 0 0 172.16.56.200:41788 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:58260 100.100.142.4:80 TIME_WAIT
tcp 0 0 172.16.56.200:41812 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:41854 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:58252 100.100.142.4:80 TIME_WAIT
tcp 0 0 172.16.56.200:49586 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:41754 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:50466 120.55.222.235:80 TIME_WAIT
tcp 0 0 172.16.56.200:38514 100.100.142.5:80 TIME_WAIT
tcp 0 0 172.16.56.200:49832 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:52162 100.100.30.25:80 ESTABLISHED
tcp 0 0 172.16.56.200:50372 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:50306 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:49600 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:41908 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:60292 100.100.142.1:80 TIME_WAIT
tcp 0 0 172.16.56.200:37650 100.100.54.133:80 TIME_WAIT
tcp 0 0 172.16.56.200:41938 172.16.34.144:3306 ESTABLISHED
tcp 0 0 172.16.56.200:49736 172.16.0.24:3306 ESTABLISHED
tcp 0 0 172.16.56.200:41890 172.16.34.144:3306 ESTABLISHED
udp 0 0 127.0.0.1:323 0.0.0.0:*
udp 0 0 0.0.0.0:45881 0.0.0.0:*
udp 0 0 127.0.0.53:53 0.0.0.0:*
udp 0 0 172.16.56.200:68 0.0.0.0:*
udp6 0 0 ::1:323 :::*
raw6 0 0 :::58 :::* 7
现在需要你输出和本机3306端口建立连接的各个状态的数目,按照以下格式输出
- TOTAL_IP表示建立连接的ip数目
- TOTAL_LINK表示建立连接的总数目
TOTAL_IP 3
ESTABLISHED 20
TOTAL_LINK 20
题目要求的有三个要计算的,这才算是名副其实的困难题吧🤣🤣
相关命令学习
awk
:文本和数据进行处理的编程语言
awk
命令来自于三位创始人”Alfred Aho,Peter Weinberger, Brian Kernighan “的姓氏缩写,其功能是用于对文本和数据进行处理的编程语言。使用awk
命令可以让用户自定义函数或正则表达式对文本内容进行高效管理,与sed
、grep
并称为Linux系统中的文本三剑客。
语法格式:awk 参数 文件
常用参数:
参数 | 功能 |
---|---|
-F | 指定输入时用到的字段分隔符 |
-v | 自定义变量 |
-f | 从脚本中读取awk 命令 |
-m | 对val 值设置内在限制 |
常用的awk
内置变量:
awk
语法由一系列条件和动作组成,在花括号内可以有多个动作,多个动作之间用分号分隔,在多个条件和动作之间可以有若干空格,也可以没有。
变量名称 | 说明 |
---|---|
FILENAME | 当前输入文档的文件名 |
FNR | 当前输入文档的当前行号,尤其当多个输入文档时有用 |
FS | 设置字段分隔符,默认为空格或制表符 |
NF | 当前记录(行)的字段(列)个数 |
NR | 输入数据流的当前记录数(行号) |
OFS | 输出字段分隔符,默认为空格 |
ORS | 输出记录分隔符,默认为换行符 |
RS | 输入记录分隔符,默认为换行符 |
awk
是一种处理文本文件的编程语言,文件的每行数据都被称为记录,默认以空格或制表符为分隔符,每条记录被分成若干字段(列),awk
每次从文件中读取一条记录。
例子:
- 仅显示指定文件中第1、2列的内容(默认以空格为间隔符):
lucky@DESKTOP-VQ8KID4:~/shell$ awk '{print $1,$2}' nowcoder.txt
#include <iostream>
using namespace
int main()
{
int a
int b
cout <<
return 0;
}
- 以冒号为间隔符,仅显示指定文件中第1列的内容:
lucky@DESKTOP-VQ8KID4:~/shell$ awk -F : '{print $1,$2}' /etc/passwd
root x
daemon x
bin x
...
tcpdump x
sshd x
landscape x
pollinate x
lucky x
/etc/passwd
文件中的内容由:
分隔开。
- 以冒号为间隔符,显示系统中所有UID号码大于500的用户信息(第3列):
lucky@DESKTOP-VQ8KID4:~/shell$ awk -F : '$3>=500' /etc/passwd
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
lucky:x:1000:1000:,,,:/home/lucky:/bin/bash
- 仅显示指定文件中含有指定关键词
main
的内容:
lucky@DESKTOP-VQ8KID4:~/shell$ awk '/main/{print}' nowcoder.txt
int main()
- 以冒号为间隔符,仅显示指定文件中最后一个字段的内容:
lucky@DESKTOP-VQ8KID4:~/shell$ awk -F : '{print $NF}' /etc/passwd
/bin/bash
/usr/sbin/nologin
/usr/sbin/nologin
...
/usr/sbin/nologin
/bin/false
/bin/bash
- 输出行号,
NR
将所有文件的数据视为一个数据流,而FNR
则是将多个文件的数据视为独立的若干个数据流,遇到新文件时行号从1开始重新递增。
lucky@DESKTOP-VQ8KID4:~$ awk '{print NR}' first.txt three.sh
1
2
3
lucky@DESKTOP-VQ8KID4:~$ awk '{print FNR}' first.txt three.sh
1
1
2
grep
:强大的文本搜索工具
grep
来自于英文词组“global search regular expression and print out the line”的缩写,意思是用于全面搜索的正则表达式,并将结果输出。人们通常会将grep
命令与正则表达式搭配使用,参数作为搜索过程中的补充或对输出结果的筛选,命令模式十分灵活。
与之容易混淆的是egrep
命令和fgrep
命令。如果把grep
命令当作是标准搜索命令,那么egrep
则是扩展搜索命令,等价于“grep -E
”命令,支持扩展的正则表达式。而fgrep
则是快速搜索命令,等价于“grep -F
”命令,不支持正则表达式,直接按照字符串内容进行匹配。
语法格式: grep [参数] 文件
常用参数:
参数 | 功能 |
---|---|
-i | 忽略大小写 |
-c | 只输出匹配行的数量 |
-l | 只列出符合匹配的文件名,不列出具体的匹配行 |
-n | 列出所有的匹配行,显示行号 |
-h | 查询多文件时不显示文件名 |
-s | 不显示不存在、没有匹配文本的错误信息 |
-v | 显示不包含匹配文本的所有行 |
-w | 匹配整词 |
-x | 匹配整行 |
-r | 递归搜索 |
-q | 禁止输出任何结果,已退出状态表示搜索是否成功 |
-b | 打印匹配行距文件头部的偏移量,以字节为单位 |
-o | 与-b 结合使用,打印匹配的词据文件头部的偏移量,以字节为单位 |
-F | 匹配固定字符串的内容 |
-E | 支持扩展的正则表达式 |
- 搜索某个文件中,包含某个关键词的内容:
lucky@DESKTOP-VQ8KID4:~/shell$ grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
- 搜索某个文件中,以某个关键词开头的内容:
lucky@DESKTOP-VQ8KID4:~/shell$ grep ^root /etc/passwd
root:x:0:0:root:/root:/bin/bash
- 搜索多个文件中,包含某个关键词的内容:
root@DESKTOP-VQ8KID4:~# grep lucky /etc/passwd /etc/shadow
/etc/passwd:lucky:x:1000:1000:,,,:/home/lucky:/bin/bash
/etc/shadow:lucky:$6$SBxuPYFLSnBcfbHN$OkFFnnJCpf2P4OLOnnaWXMq.xbmgL3H5aRy4nkEkk/.8VHABaKDS6MdYm3UR3TpHZplAl5HVyffI8nbLlAAoh1:19256:0:99999:7:::
- 搜索多个文件中,包含某个关键词的内容,不显示文件名称:
root@DESKTOP-VQ8KID4:~# grep -h lucky /etc/passwd /etc/shadow
lucky:x:1000:1000:,,,:/home/lucky:/bin/bash
lucky:$6$SBxuPYFLSnBcfbHN$OkFFnnJCpf2P4OLOnnaWXMq.xbmgL3H5aRy4nkEkk/.8VHABaKDS6MdYm3UR3TpHZplAl5HVyffI8nbLlAAoh1:19256:0:99999:7:::
- 输出在某个文件中,包含某个关键词行的数量:
root@DESKTOP-VQ8KID4:~# grep -c root /etc/passwd /etc/shadow
/etc/passwd:1
/etc/shadow:1
- 搜索某个文件中,包含某个关键词位置的行号及内容:
lucky@DESKTOP-VQ8KID4:~/shell$ grep -n int nowcoder.txt
3:int main()
5: int a = 10;
6: int b = 100;
- 搜索某个文件中,不包含某个关键词的内容:
lucky@DESKTOP-VQ8KID4:~/shell$ grep -v int nowcoder.txt
#include <iostream>
using namespace std;
{
cout << "a + b:" << a + b << endl;
return 0;
}
- 搜索当前工作目录中,包含某个关键词内容的文件,未找到则提示:
root@DESKTOP-VQ8KID4:/# grep -l root *
grep: bin: Is a directory
grep: boot: Is a directory
grep: dev: Is a directory
grep: etc: Is a directory
grep: home: Is a directory
init
grep: lib: Is a directory
grep: lib32: Is a directory
...
grep: tmp: Is a directory
grep: usr: Is a directory
grep: var: Is a directory
- 搜索当前工作目录中,包含某个关键词内容的文件,未找到不提示:
lucky@DESKTOP-VQ8KID4:~/shell$ grep -sl main *
nowcoder.txt
- 递归搜索,不仅搜索指定目录,还搜索其内子目录内是否有关键词文件:
root@DESKTOP-VQ8KID4:/# grep -srl root /etc
/etc/services
/etc/ltrace.conf
/etc/systemd/logind.conf
/etc/crontab
/etc/newt/palette.ubuntu
/etc/xattr.conf
/etc/apparmor.d/tunables/home
...
- 搜索某个文件中,精准匹配到某个关键词的内容(搜索词应与整行内容完全一样才会显示,有别于一般搜索):
lucky@DESKTOP-VQ8KID4:~/shell$ grep -x "return 0;" nowcoder.txt
lucky@DESKTOP-VQ8KID4:~/shell$ grep -x " return 0;" nowcoder.txt
return 0;
- 判断某个文件中,是否包含某个关键词,通过返回状态值输出结果(0为包含,1为不包含),方便在Shell脚本中判断和调用:
lucky@DESKTOP-VQ8KID4:~/shell$ grep -q return nowcoder.txt
lucky@DESKTOP-VQ8KID4:~/shell$ echo $? # 包含
0
lucky@DESKTOP-VQ8KID4:~/shell$ grep -q returns nowcoder.txt
lucky@DESKTOP-VQ8KID4:~/shell$ echo $? # 不包含
1
- 搜索某个文件中,空行的数量:
lucky@DESKTOP-VQ8KID4:~/shell$ grep -c ^$ nowcoder.txt
0
wc
:统计文件的字节数、单词数、行数
wc
命令来自于英文词组“Word count”的缩写,其功能是用于统计文件的字节数、单词数、行数等信息,并将统计结果输出到终端界面。利用wc
命令可以很快的计算出准确的单词数及行数,评估出文本的内容长度。
语法格式:wc [参数] 文件
常用参数:
参数 | 功能 |
---|---|
-w | 统计单词数 |
-c | 统计字节数 |
-l | 统计行数 |
-m | 统计字符数 |
-L | 显示最长行的长度 |
--help | 显示帮助信息 |
--version | 显示版本信息 |
统计指定文件的单词数量:
lucky@DESKTOP-VQ8KID4:~/shell$ wc -w nowcoder.txt
30 nowcoder.txt
统计指定文件的字节数量:
lucky@DESKTOP-VQ8KID4:~/shell$ wc -c nowcoder.txt
142 nowcoder.txt
统计指定文件的字符数量:
lucky@DESKTOP-VQ8KID4:~/shell$ wc -m nowcoder.txt
142 nowcoder.txt
统计指定文件的总行数:
lucky@DESKTOP-VQ8KID4:~/shell$ wc -l nowcoder.txt
9 nowcoder.txt
echo
:输出字符串或提取后的变量值
echo
是用于在终端设备上输出指定字符串或变量提取后值的命令,能够给用户一些简单的提醒信息,也可以将输出的指定字符串内容同管道符一起传递给后续命令作为标准输入信息再来进行二次处理,又或者同输出重定向符一起操作,将信息直接写入到文件中。
如需提取变量值,需在变量名称前加入$
符号做提取,变量名称一般均为大写形式。
语法格式:echo [参数] 字符串/变量
常用参数:
-n | 不输出结尾的换行符 |
---|---|
-e "\\a" | 发出警告音 |
-e "\\b" | 删除前面的一个字符 |
-e "\\c" | 结尾不加换行符 |
-e "\\f" | 换行,光标扔停留在原来的坐标位置 |
-e "\\n" | 换行,光标移至行首 |
-e "\\r" | 光标移至行首,但不换行 |
-E | 禁止反斜杠转移,与-e参数功能相反 |
参考实例
- 输出指定字符串到终端设备界面(默认为电脑屏幕):
wz@lucky:~$ echo hello world
hello world
- 输出某个变量值内容:
wz@lucky:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
- 搭配转义符一起使用,输出纯字符串内容:
wz@lucky:~$ echo \$PATH
$PATH
- 搭配输出重定向符一起使用,将字符串内容直接写入文件中:
wz@lucky:~$ echo Hello World > hello.txt
wz@lucky:~$ cat hello.txt
Hello World
- 搭配反引号执行命令,并将执行结果输出:
wz@lucky:~$ echo `uptime`
19:41:45 up 7 min, 1 user, load average: 0.17, 0.54, 0.37
- 输出带有换行符的内容:
wz@lucky:~$ echo -e "One\nTwo\nThree"
One
Two
Three
- 指定删除字符串中某些字符,随后将内容输出:
wz@lucky:~$ echo -e "123\b456"
12456
题目解决方案
方法一:grep
+awk
既然要求三个值,我们得把需要的数据先提取出来才行。
lucky@LAPTOP-G2DIO3FV:~$ awk -F"[ :]+" '/3306/{print $6, $8}' nowcoder.txt
172.16.34.144 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.34.143 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.34.144 ESTABLISHED
172.16.34.144 ESTABLISHED
172.16.34.144 ESTABLISHED
172.16.34.144 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.34.144 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.34.144 ESTABLISHED
172.16.34.144 ESTABLISHED
172.16.0.24 ESTABLISHED
172.16.34.144 ESTABLISHED
这是31题我们讲过得分割方法,可以倒回去复习一下,这里就不多说啦。
把数据提取出来之后呢?我们来分析一下要求的三个值:
TOTAL_LINK
最简单,直接求数据得行数即可,这个用awk命令得NR就能得出。TOTAL_IP
呢?要求建立连接的ip数目,仔细想想,这不就跟之前做了好多道题那样,直接用数组计数就行了。ESTABLISHED
同理。
那么,很简单了,我们用awk
命令就可以完全搞定这三个值的计算了。
awk -F"[ :]+" '/3306/{print $6, $8}' nowcoder.txt | awk -v establish=0 '{if($2=="ESTABLISHED"){establish++;};a[$1]=0} END{print "TOTAL_IP",length(a);print "ESTABLISHED",establish;print "TOTAL_LINK",NR}'
批注:
- 这里定义的变量
establish
是拿来计算ESTABLISHED
值的;- 数组
a
则是拿来计算IP个数的,为啥用数组呢?因为用数组既可以去重,还可以计数!去重不难理解吧?计数,直接用length(a)
不就计算出IP个数了吗~TOTAL_LINK
不说了,一个NR
就完事~
完整代码如下:(做了适当缩进,方便阅读)
awk -F"[ :]+" '/3306/{
print $6,$8
}' nowcoder.txt | awk -v establish=0 '{
if($2=="ESTABLISHED"){
establish++
}
a[$1]=0
} END {
print "TOTAL_IP", length(a)
print "ESTABLISHED", establish
print "TOTAL_LINK", NR
}'
题目看起来很难,其实也就那样…做题时注意一步一步分解。
方法二:awk
一个awk
就可以搞定啦!代码如下:
awk '{
if($1=="tcp" && $5 ~ "3306"){
if ($6=="ESTABLISHED") {
state++
}
ip[$5]++
n++
}
}END{
print "TOTAL_IP " length(ip);
print "ESTABLISHED " state
print "TOTAL_LINK " n
}' nowcoder.txt
不详细说了,和上面的方法一其实类似,不一样的地方在于:
- 这里使用awk命令的if来筛选出需要的数据
- 此外,对于
TOTAL_LINK
的计算直接用一个递增的变量来计算的,换汤不换药呀。
方法三:grep+awk+wc(分步计算)
咱就是说,既然求的是三个值,如果前面的awk
玩不转,那么一个一个值得计算,为啥不行呢?你说是吧~
- 首先来计算IP地址的个数,筛选出
3306
的行,利用数据进行计数即可:
lucky@LAPTOP-G2DIO3FV:~$ grep '[0-9]:3306' nowcoder.txt | awk -F" " '{a[$5]++} END{for(i in a) print a[i]}' | wc -l
3
- 计算状态为
nESTABLISHED
的个数,没啥好说的,看代码:
lucky@LAPTOP-G2DIO3FV:~$ grep '[0-9]:3306' nowcoder.txt | grep 'ESTABLISHED' | wc -l
20
- 计算连接数,更简单,看代码即可:
lucky@LAPTOP-G2DIO3FV:~$ grep '[0-9]:3306' nowcoder.txt | wc -l
20
分别把这三个值用变量存起来,然后用echo
按要求输出即可。最终代码如下:
establish=$(grep '[0-9]:3306' nowcoder.txt | grep 'ESTABLISHED' | wc -l)
ip=$(grep '[0-9]:3306' nowcoder.txt | awk -F" " '{a[$5]++} END{for(i in a) print a[i]}' | wc -l)
link=$(grep '[0-9]:3306' nowcoder.txt | wc -l)
echo -e "TOTAL_IP "$ip "\nESTABLISHED "$establish"\nTOTAL_LINK "$link
定义变量的方式很多,你用````也是可以的,也即:
establish=`grep '[0-9]:3306' nowcoder.txt | grep 'ESTABLISHED' | wc -l`
ip=`grep '[0-9]:3306' nowcoder.txt | awk -F" " '{adi[$5]++} END{for(i in adi) print adi[i]}' | wc -l`
link=`grep '[0-9]:3306' nowcoder.txt | wc -l`
echo -e "TOTAL_IP "$ip "\nESTABLISHED "$establish"\nTOTAL_LINK "$link
方法四:纯Shell方法
逻辑一模一样,至于shell的语法这里就不多说了~
declare -A b
while read line; do
a=($line)
if [[ ${a[4]} =~ ^.*?3306$ ]]; then
echo "TOTAL_LINK"
if [[ ${a[5]} == "ESTABLISHED" ]]; then
echo "ESTABLISHED"
fi
if [ -z ${b[${a[4]}]} ]; then
echo "TOTAL_IP"
let b[${a[4]}]=1
fi
fi
done <nowcoder.txt | sort | uniq -c | awk '{print $2" "$1}' | sort -nk2