[实验] Linux 硬盘的加密 (通过 crypt 实现)

纪念:站主于 2019 年 11 月完成了此开源实验,并将过程中的所有命令经过整理和注释以后,形成以下教程

步骤一:硬盘加密后的注意事项

1) 加密后不能直接挂载
2) 加密后硬盘丢失也不用担心数据被盗
3) 加密后必须做映射才能挂载

步骤二:生成一个新的分区

2.1 显示现有的分区

# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sr0     11:0    1 1024M  0 rom  
vda    253:0    0   10G  0 disk 
└─vda1 253:1    0   10G  0 part /
vdb    253:16   0   10G  0 disk 

(补充:在这里是加了一个 vdb 硬盘用来进行分区并加密)

2.2 创建一个新的分区

# fdisk /dev/vdb
命令(输入 m 获取帮助):n
分区号 (1-8,默认 1):
起始 扇区 (0-20971440,默认 0):
Last 扇区 or +扇区 or +size{K,M,G,T,P} (0-20971440,默认 20971440):+5G
分区 1 已设置为 Linux native 类型,大小设为 5 GiB

命令(输入 m 获取帮助):w
The partition table has been altered!

2.2 显示新的分区

# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sr0     11:0    1 1024M  0 rom  
vda    253:0    0   10G  0 disk 
└─vda1 253:1    0   10G  0 part /
vdb    253:16   0   10G  0 disk 
└─vdb1 253:17   0    5G  0 part 

步骤三:创建逻辑卷

3.1 创建卷组

# vgcreate mysqldatavg /dev/vdb1
WARNING: sun signature detected on /dev/vdb1 at offset 508. Wipe it? [y/n]: y
  Wiping sun signature on /dev/vdb1.
  Physical volume "/dev/vdb1" successfully created.
  Volume group "mysqldatavg" successfully created

3.2 创建逻辑卷

# lvcreate -n mysqldatalv -L 1G mysqldatavg
  Logical volume "mysqldatalv" created.

步骤四:给逻辑卷加密

4.1 给逻辑卷加密

# cryptsetup luksFormat /dev/mapper/mysqldatavg-mysqldatalv

WARNING!
========
这将覆盖 /dev/mapper/mysqldatavg-mysqldatalv 上的数据,该动作不可取消。

Are you sure? (Type uppercase yes): YES
输入 /dev/mapper/mysqldatavg-mysqldatalv 的口令:
确认密码:

4.2 解锁逻辑卷

# cryptsetup luksOpen /dev/mapper/mysqldatavg-mysqldatalv mysqldata
输入 /dev/mapper/mysqldatavg-mysqldatalv 的口令:

(补充:这里的 mysqldata 是解锁后的硬件名称)

4.3 格式化逻辑卷

# mkfs.ext4 /dev/mapper/mysqldata

(注意:要先解锁了逻辑卷以后才能格式化逻辑卷)

4.4 锁住逻辑卷

# cryptsetup luksClose mysqldata

内容五:自动挂载加密逻辑卷

5.1 修改系统自动挂载文件

# vim /etc/fstab

添加以下内容:

......
/dev/mapper/mysqldata /var/lib/mysql ext4 defaults 0 0

5.2 创建一个映射器

# vim /etc/cypttab

添加以下内容:

......
mysqldata /dev/mapper/mysqldatavg-mysqldatalv /root/keyfile luks

(补充:这里的三个参数分别代表:虚拟设备名、真实设备、密码的存储文件)

5.3 创建随机密钥文件

# dd if=/dev/urandom of=/root/keyfile bs=1024 count=4

5.4 将密钥文件设为只读

# chmod 0400 /root/keyfile

5.5 将密码添到 luks 中,让密码立刻生效

# cryptsetup luksAddKey /dev/mysqldatavg/mysqldatalv /root/keyfile
输入任意已存在的口令:

5.6 测试挂载效果

5.6.1 挂载加密逻辑卷
# mount -a
5.6.2 测试加密效果
# df -h

[内容] MariaDB & MySQL 安全调优思路

内容一:对网络数据进行加密传输

1.1 生成 SSL

1.1.1 创建 CA 证书
# openssl genrsa 2048 > ca-key.pem
Generating RSA private key, 2048 bit long modulus
..+++
...................................+++
e is 65537 (0x10001)

# openssl req -new -x509 -nodes -days 3600 -key ca-key.pem -out ca.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:ca
Email Address []:

(注意:创建 CA 证书、服务端证书、客户端证书时 Common Name 必须要有值,且必须相互不一样)

1.1.2 创建服务端证书,并去除加密,并使用刚刚的 CA 证书进行签名
# openssl req -newkey rsa:2048 -days 3600 -nodes -keyout server-key.pem -out server-req.pem
Generating a 2048 bit RSA private key
.............+++
...+++
writing new private key to 'server-key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

# openssl rsa -in server-key.pem -out server-key.pem
writing RSA key

# openssl x509 -req -in server-req.pem -days 3600 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
Signature ok
subject=/C=XX/L=Default City/O=Default Company Ltd
Getting CA Private Key

(注意:创建 CA 证书、服务端证书、客户端证书时 Common Name 必须要有值,且必须相互不一样)

1.1.3 创建客户端证书,并去除加密,并使用刚刚的 CA 证书进行签名
# openssl req -newkey rsa:2048 -days 3600 -nodes -keyout client-key.pem -out client-req.pem
Generating a 2048 bit RSA private key
..................+++
..............................................+++
writing new private key to 'client-key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

# openssl rsa -in client-key.pem -out client-key.pem
writing RSA key

# openssl x509 -req -in client-req.pem -days 3600 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem
Signature ok
subject=/C=XX/L=Default City/O=Default Company Ltd
Getting CA Private Key
1.1.4 对生成的证书进行验证
# openssl verify -CAfile ca.pem server-cert.pem client-cert.pem
server-cert.pem: OK
client-cert.pem: OK

1.2 将 SSL 添加到 MariaDB & MySQL

1.2.1 将 SSL 放在指定的位置
# mv ca.pem /home/mysql/sslconfig/ca.pem
# mv server-cert.pem /home/mysql/sslconfig/server-cert.pem
# mv server-key.pem /home/mysql/sslconfig/server-key.pem
1.2.2 修改配置文件
# vim /etc/my.cnf

在:

......
[mysqld]

下面添加:

ssl-ca=/home/mysql/sslconfig/ca.pem
ssl-cert=/home/mysql/sslconfig/server-cert.pem
ssl-key=/home/mysql/sslconfig/server-key.pem
......

(补充:这里以 MariaDB 数据库的配置文件是 /etc/my.cnf 为例)

1.2.3 重启数据库
# systemctl restart mariadb

(补充:这里以重启 MariaDB 数据库为例)

1.3 验证 SSL
1.3.1 查看 have_ssl 和 ssl 变量
> show variables like 'have_%ssl';
> show variables like '%ssl%';

(补充:如果它们的参数为 yes ,表示服务端已经开启 SSL)

1.3.2 查看 SSL 的状态
> show status like 'ssl_cipher'

(补充:如果出现 “SSL:Cipher in use is DHE-RSA-AES256-SHA“ 则表示客户端已经使用 SSL 连接了)

1.3.3 确保所有数据库用户使用 SSL
> grant usage on <database>.<table> to '<user>'@'<host>' reouter ssl;
1.3.4 用户通过 SSL 连接数据库的方法
> mysql -u <user> -p -h <host> --ssl-ca=/home/mysql/sslconfig/ca.pem

内容二:开启审计

(如果是 MariaDB 和 MySQL 5.7)

> set global log_warning=2;

(如果是 MySQL 8.0 及以上版本开启审计)

> set global general_log = on;
> set global log_timestamps = SYSTEM;

[命令] Linux 命令 awk (显示文本的列)

内容一:awk 格式

# awk <option> '<condition>{<command>}' <file>

或者:

# awk <option> ' BEGIN{<option> <command>} {<command>} END{<command>}' <file>


