# Sed-Awk **Repository Path**: xiaoxinbupa/sed-awk ## Basic Information - **Project Name**: Sed-Awk - **Description**: sed和awk是编写命令一定要会使用的工具,与grep一起被称为三大组件 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-08-19 - **Last Updated**: 2024-08-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Sed-Awk #### 介绍 sed和awk是编写命令一定要会使用的工具,与grep一起被称为三大组件 ### 18 sed基本用法 #### 18.1 问题 本案例要求熟悉sed命令的p、d、s等常见操作 #### 18.2 方案 ![image-20240531142014588](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240531142014588.png) sed文本处理工具的用法: ```bash 用法1:前置命令 | sed [选项] '条件指令' 用法2:sed [选项] '条件指令' 文件.. .. ``` #### 18.3 步骤 步骤一:认识sed工具 sed命令的常用选项如下: ==-n(屏蔽默认输出,默认sed会输出读取文档的全部内容)== ==-r(支持扩展正则)== ==-i(修改源文件)== - 条件可以是行号或者/正则/,没有条件时默认为所有行都执行指令 - 指令可以是p输出、d删除、s替换 ![image-20240531141914579](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240531141914579.png) 步骤二:使用sed 1)行号案例 ```bash head -5 /etc/passwd > user #准备素材 sed -n 'p' user #输出所有行 sed -n '1p' user #输出第1行 sed -n '2p' user #输出第2行 sed -n '3p' user #输出第3行 sed -n '2,4p' user #输出2~4行 sed -n '2p;4p' user #输出第2行与第4行 sed -n '3,+1p' user #输出第3行以及后面1行 sed -n '1~2p' /etc/passwd #输出奇数行 ``` 2)使用正则当条件 ```bash sed -n '/^root/p' user #输出以root开头的行 sed -n '/root/p' user #输出包含root的行 sed -nr '/^root|^bin/p' user #输出以root开头的行或bin开头的行,|是扩展正则,需要r选项 ``` 3)特殊用法 ```bash sed -n '1!p' user #输出除了第1行的内容,!是取反 sed -n '$p' user #输出最后一行 sed -n '=' user #输出行号,如果是$=就是最后一行的行号 ``` 以上操作,如果去掉-n,在将p指令改成d指令就是删除 步骤三:sed工具的p、d、s操作指令案例集合 1)p指令案例集锦(自己提前生成一个a.txt文件) ```bash [root@svr5 ~]# sed -n 'p' a.txt #输出所有行,等同于cat a.txt [root@svr5 ~]# sed -n '4p' a.txt #输出第4行 [root@svr5 ~]# sed -n '4,7p' a.txt #输出第4~7行 [root@svr5 ~]# sed -n '/^bin/p' a.txt #输出以bin开头的行 [root@svr5 ~]# sed -n '$=' a.txt #输出文件的行数 ``` 2)d指令案例集锦(自己提前生成一个a.txt文件) ```bash [root@svr5 ~]# sed '3,5d' a.txt #删除第3~5行 [root@svr5 ~]# sed '/xml/d' a.txt #删除所有包含xml的行 [root@svr5 ~]# sed '/xml/!d' a.txt #删除不包含xml的行,!符号表示取反 [root@svr5 ~]# sed '/^install/d' a.txt #删除以install开头的行 [root@svr5 ~]# sed '$d' a.txt #删除文件的最后一行 [root@svr5 ~]# sed '/^$/d' a.txt #删除所有空行 ``` 3)sed命令的s替换基本功能(s/旧内容/新内容/选项): ```bash [root@svr5 ~]# vim shu.txt #新建素材 2017 2011 2018 2017 2017 2024 2017 2017 2017 sed 's/2017/6666/' shu.txt #把所有行的第1个2017替换成6666 sed 's/2017/6666/2' shu.txt #把所有行的第2个2017替换成6666 sed '1s/2017/6666/' shu.txt #把第1行的第1个2017替换成6666 sed '3s/2017/6666/3' shu.txt #把第3行的第3个2017替换成6666 sed 's/2017/6666/g' shu.txt #所有行的所有个2017都替换 sed '/2024/s/2017/6666/g' shu.txt #找含有2024的行,将里面的所有2017替换成6666 ``` 思考:如果想把 /bin/bash 替换成 /sbin/sh 怎么操作? ```bash sed -i '1s/bin/sbin/' user #传统方法可以一个一个换,先换一个 sed -i '1s/bash/sh/' user #再换一个 ``` 如果想一步替换: ```bash sed 's//bin/bash//sbin/sh/' user #直接替换,报错 sed 's/\/bin\/bash/\/sbin\/sh/' user #使用转义符号可以成功,但不方便 sed 's!/bin/bash!/sbin/sh!' user #最佳方案,更改s的替换符 sed 's(/bin/bash(/sbin/sh(' user #替换符号可以用键盘上大部分字符 ``` #### 18.4案例:编写脚本,搭建httpd服务,用82号端口开启服务 编写脚本,按下列方法实现 ```bash #!/bin/bash setenforce 0 #关闭selinux yum -y install httpd &> /dev/null #安装网站 echo "sed-test~~~" > /var/www/html/index.html #定义默认页 sed -i '/^Listen 80/s/0/2/' /etc/httpd/conf/httpd.conf #修改配置文件,将监听端口修改为82 systemctl restart httpd #开服务 systemctl enable httpd #设置开机自启 ``` 然后运行脚本 ```bash curl 192.168.2.5:82 #脚本运行之后,测试82端口看到页面即可 sed-test~~~ ss -ntulp | grep httpd #检查服务的端口是否为82 ``` ss -ntulp #看到端口启动的信息 人工智能提示词: 使用sed工具编写shell脚本,将httpd服务的端口修改为82,并定义默认页面为”sed-test”后开启服务 #### 18.5 sed综合脚本应用 ##### 18.5.1 问题 本案例要求编写脚本,实现以下需求,效果如图-1: - 找到使用bash作登录Shell的本地账户名 - 列出这些账户的shadow密码记录 - 按每行“账户名 --> 密码记录”保存到文件中 ![image-20240531084713847](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240531084713847.png) 图-1 ##### 18.5.2 方案 基本思路如下: 1. 先用sed工具取出登录Shell为/bin/bash的账户 2. 再结合循环取得的账户记录,逐行进行处理 3. 针对每一行账户记录,采用掐头去尾的方式获得名称、密码 4. 按照指定格式追加到文件 ##### 18.5.3 步骤 实现此案例需要按照如下步骤进行。 ```bash #!/bin/bash u=$(sed -n '/bash$/s/:.*//p' /etc/passwd) #找到passwd文档中以bash结尾的行,然后将行中冒号以及冒号后面内容都删除,此处的p代表仅仅显示s替换成功的行,最后赋值给u for i in $u #将那些用bash的账户名交给for循环 do pass=$(grep $i /etc/shadow) #用每个账户名去shadow中找对应信息 pass=${pass#*:} #掐头,从左往右删除到第1个冒号 pass=${pass%%:*} #去尾,从右往左删除到最后一个冒号,经过上述步骤,pass就是最终要的密码了 echo "$i --> $pass" #按格式喊出,如果要存到文件中就用追加重定向 done ``` #### sed补充 a行下追加 i行上添加 c替换整行 ```bash sed 'a 666' user #所有行的下面追加666 sed '1a 666' user #第1行的下面追加666 sed '/^bin/a 666' user #在以bin开头的行的下面追加666 sed 'i 666' user #所有行的上面添加666 sed '5i 666' user #第5行的上面添加666 sed '$i 666' user #最后1行的上面添加666 sed 'c 666' user #所有行都替换成666 sed '1c 666' user #替换第1行为666 ``` 替换特殊用法 ```bash cat abc.txt #先准备素材 100 laowang 98 gangge 59 laoniu sed -r 's/([0-9]+)(\s+)([a-z]+)/\3\2\1/' abc.txt #使用替换功能更改文本列,此处小括号相当于保留(复制),\1相当于粘贴之前第1个小括号里的内容 ``` ![image-20240603104108702](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603104108702.png) ![image-20240603103756765](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603103756765.png) ### 19 awk基本用法 #### 1:使用awk提取文本 ##### 1.1 问题 本案例要求使用awk工具完成下列过滤任务: - 练习awk工具的基本用法 - 提取本机系统数据 - 格式化输出信息 ##### 1.2 步骤 实现此案例需要按照如下步骤进行。 **步骤一:awk的基本用法** 1)基本操作方法 ==格式1:awk [选项] '[条件]{指令}' 文件== ==格式2:前置指令 | awk [选项] '[条件]{指令}'== 其中,print 是最常用的编辑指令;若有多条编辑指令,可用分号分隔。 Awk过滤数据时支持仅打印某一列,如第2列、第5列等。 处理文本时,默认将空格、制表符作为分隔符。 条件可以用/ /的方式,与sed类似 ![image-20240603112550763](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603112550763.png) **awk常用内置变量:** - $0 文本当前行的全部内容 - $1 文本的第1列 - $2 文件的第2列 - $3 文件的第3列,依此类推 - NR 文件当前行的行号 - NF 文件当前行的列数(有几列) ![image-20240603165754709](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603165754709.png) ```bash [root@svr5 ~]# cat abc.txt hello the world welcome to beijing awk '{print}' abc.txt #输出所有 awk '/to/{print}' abc.txt #输出有to的那行 awk '{print $2}' abc.txt #输出所有行的第2列 awk '/to/{print $1}' abc.txt #输出有to的那行的第1列 awk '{print $0}' abc.txt #输出所有行所有列 awk '{print $0,$1}' abc.txt #输出所有行所有列,第1列 awk '{print NR}' abc.txt #输出所有行的行号 awk '{print NR,$0}' abc.txt #输出所有行的行号,所有列 awk '{print NR,NF}' abc.txt #输出所有行的行号,列号(有几列) ``` 再使用之前的user文档测试 ```bash awk '/^bin/{print NR}' user #找以bin开头的行,显示该行的行号 awk '/^bin/{print NR,$0}' user #找以bin开头的行,显示该行的行号,所有列 awk '{print NF}' user #输出所有行的列号(每行有几列) ``` ![image-20240603110340810](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603110340810.png) 2)==选项 -F 可指定分隔符== ```bash awk -F: '{print $1}' user #文档中如果没有空格,可以用F修改分隔符 awk -F: '{print $1,$6}' user #使用冒号作为列的分隔符,显示第1、6列 ``` awk还识别多种单个的字符,比如以“:”或“/”分隔 ```bash awk -F [:/] '/^root/{print $1,$10}' user ``` awk的print指令不仅可以打印变量,还可以打印常量 ```bash awk -F: '{print $1" 的家目录是 "$6}' user #输出常量,加双引号即可 awk -F: '{print $1" 的解释器是 "$7}' user ``` ![image-20240603113416191](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603113416191.png) **步骤二:利用awk提取本机系统数据** 1)收集根分区剩余容量 ```bash df -h | awk '/\/$/{print $4}' #使用df -h 作为前置指令交给awk处理找到以/结尾的行,并输出第4列 df -h | awk '/\/$/{print "根分区剩余容量是"$4}' #然后加常量输出 ``` 2)收集网卡流量信息 RX为接收的数据量,TX为发送的数据量。packets以数据包的数量为单位,bytes以字节为单位: ```bash ifconfig eth0 | awk '/RX p/{print "eth0网卡接收的数据量是"$5"字节"}' ifconfig eth0 | awk '/TX p/{print "eth0网卡发送的数据量是"$5"字节"}' ``` ![image-20240603115710819](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603115710819.png) **步骤三:格式化输出信息** 1)awk处理的时机 ![image-20240603165820506](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603165820506.png) awk会逐行处理文本,支持在处理第一行之前做一些准备工作,以及在处理完最后一行之后做一些总结性质的工作。在命令格式上分别体现如下: 1. ==awk **[**选项**]** '[条件]{指令}' 文件== 2. ==awk **[**选项**]** 'BEGIN{指令} {指令} END{指令}' 文件== - BEGIN{ } 行前处理,读取文件内容前执行,指令执行1次 - { } 逐行处理,读取文件过程中执行,指令执行n次 - END{ } 行后处理,读取文件结束后执行,指令执行1次 ```bash awk -F: 'BEGIN{print "start"}{print $1}END{print "over"}' user awk 'BEGIN{print NR}{print NR}END{print NR}' user ``` ![image-20240603142902872](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603142902872.png) 2)格式化输出/etc/passwd文件 要求: 格式化输出passwd文件内容时,要求第一行为列表标题,中间打印用户的名称、UID、家目录信息,最后一行提示一共已处理文本的总行数,效果如图-1所示。 ![image-20240603110613803](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603110613803.png) 3)根据实现思路编写、验证awk过滤语句 输出信息时,可以使用“\t”显示Tab制表位: ```bash awk 'BEGIN{print "User\tUID\tHome"}' #第1步输出表头信息 awk -F: '{print $1"\t"$3"\t"$6}' user #第2步输出内容 awk 'END{print "总计"NR"行" }' user #第3步输出结尾 awk -F: 'BEGIN{print "User\tUID\tHome"}{print $1"\t"$3"\t"$6}END{print "总计"NR"行"}' user #合在一起写 ``` ![image-20240603144213503](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603144213503.png) #### 2:awk处理条件 ##### 2.1 问题 - 本案例要求使用awk工具熟悉各种条件,以达到更精确查找某行的目的 ##### 2.2 步骤 实现此案例需要按照如下步骤进行。 步骤一:认识awk处理条件的设置 使用正则设置条件 - /正则/ - ~ 包含 - !~不包含 ```bash awk -F: '$6~/root/{print}' user #输出第6列包含root的行 awk -F: '$6~/bin/{print}' user #输出第6列包含bin的行 awk -F: '$6!~/bin/{print}' user #输出第6列不包含bin的行 ``` ![image-20240603151557598](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603151557598.png) 2)使用数值/字符串比较设置条件 比较符号:==(等于) !=(不等于) >(大于) \>=(大于等于) <(小于) <=(小于等于) ```bash awk -F: '$3<3{print}' user #输出第3列小于3的行 awk -F: '$3<=3{print}' user #输出第3列小于等于3的行 awk -F: 'NR==2{print}' user #输出第2行 awk -F: 'NR>2{print}' user #输出行号大于2的行 ``` ![image-20240603152624495](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603152624495.png) 3)逻辑测试条件 ![image-20240603170115181](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603170115181.png) ```bash awk -F: 'NR>=3&&NR<=5{print}' user #找行号是3~5行 awk -F: 'NR==2||NR==4{print}' user #找行号是2或者4的行 awk -F: 'NR==2||NR==40{print}' user #如果只有一个条件满足就显示一个 ``` 当定义了条件且指令就是print时可以省略指令不写 ```bash awk -F: '$7~/bash/&&$3<=500' user #找第7列包含bash并且第3列小于等于500的行 awk 'NR==2&&NR==4' user #找行号既是2又是4的行,不存在,无输出 awk -F: '$7~/bash/&&NR<=3' user #找第7列包含bash并且行号是1~3的 awk -F: '$7~/bash/||NR<=3' user #找第7列包含bash或者行号是1~3的 ``` ![image-20240603154208968](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603154208968.png) 4)数学运算 ![image-20240603170130890](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603170130890.png) ```bash awk 'NR%2==0{print NR,$0}' user #在条件中使用运算,找到将行号除以2余数等于0的行,然后输出该行的行号和所有列,相当于输出偶数行 ``` #### 3 使用awk统计网站访问量 使用awk统计网站访问量 ```bash setenforce 0 #关闭selinux systemctl stop firewalld #关闭防火墙 systemctl restart httpd #开启网站服务 ``` 使用浏览器多访问几次网站,包括本机用curl ```bash curl 192.168.88.2:82 #如果端口没改过就不用敲:82 awk '{print $1}' /var/log/httpd/access_log #初步统计,不完美 ``` 完美版: ![image-20240603164053181](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603164053181.png) #### 4 awk数组 定义数组的格式:数组名[下标]=元素值 调用数组的格式:数组名[下标] ![image-20240603170152878](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603170152878.png) ```bash awk 'BEGIN{a=10;a=20;print a}' #首先测试普通变量 awk 'BEGIN{a[1]=10;a[2]=20;print a[2],a[1]}' #使用awk测试数组,创建数组a,下标1对应值是10,下标2对应值是20,然后输出下标是2与下标是1的值 ``` ![image-20240603165114074](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603165114074.png) 注意,awk数组的下标除了可以使用数字,也可以使用字符串,字符串需要使用双引号: ```bash awk 'BEGIN{a["abc"]="abcabc";a["xyz"]="xyzxyz";print a["xyz"]}' ``` ![image-20240603165023716](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603165023716.png) 以上信息是手工输入,还可以从文档收集 准备一个测试文档,里面有6行,每行分别是abc、xyz、abc、opq、xyz、abc 然后按照awk逐行处理的工作特点使用awk '{a[$1]++}' shu.txt 走完每一行得到下列结果,但不会输出到屏幕 ```bash 逐行任务 每行实际执行 执行结果 a[$1]++ a[abc]++ a[abc]=1 a[$1]++ a[xyz]++ a[xyz]=1 a[$1]++ a[abc]++ a[abc]=2 a[$1]++ a[opq]++ a[opq]=1 a[$1]++ a[xyz]++ a[xyz]=2 a[$1]++ a[abc]++ a[abc]=3 awk '{a[$1]++}END{print a["abc"]}' shu.txt #如果要看值,可以输出数组名[下标],由于数组下标不确定,逐行任务运算前可以不用BEGIN任务定义 ``` 根据上述操作了解数组可以收集信息,但收集完了之后查看确不方便,可以用for循环实现。方法如下: for(变量名 in 数组名){print 变量名} #这个格式可以查看数组的所有下标 ```bash awk '{a[$1]++}END{for(i in a){print i,a[i]}}' shu.txt #使用逐行任务与数组收集文档shu.txt中的信息,然后在END任务中使用for循环显示所有数组a的下标与值 ``` ![image-20240603165426836](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603165426836.png) #### 5:awk扩展应用 ##### 5.1 问题 本案例要求使用awk工具完成下列两个任务: 分析Web日志的访问量排名,要求获得客户机的地址、访问次数,并且按照访问次数排名 ##### 5.2 方案 1)awk统计Web访问排名 在分析Web日志文件时,每条访问记录的第一列就是客户机的IP地址,其中会有很多重复的IP地址。因此只用awk提取出这一列是不够的,还需要统计重复记录的数量并且进行排序。 通过awk提取信息时,利用IP地址作为数组下标,每遇到一个重复值就将此数组元素递增1,最终就获得了这个IP地址出现的次数。 针对文本排序输出可以采用sort命令,相关的常见选项为-r、-n、-k。其中-n表示按数字顺序升序排列,而-r表示反序,-k可以指定按第几个字段来排序。 ##### 5.3 步骤 实现此案例需要按照如下步骤进行。 步骤一:统计Web访问量排名 分步测试、验证效果如下所述。 1)提取IP地址及访问量 ```bash awk '{ip[$1]++}END{for(i in ip){print ip[i],i }}' /var/log/httpd/access_log #数组名称可以自定义其他的,通过awk数组+for循环查看日志中哪个ip来访过以及来访的次数 ``` 2)对第1)步的结果根据访问量排名 ```bash awk '{ip[$1]++}END{for(i in ip){print ip[i],i}}' /var/log/httpd/access_log | sort -nr #使用sort命令增加排序功能,-n是以数字形式排序,-r是降序 -k是按第几列排序 ``` ![image-20240603175421279](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603175421279.png) -----/var/log/secure 是记录ssh访问本机的日志,同样可以使用awk查看记录 ![image-20240603175558904](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240603175558904.png) #### 6:安全检测 ##### 6.1 问题 本案例要检测登录者的IP 检测安全日志,登录root且密码错误就输出对方IP ##### 6.2 步骤 实现此案例需要按照如下步骤进行。 步骤一:准备工作 1)/var/log/secure是安全日志,如果有人登陆时输入错误密码的话信息会记录下来,这种信息可以用awk抓取出来,方法如下: ```bash awk '/Failed password for root/{ip[$11]++}END{for(i in ip){print i,ip[i]}}' /var/log/secure #统计安全日志中访问root账户且密码输入错误的ip地址与次数 ``` #### 7:awk编写监控脚本 ##### 7.1 问题 本案例要求编写脚本,实现计算机各个性能数据监控的功能,具体监控项目要求如下: ##### 7.2 步骤 实现此案例需要按照如下步骤进行。 **步骤一:准备工作** 1)部分常用命令 ```bash [root@svr5 ~]# uptime #查看CPU负载 [root@svr5 ~]# ifconfig eth0 #查看网卡流量 [root@svr5 ~]# free #查看内存信息 [root@svr5 ~]# df #查看磁盘空间 [root@svr5 ~]# wc -l /etc/passwd #查看计算机账户数量 [root@svr5 ~]# who |wc -l #查看登录账户数量 [root@svr5 ~]# rpm -qa |wc -l #查看已安装软件包数量 ``` **步骤二**:编写参考脚本 1)脚本内容如下: ```bash #!/bin/bash while : do clear #清屏 free -h | awk '/^Mem:/{print "剩余内存容量是"$4}' df -h | awk '/\/$/{print "根分区剩余容量是"$4}' awk 'END{print "用户总数是"NR"个"}' /etc/passwd who | awk 'END{print "登录用户数量是"NR"个"}' uptime | awk '{print "cpu的15分钟平均负载是"$NF}' rpm -qa | awk 'END{print "安装的软件包数量是"NR"个"}' sleep 3 done ``` 人工智能提示词: 编写shell脚本,利用awk截取系统各种信息,如CPU的15分钟平均负载、网卡接收流量信息、内存与硬盘剩余空间大小、账户数量等,并每隔3秒循环显示一次 ![image-20240604101913373](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240604101913373.png)v ![image-20240604203813965](https://gitee.com/xiaoxinbupa/linux-note/raw/master/linux_base_picture/image-20240604203813965.png) ```bash [root@pc1 ~]# netstat -an | awk '/^tcp\s/&&//{ip[$5]++ ; state[$5][$6]++}END{for(i in ip){for(j in state[i]){print i,ip[i],j,state[i][j]}}}' | sort -nr ``` ###