一个计算机技术爱好者与学习者

0%

好好学Linux:grep/awk/sed 文本三剑客

1. awk注意事项

awk命令格式:
awk [选项参数] 'script' var=value file(s)

这里需要特别注意的是,包裹script的是单引号,单引号,单引号!
参考文档Linux awk 命令

2. 删除空行

过滤hostlist.txt中的空行:

1
2
3
4
5
6
7
cat hostlist.txt |tr -s '\n'
cat hostlist.txt |sed '/^$/d'
cat hostlist.txt |awk '{if($0!="")print}'
cat hostlist.txt |awk '{if(length!=0) print $0}'
grep -v "^$" hostlist.txt
# 终极方法(linux和windows空行都适用)
grep -v -e '^[[:space:]]*$' hostlist.txt

3. 过滤windows换行符

在windows里编辑好的文件,上传到linux后发现多了^M。以过滤hostlist.txt中的^M为例:

1
sed -i 's/^M//g' hostlist.txt

注意,直接复制粘贴上面的命令是无效的。^M的输入方式是 Ctrl + V ,然后 Ctrl + M 。

4. 去除空格

去除test.txt文本中的空格:

1
cat test.txt | tr -d ' '

5. 空格转换行

把test.txt文本中的空格变成换行符。

1
2
cat test.txt | tr ' ' '\n'
cat test.txt | sed 's/ /\n/g'

6. 查找并插入一行

查找包含- [engineering,subject0]的文件,- [engineering,subject0]之后紧接着插入一行- [engineering,subject1]

1
2
3
sed -i 's/- \[engineering,subject0\]/- \[engineering,subject0\]\n- \[engineering,subject1\]/g' *

sed -i 's/- \[engineering,subject0\]/- \[engineering,subject0\]\n- \[engineering,subject1\]/g' `grep -e '- \[engineering,subject0\]' -rl .`

7. 大小写字符转换

1
2
3
4
5
6
7
8
9
10
11
# 大写转小写
echo "192.168.56.101 MASTER" | tr '[A-Z]' '[a-z]'

# 小写转大写
echo "192.168.56.101 master" | tr '[a-z]' '[A-Z]'

# 某一列大写转小写
echo "192.168.56.101 MASTER" | awk '$3=tolower($2) {print $1" "$2" "$3}'

# 某一列小写转大写
echo "192.168.56.101 master" | awk '$3=toupper($2) {print $1" "$2" "$3}'

PS:对于/etc/hosts中的主机名和域名,只保留大写或者只保留小写就可以了,因为主机名和域名不区分大小写。

8. 读取某一列

读取第一列,读取最后一列。

1
2
3
echo "www.voidking.com" | awk -F'.' '{print $1}'
echo "www.voidking.com" | awk -F'.' '{print $3}'
echo "www.voidking.com" | awk -F'.' '{print $NF}'

9. 第一列换到最后一列

已知namelist.txt:

1
2
haojin 70 80
voidking 90 100

需求:第一列name,在显示时放到最后一列。

1
cat namelist.txt | awk '{for(i=2;i<=NF;i++)printf("%s ", $i);print $1}'

10. 循环读取单列文本

已知hostlist.txt为:

1
2
www.baidu.com
www.voidking.com

需求:批量查询主机名或者主机IP

脚本:

1
for i in `cat hostlist.txt`;do host $i;done

11. 文本比较

需求:两个姓名列表,需要对比出两个文件中相同的姓名和不同的姓名。

脚本:

1
2
3
cat file1 > file.txt
cat file2 >> file.txt
cat file.txt | sort |uniq -c | sort -n > result.txt

12. 联合查询

使用shell,能否实现类似于SQL的联合查询?必须可以。

已知file1的内容为:

1
2
1 realname
2 nickname

file2的内容为:

1
2
3
4
5
voidking nickname
haojin realname
hankin nickname
jinhao realname
vk nickname