补充:在这里
1) BEGIN{ } 里的指令只执行 1 次,并且是最先执行
2) { } 里的指令会一行行地执行文件里的内容
3) END{ } 里指令只执行 1 次,并且是最后执行

内容二:awk 选项

2.1 awk 常用选项

2.1.1 awk 的 -F 选项

awk 默认是以空格分割列,但是也可以通 -F 参数指定分割每一列的符号

2.1.2 awk 的 -v 选项

将变量导入 awk 中,并可以通过 ‘{print (<variable>)}’ 将变量显示出来

2.2 awk 的 RS、ORS、FS、OFS 选项

2.2.1 awk 的 RS、ORS、FS、OFS 选项作用

1) RS 全名为 Record Separator (输入记录分隔符)
2) ORS 全名为 Output Record Separate (输出记录分隔符)
3) FS 全名为 Field Separator (输入字段分隔符)
4) OFS 全名为 Output Field Separator (输出字段分隔符)

2.2.2 RS、ORS、FS、OFS 的常用值

1) RS=”” 每 1 次读取 1 个段落,默认情况下在 awk 中,把没有空行分割的多行会被定义为 1 个段落
2) RS=”\0″ 一次性读取所有内容,但是前提是 \0 字符不能在文件中存在
3) RS=”^$” 一次性读取所有内容
4) RS=”\n+” 按行读取内容,并且忽略空行

2.3 awk 的 sub、gsub 选项

2.3.1 sub 和 gsub 的区别

1) sub 只匹配第一个字符串,类似于 sed ‘s//’
2) gsub 匹配所有字符串,类似于 sed ‘s//g’

2.3.2 sub 和 gsub 的格式
2.3.2.1 sub 的格式
# sub (<original string or regular expression>, <substitution string>):
# sub (<original string or regular expression>, <substitution string>, <target string>)
2.3.2.2 gsub 的格式
# gsub (<original string or regular expression>, <substitution string>):
# gsub (<original string or regular expression>, <substitution string>, <target string>)

内容三:awk 的常用内置变量

1) $0 文本当前行的全部内容
2) $1 文本的第 1 列
3) $2 文本的第 2 列
4) $3 文本的第 3 列

(补充:“$” 号后面的数字以此类推)

5) NR 文本当前行的行数,也就是目前处于第几行
6) NF 文本当前行的列数,也就是目前一共有几列
7) $(NF-1) 文本倒数第 1 列
8) $(NF-2) 文本倒数第 2 列
9) $(NF-3) 文本倒数第 3 列
10) , 代表空格
11) \t 代表 tab 制表位,就是比一个空格更多的空格
12) FILENAME 当前文件名
13) IGNORECASE 是否忽略大小写,当 IGNORECASE=1 时则忽略大小写

内容四:awk 基础显示的案例

4.1 显示第 1 列和第 3 列,但中间没有空格

# awk '{print $1,$3}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

4.2 显示第 1 列和第 3 列

# awk '{print $1,$3}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

4.3 显示第 1 列和第 3 列,但中间有很长的空格空间

# awk '{print $1,$3}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

4.4 显示倒数第 2 列

# awk '{print $(NF-2)}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

4.5 显示每 1 行的行号和列数

# awk -F: '{print NR,NF}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

4.6 显示前 1 个命令结果的第 5 列

# df -h | awk '{print $5}'

4.7 显示前 1 个命令最后 1 行结果的第 5 列

# df -h / | tail -1 | awk '{print $5}'

或者:

# df -h / | awk 'END{print $5}'

4.8 显示第 2 列到最后 1 列

# cat test.txt | awk '{$1="";print}' | sed -r 's/( )(.*)/\2/'


补充:
1)这里的 /etc/passwd 是要被 awk 操作的测试文件
2)这里的 $1=”” 是将第 1 列的内容变为空
3)这里的 print 作用相当于 print $0

4.9 显示每行的行数、列数、第 1 列和最后 1 列,并且将冒号 “:” 视为分割符

# awk -F: '{print NR,NF,$1,$NF}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

4.10 显示总行数减 1 的值

# awk 'END{print --NR}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

4.11 显示总行数加 1 的值

# awk 'END{print ++NR}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

4.12 显示最后 1 行第 1 列字符的长度

# echo eternalcenter | awk 'END{print length($1)}'

4.13 显示第 1 列,并且将冒号 “:” 视为分割符

# awk -F: '{print "\047 $1 \047"}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

(注意:这里的 \047 代表的是单引号 “’”)

4.14 显示第 2 列的第 1 到第 3 个字符

# awk '{print substr($2,1,3)}' test.txt

或者:

# awk '{$a=substr($2,1,3);print $a;}'

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

(注意:在 awk 当中字符索引 substr 都是从第 1 个开始的,不是从第 0 个开始的)

4.15 显示第 2 列的第 1 到第最后 1 个字符

# awk '{print substr($2,1,)}' test.txt

4.16 将第 1 列以横杠 “-” 作为分隔符进行拆分,并将拆分的内容一行行地显示

# echo eternal-center-zmy | awk '{split ($0,a,"-");for (i in a) print a[i]}'

4.17 自定义列并显示第 2 列 (不包含多余的空格)

# awk 'BEGIN{FIELDWIDTHS="2 2:4 2:3"}{print $2}' test.txt


补充:
1) FIELDWIDTH 函数的作用是:按照字符的数量划分列,有 FS 的话也会覆盖 FS 覆盖划分列的方式
2) 这里的 FILEDWITHS=”2 2:4 2:3″ 代表第 1 列有 2 个字符,相隔 2 个字符后第 2 列开始有 4 个字符,再相隔 2 个字符后第 3 列开始有 3 个字符
3) 这里的 test.txt 是要被 awk 操作的测试文件

或者:

# awk -vFIELDWIDTHS="2 2:4 2:3" '{print $2}' test.txt


补充:
1) FIELDWIDTH 的作用是:按照字符的数量划分列,有 FS 的话也会覆盖 FS 覆盖划分列的方式
2) 这里的 FILEDWITHS=”2 2:4 2:3″ 代表第 1 列有 2 个字符,相隔 2 个字符后第 2 列开始有 4 个字符,再相隔 2 个字符后第 3 列开始有 3 个字符
3) 这里的 test.txt 是要被 awk 操作的测试文件

4.18 自定义列并显示第 2 列 (包含多余的空格)

# awk 'BEGIN{FIELDWIDTHS="2 6 5"}{print $2}' test.txt


补充:
1) FIELDWIDTH 函数的作用是:按照字符的数量划分列,有 FS 的话也会覆盖 FS 覆盖划分列的方式
2) 这里的 FILEDWITHS=”2 6 5″ 代表第 1 列有 2 个字符,第 2 列开始有 6 个字符,第 3 列开始有 5 个字符
3) 这里的 test.txt 是要被 awk 操作的测试文件

或者:

# awk -vFIELDWIDTHS="2 6 5" '{print $2}' test.txt


补充:
1) FIELDWIDTH 函数的作用是:按照字符的数量划分列,有 FS 的话也会覆盖 FS 覆盖划分列的方式
2) 这里的 FILEDWITHS=”2 6 5″ 代表第 1 列有 2 个字符,第 2 列开始有 6 个字符,第 3 列开始有 5 个字符
3) 这里的 test.txt 是要被 awk 操作的测试文件

4.19 自定义每 1 列之间的间距 (保留原来的行结构)

# awk '{ printf "%-9s %-8s %-8s\n", $1, $2, $3 }' test.txt


补充:
1) 这里以设置第 1 列和第 2 列之间间隔 9 个空格,第 2 列和第 3 列之间间隔 8 个空格为例
2) 这里的 test.txt 是要被 awk 操作的测试文件

4.20 自定义每 1 列之间的间距 (所有行合为 1 行)

# awk '{ printf "%-4s %-8s %-6s", $1, $2, $3 }' test.txt


补充:
1) 这里以设置第 1 列和第 2 列之间间隔 9 个空格,第 2 列和第 3 列之间间隔 8 个空格,且每行之间间隔 6 个空格为例
2) 这里的 test.txt 是要被 awk 操作的测试文件

4.21 将冒号 “:” 视为分割符

# awk -F: '{print $1,$7}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

4.22 将冒号 “:” 和句号 “.” 视为分割符

# awk -F [:.] '{print $1,$10}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

4.23 将逗号 “,” 作为分隔符但是处于两个双引号 “””” 之间的逗号 “,” 不作为分隔符,并显示其中的第 2 列 (可以将包含特定符号的多列示为 1 列)

# awk 'BEGIN{FPAT="[^,]+|\".*\""}{print $2}' test.txt


补充:
1) FPAT 函数的作用是:使用正则表达式进行匹配,将匹配成功的部分作为列
3) 这里以不包含逗号 “,” 和两个双引号 “” 包括两个双引号 “” 里的内容作为列
2) 这里的 test.txt 是要被 awk 操作的测试文件

4.24 当第 2 列出现多个相同的内容时只显示第 1 次出现的行 (也就是以第 2 列为基准进行去重)

# awk '{arr[$2]=arr[$2]+1;if(arr[$2]==1){print}}' test.txt

或者:

# awk '{arr[$2]++;if(arr[$2]==1){print}}' test.txt

或者:

# awk '{++arr[$2];if(arr[$2]==1){print}}' test.txt

或者:

# awk '!arr[$2]++{print}' test.txt


补充:
1) 当把 arr[$2] 放到大括号 “{}” 外面时它自身就是 1 个判断,当 arr[$2] 第 1 次遭遇某个内容时,arr[$2] 此时的值是 0 ,然后才进行 ++ (也就是加 1),而当 arr[$2] 的值是 0 时,给他取反的 !arr[$2] 就是布尔真,而布尔为真时就执行 {print}
2) 当后来 arr[$2] 不是第 1 次遭遇某个内容时,arr[$2] 的值就不是 0 了,而给他取反的 !arr[$2] 就是布尔假了,也就不会再执行后面的 {print}
3) 以此类推
4) 这里的 test.txt 是要被 awk 操作的测试文件

4.25 显示所有非空的行

# ifconfig | awk 'BEGIN{RS=""}{print}'

4.26 显示第 1 个段落

# ifconfig | awk 'BEGIN{RS=""}NR==1{print}'

4.27 显示 IP 地址

# ifconfig | awk '/inet/&&!($2~/^127/){print $2}'

或者:

# ifconfig | awk 'BEGIN{RS=""};!/127.0.0.1/{print $6}'

或者:

# ifconfig | awk 'BEGIN{RS="";FS="\n"}!/127.0.0.1/{$0=$2;FS=" ";$0=$0;print $2}'


补充:
1) 执行 RS=”” 每次读取 1 个段落
2) 执行 !/lo/ 不读取带 lo 的段落
3) 执行 FS=”\n” 将换行符 “\n” 设置为分隔符
4) 执行 !/127.0.0.1/ 只选择不带 127.0.0.1 的段落
5) 执行 $0=$2 将此段落里的内容换成第 2 行的内容 ($0 代表所有内容),设置了 FS 之后,需要重新给 $0 赋值以后才能让 FS 的新值生效
6) 执行 FS=” ” 将空格 “ ” 设置为分隔符
7) 执行 $0=$0 让所有内容等于所有内容 ($0 代表所有内容),设置了 FS 之后,需要重新给 $0 赋值以后才能让 FS 的新值生效
8) 执行 print $2 打印第 2 列

或者:

# ip a s | awk '/[1-2]?[0-9]{0,2}\.[1-2]?[0-9]{0,2}/&&!/127.0.0.1/{print $2}'

4.28 显示从包含 1.eternalcenter.com 的行到包含 5.eternalcenter.com 的行的所有内容

# awk '~/1.eternalcenter.com/,~/5.eternalcenter.com/' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

4.29 显示正数从 [mysql] 的行开始到下 1 个中括号 “[]” 出现前出现的所有内容

# vim awk.txt

创建以下内容:

index($0, "[mysql]"){
    print
    while( (getline var) > 0 ){
        if(var ~ /\[.*\]/){exit}
        print var
    }
}
# awk -f awk.txt /etc/my.cnf


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 执行 index($0, “[mysql]”),搜索 $0 中内容包含 [mysql] 的行 ($0 代表所有内容)
3) 执行 print 将匹配到的这 1 行显示出来
4) getline 有 3 类返回值,当返回值大于 0 时,表示已经读取到了内容。当返回值等于 0 时,表示没有读取到内容,第一行就是 EOF 结尾。当返回值小于 0 时,则代表没读取内容报错
5) 执行 while( (getline var) > 0 ){print var},从 [mysql] 这 1 行往下第 1 行开始往下 1 行 1 行地读取内容,并形成循环,每读取有 1 行,就将内容赋值给变量 var,且 getline 的返回值就为 1,确定 (getline var) 的返回值大于 0 时将 var 的内容显示出来
6) 执行 if(var ~ /[.*]/){exit} 如果匹配到变量 var 里的内容包含中括号了 “[]” 则显示出来
7) 这里的 /etc/my.cnf 是要被 awk 操作的测试文件

或者:

# echo '[mysql]' ; awk 'index($0, "[mysql]"){while((getline var)>0){if(var ~/\[.*\]/){exit} print var}}' /etc/my.cnf


补充:
1) 执行 echo ‘[mysql]’ 显示 echo ‘[mysql]’
2) 执行 index($0, “[mysql]”),搜索 $0 中内容包含 [mysql] 的行 ($0 代表所有内容)
3) getline 有 3 类返回值,当返回值大于 0 时,表示已经读取到了内容。当返回值等于 0 时,表示没有读取到内容,第一行就是 EOF 结尾。当返回值小于 0 时,则代表没读取内容报错
4) 执行 while( (getline var) > 0 ){print var},从 [mysql] 这 1 行往下第 1 行开始往下 1 行 1 行地读取内容,并形成循环,每读取有 1 行,就将内容赋值给变量 var,且 getline 的返回值就为 1,确定 (getline var) 的返回值大于 0 时将 var 的内容显示出来
5) 执行 if(var ~ /[.*]/){exit} 如果匹配到变量 var 里的内容包含中括号了 “[]” 则显示出来
6) 这里的 /etc/my.cnf 是要被 awk 操作的测试文件

4.30 将上斜号加星号 “/*” 和星号加上斜号 */ 里的内容视为注释,并省略掉里面的内容显示其他内容

# vim awk.txt

创建以下内容:

index($0, "/*"){
    #if in the same line with */
    if (index ($0,"*/")){
        print gensub("^(.*)\\/\\*.*\\*\\/(.*)$","\\1\\2","g",$0)
     }
 
    else {

    #if not in the same line with  */

    #display the content before /*
    print gensub("^(.*)/\\*.*","\\1","g",$0)

    #alaways read until encountered */
        while( (getline var) > 0 ){
            if(index(var,"*/")){
                print gensub("^.*\\*/(.*)","\\1","g",var)
                break
            }
        }
    }
}

!index($0, "/*"){
    print
}
# awk -f awk.txt test.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 这里的 test.txt 是要被 awk 操作的测试文件
3) gensub 函数的格式是:gensub(“”,””,”g”,),作用是:将字符串中的某些字符进行替换
4) getline 有 3 类返回值,当返回值大于 0 时,表示已经读取到了内容。当返回值等于 0 时,表示没有读取到内容,第一行就是 EOF 结尾。当返回值小于 0 时,则代表没读取内容报错
5) 执行 while( (getline var) > 0 ){print var},从 [mysql] 这 1 行往下第 1 行开始往下 1 行 1 行地读取内容,并形成循环,每读取有 1 行,就将内容赋值给变量 var,且 getline 的返回值就为 1,确定 (getline var) 的返回值大于 0 时将 var 的内容显示出来