需求:根据file1和file2的第二列,把file1和file2合并成一个文件。

1
2
3
4
5
2 voidking nickname
1 haojin realname
2 hankin nickname
1 jinhao realname
2 vk nickname

这个需求使用awk命令来实现。
NR,表示awk开始执行程序后所读取的数据行数。
FNR,与NR功用类似,不同的是awk每打开一个新文件,FNR便从0重新累计。
NR==FNR:用于在读取两个或两个以上的文件时,判断是不是在读取第一个文件。
awk处理多个文件的语法:

1
awk -F 分隔符 'BEGIN { 初始化 } { 循环执行部分 } END { 结束处理 }' file_list1 file_list2

其中BEGIN和END可以省略,-F也可以使用默认,循环执行部分,是按行对文件进行处理的。

脚本:

1
2
awk -F " " 'NR==FNR{a[$2]=$0;next}{print a[$2]" "$1}' file1 file2 \
| awk '{print $1" "$3" "$2}'

由NR=FNR为真时,判断当前读入的是第一个文件file1,执行第一个花括号内的内容。
把file1中每行记录都存入数组a,并使用file1的第2个字段作为下标。

由NR=FNR为假时,判断当前读入了第二个文件file2,执行第二个花括号内的内容。
file2中的每行,根据file2的第2个字段打印数组a中的内容,同时打印file2中的第一列。

13. 求交集

已知file1内容为:

1
2
3
haojin
voidking
vk

file2内容为:

1
2
3
4
5
haoshuai 95 93 80
vk 99 99 100
haojin 100 100 99
baidu 100 100 100
voidking 99 99 99

需求:file1第一列和file2第一列求交集,显示file2中交集的内容。

1
2
3
awk '{if(NR==FNR){a[$1]=$1}else if($1 in a){print $0}}' file1 file2
# or
awk '{if(NR==FNR)a[$1]=$1;else if($1 in a)print $0}' file1 file2

14. 每两行合成一行

已知ip-port.txt内容为:

1
2
3
4
5
6
127.0.0.1
80
127.0.0.1
8080
192.168.56.101
3306

需求:把ip和对应端口放在同一行。

1
2
3
127.0.0.1 80
127.0.0.1 8080
192.168.56.101 3306

脚本:

1
2
3
4
5
sed -n "N;s/\n/ /p" ip-port.txt
awk 'NR%2{printf "%s ",$0;next;}1' ip-port.txt
awk 'ORS=NR%2?FS:RS' ip-port.txt
awk '{ ORS = (NR%2 ? FS : RS) } 1' ip-port.txt
awk '{ ORS = (NR%2 ? "," : RS) } 1' ip-port.txt

15. 计算一列的和

已知fruit.txt内容为:

1
2
3
4
apple 10
orange 7
banana 0
watermelon 1

第一列是水果名称,第二列是水果数量。

需求1:计算水果的总数。

1
2
awk '{sum += $2};END {print sum}' fruit.txt
cat fruit.txt | awk '{sum += $2};END {print sum}'

需求2:计算存在多少种水果。

1
cat fruit.txt | awk '{if ($2>0) (sum += 1); else (sum += 0)};END{print sum}'

16. 根据时间筛选日志

筛选11月10日和11月11日的14:30到18:00的日志

1
2
3
4
grep -E '1[0,1]/Nov/2022:14:[3,4,5][0-9]|1[0,1]/Nov/2022:1[5-7]|1[0,1]/Nov/2022:18:00' access.log
# or
grep -E '10/Nov/2022' access.log | sed -n '/2022:14:30/,/2022:18:00/p' > tmp.log
grep -E '11/Nov/2022' access.log | sed -n '/2022:14:30/,/2022:18:00/p' >> tmp.log

grep筛选出的日志,包含18:00:00-18:00:59内的日志。
sed筛选出的日志,只包含第一个匹配上2022:18:00的日志,不包含18:00:00-18:00:59内的日志。