4.31 将 2 段分别包含 i-order 内容和 fail 内容并以中括号 “[” 结尾的段落视为 1 段并显示

# vim awk.txt

添加以下内容:

BEGIN{
    RS="]\n"
    ORS=RS
}

{
    if($0 ~ /ok/ && prev ~ /order/){
        print prev
        print $0
    }
    prev = $0
}
# awk -f awk.txt test.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 这里的 test.txt 是要被 awk 操作的测试文件

4.32 显示被执行的文件 test.txt

# awk '{print FILENAME}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

内容五:awk 替换显示的案例

5.1 将第 1 列为 root 的行的第 3 列替换成 “1” 后显示,并且将冒号 “:” 视为分割符

# awk -F: '$1=="root"{ $3 = '1';print  }' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

5.2 将第 4 列里的横杠 “-” 替换成空值后显示

# echo "a- b c 2022-01-01 a:d" | awk 'gsub(/-/,"",$4)'

5.3 统计第 4 列里的横杠 “-” 的次数并把第 4 列替换统计后的次数后显示

# echo "z m 7 2022-01-01 a:d" | awk '$4=gsub(/-/,"",$4)'

5.4 统计此行中横杠 “-” 出现的次数并将第 4 列替换统计后的次数后显示

# echo "z- m y 2021-11-11 z:y" | awk '$4=gsub(/-/,"")'
z m y 3 z:y

5.5 将全部横杠 “-” 替换成空值后显示

# echo "z m y z-m-y 2022-01-01 z:y" | awk 'gsub(/-/,"")'
z m y zmy 20211111 z:y

或者:

# echo eternal-center-zmy | awk 'BEGIN{ FS="-";}{print $1,$2,$3}'
eternal center zmy

或者:

# echo eternal-center-zmy | awk '{split ($0,a,"-");print a[1],a[2],a[3]}'
eternal center zmy

5.6 将全部横杠 “-” 和冒号 “:” 替换成空值后显示

# echo "a- b c 2022-01-01 a:d" | awk '{gsub(/-|:/, "");print}'

5.7 将全部横杠 “-” 替换成 3 个星号 “***” 后显示

# echo eternal-center-zmy | awk 'BEGIN{ FS="-";OFS="***"}{print $1,$2,$3}'
eternal***center***zmy

5.8 给每 1 行前面添加行号后显示

# cat test.txt 
eternalcenter.com-ec-myz
eternalcentre.com-ec-myz
eternalcenter.org-ec-myz
eternalcentre.org-ec-myz

# awk 'BEGIN{ OFS="." }{ print NR,$0 }' test.txt 
1.eternalcenter.com-ec-myz
2.eternalcentre.com-ec-myz
3.eternalcenter.org-ec-myz
4.eternalcentre.org-ec-myz

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.9 在 a b c d 中的 b 后面插入 e f g

# echo "a b c d" | awk '{$2=$2" e f g";print}'

(补充:修改字段或 NF 的值后 $0 会进行重建并进行联动效应)

5.10 去除字符串中多余的空格,每个字节只用 1 个空格隔开

# echo "a     b c        d" | awk '{$2=$2;print}'

(补充:OFS 默认等于一个空格 “ ”,这里修改了 $2 的值以后, awk 会根据 OFS 的值重建 $0 ($0 代表所有内容),它会去使用 OFS 的值去链接各个字段)

5.11 去除字符串中多余的空格,每个字节使用多个个空格隔开

# echo "a     b c        d" | awk 'BEGIN{OFS="\t"}{$1=$1;print}'

(补充:OFS 默认等于 1 个空格 “ ”,在这里先试将 OFS 的值修改成了 “\t”,再修改了 $2 的值以,此时 awk 会根据 OFS 的值重建 $0 ($0 代表所有内容),它会去使用 OFS 的值去链接各个字段)

5.12 以竖杠 “|” 作为分割符将第 2 列的数字去掉后显示

# cat test.txt 
eternalcenter.com|201901ec|com
eternalcentre.com|201902ec|com
zhumingyu.com|201903zmy|com
mingyuzhu.com|201904myz|com
eternalcenter.org|201905ec|org
eternalcentre.org|201906ec|org


# awk -F '|' 'BEGIN{ OFS="|" }{sub(/[0-9]+/,"",$2);print$0}' test.txt 
eternalcenter.com|ec|com
eternalcentre.com|ec|com
zhumingyu.com|zmy|com
mingyuzhu.com|myz|com
eternalcenter.org|ec|org
eternalcentre.org|ec|org

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

或者:

# cat test.txt 
eternalcenter.com|201901ec|com
eternalcentre.com|201902ec|com
zhumingyu.com|201903zmy|com
mingyuzhu.com|201904myz|com
eternalcenter.org|201905ec|org
eternalcentre.org|201906ec|org

# awk -F '|' -v OFS='|' '{sub(/[0-9]+/,"",$2);print $0}' test.txt 
eternalcenter.com|ec|com
eternalcentre.com|ec|com
zhumingyu.com|zmy|com
mingyuzhu.com|myz|com
eternalcenter.org|ec|org
eternalcentre.org|ec|org

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.13 将句号 “.” 替换成换行符后显示

# cat test.txt 
m.y.z

# awk 'BEGIN{RS=".";}{print $0}' test.txt 
m
y
z

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.14 将换行符替换成句号 “.” 后显示

# cat test.txt 
m
y
z


# awk 'BEGIN{ORS=".";}{print $0}' test.txt 
m.y.z.

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.15 awk 行列转换 (每 1 行最前面有 1 个空格)

# awk '{for(i=1;i<=NF;i++){arr[i]=arr[i]" "$i}}END{for(i=1;i<=NF;i++){print arr[i]}}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.16 awk 行列转换

# awk '{for(i=1;i<=NF;i++){if(i in arr){arr[i]=arr[i]" "$i}else{arr[i]=$i}}}END{for(i=1;i<=NF;i++){print arr[i]}}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.17 行列转换,并且将第 1 行相同的列,行列转换以后合并,也就是行列转换以后没有第 1 列相互重复的行 (多个第 1 列相同的行,后面的行会在去除第 1 列后将剩余的列加在上 1 行的后面)

# awk '{for(i=1;i<=NF;i++){if(i in arr){arr[i]=arr[i]" "$i}else{arr[i]=$i}}}END{for(i=1;i<=NF;i++){print arr[i]}}' test.txt | sort | sed -e :a -e '$!N;/^\(.....\).*\n\1.*/s/^\(.*\)\(\n\)\(.....\)\(.*\)/\1\4/g; ta'

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.18 行列转换,并且将第 1 行相同的列,行列转换以后合并,也就是行列转换以后没有第 1 列相互重复的行 (指定 1 列是以 .com 结尾)

# awk '{for(i=1;i<=NF;i++){if(i in arr){arr[i]=arr[i]" "$i}else{arr[i]=$i}}}END{for(i=1;i<=NF;i++){print arr[i]}}' test.txt | sort | sed -e :a -e '$!N;/^\(.*\.com\).*\n\1.*/s/^\(.*\)\(\n\)\(.*\.com\)\(.*\)/\1\4/g; ta'

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

5.19 行列转换,并且将第 1 行相同的列,行列转换以后合并,也就是行列转换以后没有第 1 列相互重复的行 (指定 1 列是以空格 “ ” 结尾)

# awk '{for(i=1;i<=NF;i++){if(i in arr){arr[i]=arr[i]" "$i}else{arr[i]=$i}}}END{for(i=1;i<=NF;i++){print arr[i]}}' test.txt | sort | sed -e :a -e '$!N;/^\(.*\ \).*\n\1.*/s/^\(.*\)\(\n\)\(.*\ \)\(.*\)/\1 \4/g; ta'

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

内容六:awk 导入变量的案例

6.1 将 date 命令的运行结果,通过 getline 赋值给 d ,之后将 d 的值显示出来

# awk 'BEGIN {"date"|getline d; print d}'

6.2 将 eternalcenter 的值赋予给变量 content,并将变量 content 的值显示出来

# content=eternalcenter
# awk '{print "$content"}'
content
# awk '{print '$content'}'
eternalcenter

(注意:awk 在使用变量时必须使用单引号 “””,否则会把变量名称当作字符串)

6.3 将 eternalcenter 的值赋予变量 value,再将变量 value 的值赋予给变量 content,并将变量 content 的值显示出来

# value=eternalcenter
# echo | awk -v content=$value '{print(content)}'
eternalcenter

6.4 将 mingyuzhu 的值赋予变量 n,显示第 1 列等于变量 n 的值的行第 1 列、第 2 列和第 3 列

# gawk -F: '($1 == n) {print $1,$2,$3}' n=mingyuzhu /etc/group
(补充:这里的 /etc/group 是要被 gawk 操作的测试文件)

内容七:awk 变量判断的案例

如果变量 value 的值为 eternalcenter 则显示 0 否则显示 1

# echo eternalcenter | awk -v value="eternalcenter" '{print($1==value)? "0":"1"}'

或者:

# echo eternalcenter | awk -v value="eternalcenter" 'BEGIN{if($1==value){result=0}else(result=1)} END{print result}'

内容八:awk 常量显示的案例

# awk -F: '{print $1,"interpreter:",$7}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

内容九:awk 匹配显示的案例

9.1 显示包含 Failed 的行的第 11 列

# awk '/Failed/{print $11}' /var/log/secure

或者:

# awk 'index($0,"Failed"){print $11}' /var/log/secure

(补充:这里的 /var/log/secure 是要被 awk 操作的测试文件)

9.2 显示以 bash 结尾的行,并且将冒号 “:” 视为分割符

# awk -F: '/bash$/{print}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

9.3 显示以 root 或者 adm 开头的行的第 1 列和第 3 列,并且将冒号 “:” 视为分割符

# awk -F: '/^(root|adm)/{print $1,$3}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

9.4 显示以 r 或者 a 开头的行的第 1 列和第 3 列,并且将冒号 “:” 视为分割符

# awk -F: '/^[ra]/{print $1,$3}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

9.5 显示第 1 列包含 root 的行,并且将冒号 “:” 视为分割符

# awk -F: '$1~/root/' /etc/passwd

或者:

# awk -F: '$1 ~ /root/' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

9.6 显示第 7 列不以 nologin 结尾的行的第 1 列和第 7 列,并且将冒号 “:” 视为分割符

# awk -F: '$7!~/nologin$/{print $1,$7}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

9.7 显示前 1 个命令包含 Rx p 的行的第 5 列

# ifconfig eth0 | awk '/RX p/{print $5}'

9.8 显示前 1 个命令以 / 结尾的行的第 4 列

# df -h | awk '/\/$/{print $4}'

9.9 显示第 1 列以两个数字结尾的行的第 1 列,并且将冒号 “:” 视为分割符

# awk -F: '$1 ~/[0-9][0-9]$/(print $1}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

9.10 显示以 root 开头到以 mysql 开头的所有行的第 1 列,并且将冒号 “:” 视为分割符

# awk -F '/^root/,/^mysql/{print $1}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

内容十:awk 使用比较符号匹配的案例

10.1 显示行号等于 3 的行,并且将冒号 “:” 视为分割符

# awk -F: 'NR==3{print}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.2 显示第 3 列数值等于 1000 的行的第 1 列和第 3 列,并且将冒号 “:” 视为分割符

# awk -F: '$3==1000{print $1,$3}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.3 显示第 3 列数值不等于 1000 的行的第 1 列和第 3 列,并且将冒号 “:” 视为分割符

# awk -F: '$3!=1000{print $1,$3}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.4 显示第 3 列数值大于 1000 的行的第 1 列和第 3 列,并且将冒号 “:” 视为分割符

# awk -F: '$3>=1000{print $1,$3}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.5 显示第 3 列数值小于 10 的行的第 1 列和第 3 列,并且将冒号 “:” 视为分割符

# awk -F: '$3<10{print $1,$3}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.6 显示第 3 列数值大于 10 并且小于 20 的行,并且将冒号 “:” 视为分割符

# awk -F: '$3>10 && $3<20' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.7 显示第 3 列数值大于 1000 或者小于 10 的行,并且将冒号 “:” 视为分割符

# awk -F: '$3>1000 || $3<10' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.8 显示第 3 列乘以第 4 列小于 100 的行,并且将冒号 “:” 视为分割符

# awk -F: '$3 * $4 <100' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.9 显示第 1 列为 root 的行,并且将冒号 “:” 视为分割符

# awk -F: '$1=="root"' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

10.10 显示第 1 列是 test1 且第 2 列是 test2 的行

# awk '{if($1=="test1"&&$2=="test2"){print}}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

10.11 显示第 1 列包含 test1 且第 2 列包含 test2 的行

# awk '$1~/test1/&&$2~/test2/' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

10.12 显示第 1 列是 test1 或者第 2 列是 test2 的行

# awk '{if($1=="test1"||$2=="test2"){print}}' test.txt

或者:

# awk '{if($1=="test1"){print};if($2=="test2"){print}}' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

10.13 显示第 1 列包含 test1 或者第 2 列包含 test2 的行

# awk '$1~/test1/;$2~/test2/' test.txt

或者:

# awk '$1~/test1/||$2~/test2/' test.txt

(补充:这里的 test.txt 是要被 awk 操作的测试文件)

内容十一:awk 计算的案例

11.1 0 + 1

# awk 'BEGIN{print x+1}'

或者:

# awk 'BEGIN{x++;print x}'

(注意:这里的 x 可以不定义,直接用,默认值为 0)

11.2 8 + 2

# awk 'BEGIN{x=8;print x+=2}'

(注意:这里的 x 可以不定义,直接用,默认值为 0)

11.3 8 – 1

# awk 'BEGIN{x=8;x--;print x}'

(注意:这里的 x 可以不定义,直接用,默认值为 0)

11.4 2 + 3

# awk 'BEGIN{print 2+3}'

11.5 3.2 + 3.5

# awk 'BEGIN{print 3.2+3.5}'

11.6 2 * 3

# awk 'BEGIN{print 2*3}'

11.7 2 乘 3 再加 3

# awk 'BEGIN{print 2*3+3}'

11.8 3 加 3 再乘 2

# awk 'BEGIN{print (3+3)*2}'

11.9 求 23 除 8 的余数

# awk 'BEGIN{print 23%8}'

11.10 显示第 3 加上 10 的结果,并且将冒号 “:” 视为分割符

# awk -F: '$3 + 10' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

11.11 找 200 以内 3 的倍数

# seq 200 | awk '$1%3==0'

11.12 找 100 以内 7 的倍数或者包含 7 的数

# seq 100 | awk '$1%7==0||$1~/7/'

11.13 将第 4 列求和后显示,并且将冒号 “:” 视为分割符

# awk -F: 'BEGIN{total=0}{total+=$4}END{print total}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

11.14 当第 2 列中,以横杠 “-” 作为分隔符的第 2 列的内容等于 01,则将此行的第 3 列的数值相加,并以第 1 列作为基准进行显示 (统计员工 1 月份的消费之和)

# cat test.txt 
Tom 2022-01-01 100
John 2022-02-05 300
Tom 2022-03-20 400
John 2022-01-22 200
Tom 2022-01-05 500
# awk '{split($2,a,"-");if(a[2]==01){b[$1]+=$3}}END{for(i in b)print i,b[i]}' test.txt

内容十二:awk 计数的案例

12.1 计算以 bash 结尾的行数

# awk 'BEGIN{x=0}/bash$/{x++} END{print x}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

12.2 计算第 3 列小于等于 1000 的行数,并且将冒号 “:” 视为分割符

# awk -F: '{if($3<=1000){i++}}END{print i}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

12.3 计算 3 列大于 1000 的行数,并且将冒号 “:” 视为分割符

# awk -F: '{if($3>1000){i++}}END{print i}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

12.4 计算第 7 列以 bash 结尾的行数,并且将冒号 “:” 视为分割符

# awk -F: '{if($7~/bash$/){i++}}END{print i}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

12.5 计算第 3 列数值和第 4 列数值的和并赋值给变量,再将变量的值显示出来,并且将冒号 “:” 视为分割符

# awk -F: '{result=$3+$4; printf result}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

12.6 计算第 3 列数值除以第 4 列数值再乘以 100.0

# awk '{print $3/$2 * 100.0}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

12.7 计算第 1 列所有值各自总共出现的次数

# awk '{a[$1]++}END{for(i in a){print i,a[$i]}}' /usr/local/nginx/logs/access.log

(补充:这里的 /usr/local/nginx/logs/access.log 是要被 awk 操作的测试文件)

12.8 统计第 4 列里的横杠 “-” 的次数并把第 4 列替换统计后的次数后显示

# echo "z m 7 2022-01-01 a:d" | awk '$4=gsub(/-/,"",$4)'

12.9 统计此行中横杠 “-” 出现的次数并将第 4 列替换统计后的次数后显示

# echo "z- m y 2021-11-11 z:y" | awk '$4=gsub(/-/,"")'
z m y 3 z:y

案例十三:awk if 判断语句计数的案例

13.1 计算第 3 列小于等于 1000 的行数和第 3 列大于 1000 的行数,并且将冒号 “:” 视为分割符

# awk -F: '{if($3<=1000){i++}else{j++}} END{print i,j}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

13.2 计算第 7 列以 bash 结尾的行数和第 7 列不以 bash 结尾的行数,并且将冒号 “:” 视为分割符

# awk -F: '{if($7~/bash$/){i++}else{j++}} END{print i,j}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

13.3 如果第 3 列等于 0 则显示 “root”,否则显示 “not root”,并且将冒号 “:” 视为分割符

# awk -F: '{print ($3=0 ? "root": not root)}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

13.4 如果第 3 列大于 1000 则显示第 3 列的值,否则显示 “system id”,并且将冒号 “:” 视为分割符

# awk -F: 'BEGIN{max=1000}{id=($3>max ?$3 :"system id");print id}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

13.5 如果第 1 列的字符串的长度大于 3 则显示第 1 列的内容

# awk '{if (length($1) > 3) print $1}' test.txt

13.6 当第 2 列等于大写的 “A” 或者小写的 “a” 时则显示此行的第 2 列

# awk '{IGNORECASE=1;if($2=="a"){print $2}}' test.txt

内容十四:awk for 循环统计的案例

14.1 创建数组 a,其中 a[0]=0 a[1]=11 a[2]=22,并且将数组 a 显示出来

# awk 'BEGIN{a[0]=0;a[1]=11;a[2]=22;for(i in a){print i,a[i]}}'

14.2 统计所有的 IP 地址以及每 1 个 IP 地址访问的次数

# awk '{ip[$1]++} END{for(i in ip) {print i,ip[i]}}' /var/log/httpd/access_log


补充:
1) 这里的 /var/log/httpd/access_log 是要被 awk 操作的测试文件
2) 这里以统计 /var/log/httpd/access_log 文件里第 1 列的 IP 地址为例

14.3 统计所有的 IP 地址以及每 1 个 IP 地址访问的次数,并以从多到少的顺序进行排序

# awk '{ip[$1]++} END{for(i in ip) {print i,ip[i]}}' /var/log/httpd/access_log | sort -k2nr


补充:
1) 这里的 /var/log/httpd/access_log 是要被 awk 操作的测试文件
2) 这里以统计 /var/log/httpd/access_log 文件里第 1 列的 IP 地址为例

14.4 统计所有的 IP 地址以及每 1 个 IP 地址非 200 状态访问的次数

# awk '$8!=200{ip[$1]++}END{for(i in ip){print ip[i],i}}' /var/log/httpd/access_log

(
补充:
1) 这里的 /var/log/httpd/access_log 是要被 awk 操作的测试文件
2) 这里以统计 /var/log/httpd/access_log 文件里第 1 列的 IP 地址为例
)

14.5 统计所有的 IP 地址以及每 1 个 IP 地址非 200 状态访问的次数,并只显示数量最多的前 10 个 IP 地址和其对应的次数

# awk '$8!=200{ip[$1]++}END{for(i in ip){print ip[i],i}}' /var/log/httpd/access_log | sort -k1nr | head -n

(
补充:
1) 这里的 /var/log/httpd/access_log 是要被 awk 操作的测试文件
2) 这里以统计 /var/log/httpd/access_log 文件里第 1 列的 IP 地址为例
)

或者:

# awk '$8!=200{ip[$1]++}END{PROCINFO["sorted_in"]="@val_num_desc";for(i in ip){if(count++=10){exit}print ip[i],i}}' /var/log/httpd/access_log

(
补充:
1) 这里的 /var/log/httpd/access_log 是要被 awk 操作的测试文件
2) 这里以统计 /var/log/httpd/access_log 文件里第 1 列的 IP 地址为例
3) 这里的 PROCINFO[“sorted_in”]=”@val_num_desc” 表示在 for(i in ip) 时通过 ip[i] 的数值进行降序排序,也就是 ip 数组中,最大的一个值会排在前面

14.6 统计 TCP 连接出现的次数

# ss -ntulap 2> /dev/null | awk '/^tcp/{tcp[$6]++} END{for(i in tcp){print i,tcp[i]}}'

或者:

# ss -ntulap 2> | grep 'tcp' | awk '{print $6}' | sort | uniq -c

14.7 统计每个网页被各个 IP 地址访问的次数,并以每个网页和 IP 地址作为文件名创建文件,里面存储着此网页被访问的 IP 地址和对应的访问次数

# vim awk.txt

创建以下内容:

BEGIN{
    FS=" "
}
{
    if(!arr[$1,$2]++){
        arr1[$1]++
    }
}

END{
    for(i in arr){
        print i,arr[i] >(i".txt")
    }
}
# awk -f awk.txt test.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 这里的 test.txt 是要被 awk 操作的测试文件
3) 这里以统计 test.txt 文件里第 1 列的网页和第 2 列第 IP 地址为例
4) 这里当把 arr[$1,$2] 放到 if 的小括号 “()” 里面时,它是 1 个判断,当 arr[$1,$2] 第 1 次遭遇某个内容时,arr[$1,$2] 此时的值是 0 ,然后才进行 ++ (也就是加 1),而当 arr[$1,$2] 的值是 0 时,给他取反的 !arr[$1,$2] 就是布尔真,而布尔为真时就执行 arr1[$1]++
5) 当后来 arr[$1,$2] 不是第 1 次遭遇某个内容时,arr[$1,$2] 的值就不是 0 了,而给他取反的 !arr[$1,$2] 就是布尔假了,也就不会再执行后面的 arr1[$1]++

或者:

# vim awk.txt

创建以下内容:

BEGIN{
    FS=" "
}

!arr[$1,$2]++ {
    arr1[$1]++
}

END{
    for(i in arr){
        print i,arr[i] >(i".txt")
    }
}
# awk -f awk.txt test.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 这里的 test.txt 是要被 awk 操作的测试文件
3) 这里以统计 test.txt 文件里第 1 列的网页和第 2 列第 IP 地址为例
4) 这里当把 arr[$1,$2] 放到大括号 “{}” 外面时它自身就是 1 个判断,当 arr[$1,$2] 第 1 次遭遇某个内容时,arr[$1,$2] 此时的值是 0 ,然后才进行 ++ (也就是加 1),而当 arr[$1,$2] 的值是 0 时,给他取反的 !arr[$1,$2] 就是布尔真,而布尔为真时就执行 arr1[$1]++
5) 当后来 arr[$1,$2] 不是第 1 次遭遇某个内容时,arr[$1,$2] 的值就不是 0 了,而给他取反的 !arr[$1,$2] 就是布尔假了,也就不会再执行后面的 arr1[$1]++

14.8 将每 1 列的值前面加上所属行的行号,按行单独显示出来

# awk -F: '{i=1;while(i<NF) {print NR,$i;i++}}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

或者:

# awk -F: '{for(i=1;i<NF;i++)  {print NR,$i}}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

14.9 显示九九乘法表

# seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'


补充:
1) 执行 seq 9 命令,第 1 行输出 1,第 2 行输出 2,以此类推直到第 9 行,并将结果传输给 sed 命令
2) 执行 sed 的 H 参数,将 pattern space (模型空间) 中的内容 append (添加) 到 hold space (保持空间) 分行符 “/n” 后,并将此时的 pattern space (模型空间) 传输给 g 参数。当执行到第 1 行时,添加到 hold space (保持空间) 是 1,hold space (保持空间) 里的值是 \n1,当时执行到第 2 行时,添加到 hold space (保持空间) 是 2,hold space (保持空间) 里的值是 \n1\n2,依次类推
3) 执行 sed 的 g 参数,将 hold space (保持空间) 中的内容拷贝到 pattern space (模型空间) 中,原来 pattern space (模型空间) 里的内容被清除。当执行到第 1 行时,hold space (保持空间)里的值是 \n1,pattern space (模型空间) 里的值将是 \n1,显示的结果也会是 1,当执行到第 2 行时,hold space (保持空间)里的值是 \n1\n2,pattern space (模型空间) 里的值将是 \n1\n2,显示的结果也会是 \n1\n2,依次类推,并将结果传输给命令 awk
3) awk,将多行视为 1 行,之后执行从 1 到此行列数次循环,变量 i 初始时的值为 1,每执行 1 次则变量 i 到值会加 1,每次循环时会显示:“此时变量 i 的值”*“此行的列数”=“此时变量 i 的值和此行的列数的乘积““当此时变量 i 的值等于此行的列数时则换行,否则的话则显示制表符的空格长度”。当执行到第 1 行时显示的是 1×1=1,当执行到第 2 行时显示的是 1×2=2 2×2=4,以此类推

内容十五:awk 表格显示的案例

在开始显示自定义的 1 行,在结尾再显示自定义的 1 行,并且将 “:” 视为分割符

# awk -F: 'BEGIN{print "User\tUID\tHome"}{print $1 "\t"  $3  "\t"  $6}END{print "Total",NR,"lines."}' /etc/passwd

(补充:这里的 /etc/passwd 是要被 awk 操作的测试文件)

内容十六:awk 精确时间处理的案例

16.1 将当前系统转换成 epoch 值

# awk 'BEGIN{print systime()}'

16.2 将 2019 年 01 月 01 日 01 时 01 分 01 秒转换成 epoch 值

# awk 'BEGIN{print mktime("2019 01 01 01 01 01")}'

或者:

# awk 'BEGIN{print mktime("2019 1 1 1 1 1")}'

或者:

# vim awk.txt

创建以下内容:

BEGIN{
    str="2019-01-01T01:01:01+8:00"
    patsplit(str,arr,"[0-9]{1,4}")
    Y=arr[1]
    M=arr[2]
    D=arr[3]
    H=arr[4]
    m=arr[5]
    S=arr[6]
    print mktime(sprintf("%s %s %s %s %s",Y,M,D,H,m,S))}
}
# awk -f awk.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) patsplit 函数的格式是:patsplit(<string>,<array>,”<regular expression>”),其中第 1 个是要匹配的字符串,第 2 个是要匹配结果存储进去的数组,第 3 个是正则表达式,通过正则表达式匹配字符串,然后将匹配成功的部分保存到数组中去
3) mktime 函数的格式是:mktime(‘YYYY MM DD HH mm SS [D9T]’),作用是:构建时间,如果构建成功则显示这个时间点秒级的 epoch 值,如果构建失败则返回 -1

16.3 将 2019 年 01 月 01 日 01 时 01 分 01 秒的时间点转换成 epoch 值

# date -d '@1546275661' +"%F %T"

16.4 显示从 2021-01-01T01:01:01+08:00 开始到结尾的所有日志,以时间被中括号扩起来作为时间到格式,例如:[2021-01-01T01:01:01+8:00] (处理只包含数字的时间)

# vim awk.txt

创建以下内容:

BEGIN{
  str="2021-01-01T01:01:01+08:00"
  choose_time = mktime("2021 01 01 01 01 01")
}

function strptime_now(str   ,arr,Y,M,D,H,m,S) {
  patsplit(str,arr,"[0-9]{1,4}")
  Y=arr[1]
  M=arr[2]
  D=arr[3]
  H=arr[4]
  m=arr[5]
  S=arr[6]
  return mktime(sprintf("%s %s %s %s %s %s",Y,M,D,H,m,S))
}

{
  #[2022-01-01T01:01:01+8:00]
  match($0,"^.*\\[(.*)\\].*",arr)
  tmp_time = strptime_now(arr[1])
  if(tmp_time > choose_time){
    print
  }
}
# awk -f awk.txt test.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 这里的 test.txt 是要被 awk 操作的测试文件
3) 默认情况下 Y=arr[1] 等是在定义全局变量,这里的 strptime(str ,arr,Y,M,D,H,m,S) 是指将全局变量转换成局部变量,这样此函数执行完以后变量就被删除了
4) patsplit 函数的格式是:patsplit(<string>,<array>,”<regular expression>”),其中第 1 个是要匹配的字符串,第 2 个是要匹配结果存储进去的数组,第 3 个是正则表达式,通过正则表达式匹配字符串,然后将匹配成功的部分保存到数组中去
5) mktime 函数的格式是:mktime(‘YYYY MM DD HH mm SS [D9T]’),作用是:构建时间,如果构建成功则显示这个时间点秒级的 epoch 值,如果构建失败则返回 -1
6) sprintf 函数的格式是:sprintf(“s%……”,<variable>……),作用是:显示变量里的内容,其中第 1 个是占位符,第 2 个是变量
7) match 函数的格式是:match(<column>,”<regular expression>”,<array>),其中第 1 个是要匹配的列,第 2 个是正则表达式,第 3 个是要匹配结果存储进去的数组,作用是:以正则表达式匹配匹配某列,并将匹配结果存储在数组里

16.5 显示从 01/Jan/2022:21:30:30+8:00 开始到结尾的所有日志,以时间被中括号扩起来作为时间到格式,例如:[01/Jan/2022:21:30:30+8:00] (处理只包含英文月份的时间)

# vim awk.txt

创建以下内容:

BEGIN{
  str="01/Jan/2022:21:30:30+8:00"
  choose_time = mktime("2020 01 01 01 01 01")
}

{
  #[01/Jan/2022:21:30:30+8:00]
  match($0,"^.*\\[(.*)\\].*",arr)
  tmp_time = strptime(arr[1])
  if(tmp_time > choose_time){
    print
  }
}

# 01/Jan/2022:21:30:30+8:00
function strptime(str   ,dt_str,arr,Y,M,D,H,m,S) {
  dt_str = gensub("[/:+-]"," ","g",str)
  # 01 Jan 2020 01 01 01 01 01
  split(dt_str,arr," ")
  Y=arr[3]
  M=mon_map(arr[2])
  D=arr[1]
  H=arr[4]
  m=arr[5]
  S=arr[6]
  return mktime(sprintf("%s %s %s %s %s %s",Y,M,D,H,m,S))
}

function mon_map(str   ,mons){
  mons["Jan"]=1
  mons["Feb"]=2
  mons["Mar"]=3
  mons["Apr"]=4
  mons["May"]=5
  mons["Jun"]=6
  mons["Jul"]=7
  mons["Aug"]=8
  mons["Sep"]=9
  mons["Oct"]=10
  mons["Nov"]=11
  mons["Dec"]=12
  return mons[str]
}
# awk -f awk.txt test.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 这里的 test.txt 是要被 awk 操作的测试文件
3) 默认情况下 Y=arr[1] 等是在定义全局变量,这里的 strptime(str ,arr,Y,M,D,H,m,S) 是指将全局变量转换成局部变量,这样此函数执行完以后变量就被删除了
4) gensub 函数的格式是:gensub(“<original character>”,”<new character>”,”g”,<string>),作用是:将字符串中的某些字符进行替换,其中第 1 个是原字符,第 2 个是新字符,第 3 个是要操作的字符串
5) split 函数的格式是:split(<string>,<array>,”<separator>”),作用是:将字符串里的内容分开存储到数组里,其中第 1 个是要操作的字符串,第 2 个是要存入内容的数组,第 3 个是分隔符
6) mktime 函数的格式是:mktime(‘YYYY MM DD HH mm SS [D9T]’),作用是:构建时间,如果构建成功则显示这个时间点秒级的 epoch 值,如果构建失败则返回 -1
7) sprintf 函数的格式是:sprintf(“s%……”,<variable>……),作用是:显示变量里的内容,其中第 1 个是占位符,第 2 个是变量
8) match 函数的格式是:match(<column>,”<regular expression>”,<array>),其中第 1 个是要匹配的列,第 2 个是正则表达式,第 3 个是要匹配结果存储进去的数组,作用是:以正则表达式匹配匹配某列,并将匹配结果存储在数组里

(注意:在 awk 中反译符号 “\“ 前面还必须要要有一个反译符号 ”\“,例如,如果要显示一个前引号 “””: \”)

内容十七:awk 同时处理 2 个文件的案例

先把第 2 个文件的第 5 列删除,再用第 2 个文件的第 1 列减去第 1 个文件的第 1 列,再把得到的结果粘贴到原来第 2 个文件第 5 列对应的位置

# vim awk.txt

创建以下内容:

{
  f1 = $1
  if( (getline < "test2.txt") >= 0 ){
    $5 = $1 - f1
    print $0
  }
}
# awk -f awk.txt test1.txt


补充:
1) 这里以创建和执行 awk.txt 文件为例
2) 这里的 test1.txt 和 test2.txt 是要被 awk 操作的测试文件
4) getline 有 3 类返回值,当返回值大于 0 时,表示已经读取到了内容。当返回值等于 0 时,表示没有读取到内容,第 1 行就是 EOF 结尾。当返回值小于 0 时,则代表没读取内容报错
5) 执行 getline < “test2.txt” 1 行 1 行地读取 test2.txt 文件里的内容

(注意:这里的 print $0 会根据 OFS 重建 $0,而默认情况下 OFS 是 1 个空格,所以这里重建后,列和列之间也会是 1 个空格)

或者:

# awk 'NR==FNR{arr[FNR]=$1}NR!=FNR{$5 = $1 - arr[FNR];print $0}' test1.txt test2.txt

(补充:这里 NR == FNR 的时候表示处理的是第 1 个文件,只有当处理第 1 个文件中时 NR 等于 FNR)

[内容] Linux RPM 软件包的解压 (判断安装一个 RPM 文件之后哪些目录和文件会被创建)

内容一:RPM 简介

1) RPM 的全名是 RPM Package Manager
1) RPM 是 CentOS、RHEL、openSUSE、SUSE 的软件安装包
2) RPM 是使用 cpio 格式压缩成的包

内容二:解压 RPM 文件的方法

#  rpm2cpio <文件名> | cpio -div

(补充:解压之后就可以看到如果安装这个 RPM 文件的话,有哪些目录和文件会被创建)

[内容] Linux 网络代理的设置 (全局模式)

内容一:Linux 代理环境变量的种类

1) http_proxy
2) https_proxy
3) ftp_proxy
4) socket_proxy
5) all_proxy
6) no_proxy


补充:
1) 变量可以使用通配符
2) 添加多个变量时可以使用 “,” 号分割
3) 不填种类默认以 http 协议传输

内容二:环境变量的格式

2.1 没有用户和密码的格式

export <environment variable>=http://<IP address>:<port>

2.2 有用户和密码的格式

export <environment variable>=https://<user>:<password>@<IP address>:<port>

(注意:如果密码中也有一个 “@” 符号,则需要把 “@” 符号转义一下,转义成 %40)

内容三;可以设置代理变量的文件

1) /etc/profile
2) ~/.bashrc
3) /etc/profile.d/<文件名前缀>.sh

内容四:控制设置的代理

4.1 开启设置的代理

4.1.1 方法一
# reboot
4.1.2 方法二
# source /etc/profile

4.2 显示目前生效的代理

4.2.1 显示 http_proxy 代理
# echo $http_proxy

或者:

# env | grep http_proxy
4.2.2 显示 https_proxy 代理
# echo $https_proxy

或者:

# env | grep https_proxy
4.2.3 显示 ftp_proxy 代理
# echo $ftp_proxy

或者:

# env | grep ftp_proxy
4.2.4 显示 socket_proxy 代理
# echo $socket_proxy

或者:

# env | grep socket_proxy
4.2.5 显示 no_proxy 代理
# echo $no_proxy

或者:

# env | grep no_proxy
4.2.6 显示 all_proxy 代理
# echo $all_proxy

或者:

# env | grep proxy

4.3 取消目前生效的代理

4.3.1 取消 http_proxy 代理
# unset http_proxy
4.3.2 取消 https_proxy 代理
# unset https_proxy
4.3.3 取消 ftp_proxy 代理
# unset ftp_proxy
4.3.4 取消 socket_proxy 代理
# unset socket_proxy
4.3.5 取消 no_proxy 代理
# unset no_proxy
4.3.6 取消 all_proxy 代理
# unset all_proxy

内容五:设置网络代理的案例

5.1 案例一:临时设置网络代理

# export http_proxy=http://8.8.8.8:80

(补充:这里以设置 http 的网络代理,代理不使用无密码,且 IP 和端口为 8.8.8.8:8080 为例)

或者:

# setenv http_proxy=http://8.8.8.8:80

(补充:这里以设置 http 的网络代理,代理不使用无密码,且 IP 和端口为 8.8.8.8:8080 为例)

5.2 案例二:永久设置网络代理

5.2.1 如果是 Rocky Linux & RHEL & openSUSE & SLES
5.2.1.1 修改 /etc/profile 配置文件
# vim /etc/profile

添加以下内容:

……
export http_proxy=http://8.8.8.8:80
export https_proxy=http://admin:123@8.8.8.8:8080
export no_proxy=”localhost, 127.0.0.1, ::1″


补充:这里以设置
1) 这里以设置 http 的网络代理,代理不使用无密码,且 IP 和端口为 8.8.8.8:8080
2) 这里以设置 https 的网络代理,代理使用密码,用户是 admin 密码是 123 ,且 IP 和端口为 8.8.8.8:8080 为例
3) 网络代理不影响 localhost、127.0.0.1 和 ::1
为例

(注意:如果密码中也有一个 “@” 符号,则需要把 “@” 符号转义一下,转义成 %40)

5.2.1.2 让修改的配置文件生效
# source /etc/profile

或者退出后重新登录:

(步骤略)

5.2.1.3 显示目前生效的代理
# echo $http_proxy ; echo $https_proxy
5.2.2 如果是 openSUSE & SLES
5.2.2.1 修改 /etc/sysconfig/proxy 配置文件
# vim /etc/sysconfig/proxy

将部分内容修改如下:

......
PROXY_ENABLED="yes"
......
HTTP_PROXY="http://8.8.8.8:80"
......
HTTPS_PROXY="http://admin:123@8.8.8.8:8080"
......
NO_PROXY="localhost, 127.0.0.1, ::1"
......


补充:这里以设置
1) 开启网络代理
2) 这里以设置 http 的网络代理,网络代理不使用密码,且 IP 和端口为 8.8.8.8:8080
3) 这里以设置 https 的网络代理,网络代理使用密码,用户是 admin 密码是 123 ,且 IP 和端口为 8.8.8.8:8080 为例
4) 网络代理不影响 localhost、127.0.0.1 和 ::1
为例

5.2.2.2 让修改的配置文件生效

退出登录后重新登录:

(步骤略)

5.2.2.3 显示目前生效的代理
# echo $http_proxy ; echo $https_proxy

参考文献:

https://access.redhat.com/solutions/1351253