Skip to main content

Shell 文本和日志 查询统计处理

查询场景案例

# 统计nginx日志 ip访问数量: 输出ip》对ip排序》去重和统计》对统计数量倒序排序》输出前300
awk '{print $1}' ./access.log | sort | uniq -c | sort -nr | head -n 300
# 统计nginx日志 ip访问数量: sort -k2nr 对awk输出的结果进行排序。-k2 表示按第二列进行排序,n 表示按数值排序,r 表示逆序
awk '{ ip_count[$1]++ } END { for (ip in ip_count) print ip, ip_count[ip] }' ./access.log | sort -k2nr
# 解析nginx access日志 并转换为CSV输出
awk 'BEGIN { FS=" \"|\" \"|\" |\""; OFS=","; print "Time","IP","Method","Path","Http","RspCode","RspLength","-","UA"; } { gsub( /\[|\]/, "", $1 ); split( $1, fields1, " "); split( $2, fields2, " "); split( $3, fields3, " "); print fields1[4], fields1[1], fields2[1], fields2[2], fields2[3], fields3[1], fields3[2], $4, sprintf("\"%s\"",$5) }' access.log > access.csv

# 解析nginx access日志 按照IP》Path》Code 来统计请求数量
echo "IP", "Path", "Code", "Count", "TotalCount" > test.csv
awk 'BEGIN { FS=" "; OFS=","; } { gsub( /\?.+/, "", $7 ); gsub( /\/(ar|de|de-de|en|en-ae|en-au|en-bd|en-ca|en-gb|en-in|en-kr|en-mm|en-my|en-ph|en-ro|en-sa|en-sg|en-th|en-us|en-vn|es|es-es|fr|fr-fr|fr-in|fr-ph|hi|hi-in|id|id-id|it|it-it|ja|ja-jp|ko|ko-kr|ms|ms-my|my|my-mm|nl|nl-nl|pt|pt-pt|ro|ro-ro|ru|ru-ru|th|th-th|tl|tl-ph|tr|tr-tr|vi|vi-vn|zh|zh-cn|zh-hk|zh-mo|zh-my|zh-sg|zh-tw)\//, "\/", $7); reports[$1, $7, $9]++; ip_counts[$1]++ } END { for (ip in reports) { split(ip, fields, SUBSEP); print fields[1], fields[2], fields[3], reports[ip], ip_counts[fields[1]] } }' s-api.access.log | sort -t ',' -k5nr >> test.csv

# 查找目标日志文件 并打包
find /var/log/nginx/ /var/log/member/ -maxdepth 1 -name '*s-member*.log*' -mtime -4 -exec tar -zcvf s-member-20240302.log.tar.gz {} +

# goaccess解析nginx日志,生成报表
goaccess *.access.log *.access.log.1 --log-format=COMBINED -o report.html --geoip-database=/usr/local/share/GeoIP/GeoIP2-Country.mmdb --geoip-database=/usr/local/share/GeoIP/dbip-asn-lite.mmdb

# goaccess解析nginx日志,生成报表 并用浏览器打开报表
cd /Users/zhoujh/Desktop/query-logs/var/log/nginx/ && goaccess *s-store*access.log.1 *s-store*access.log --log-format=COMBINED -o /Users/zhoujh/Desktop/goaccess-reports/s-store_2023_12_24_11_07.report.html && open -a 'Google Chrome' /Users/zhoujh/Desktop/goaccess-reports/s-store_2023_12_24_11_07.report.html

# 查询服务端日志 并将查询的结果存储到本地文件
ssh $connect_host -t "$query_nginx_cmd" > $output_nginx_file
# 查询服务端日志 并将查询的结果追加存储到本地文件
ssh $connect_host2 -t "$query_nginx_cmd" >> $output_nginx_file

# 在指定多个日志文件中 查询同时包含多个关键字的日志
grep -h -i 'page' /var/log/nginx/s-store.*.log.1 /var/log/nginx/s-store.*.log |grep '404'

# 在指定多个日志文件中 查询包含多个关键字之一的日志 输出不带文件名
grep -h -i -e ' 404 ' -e 'error' -e 'Exception' /var/log/nginx/s-store.*.log.1 /var/log/nginx/s-store.*.log

# 在find出的多个日志文件中 查询包含多个关键字之一的日志 输出不带文件名
find /var/log/nginx/s-store.*.log.1 /var/log/nginx/s-store.*.log /var/log/nginx/s-store*.gz -mtime -4 -exec zgrep -h -i -e ' 404 ' -e 'error' -e 'Exception' {} \;


grep 命令

grep - 强大的文本搜索工具

介绍

grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。用于过滤/搜索的特定字符。可使用正则表达式能配合多种命令使用,使用上十分灵活。

选项

-a --text  																		# 不要忽略二进制数据。
-A <显示行数> --after-context=<显示行数> # 除了显示符合范本样式的那一行之外,并显示该行之后的内容。
-b --byte-offset # 在显示符合范本样式的那一行之外,并显示该行之前的内容。
-B<显示行数> --before-context=<显示行数> # 除了显示符合样式的那一行之外,并显示该行之前的内容。
-c --count # 计算符合范本样式的列数。
-C<显示行数> --context=<显示行数>或-<显示行数> # 除了显示符合范本样式的那一列之外,并显示该列之前后的内容。
-d<进行动作> --directories=<动作> # 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep命令将回报信息并停止动作。
-e<范本样式> --regexp=<范本样式> # 指定字符串作为查找文件内容的范本样式。
-E --extended-regexp # 将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式。
-f<范本文件> --file=<规则文件> # 指定范本文件,其内容有一个或多个范本样式,让grep查找符合范本条件的文件内容,格式为每一列的范本样式。
-F --fixed-regexp # 将范本样式视为固定字符串的列表。
-G --basic-regexp # 将范本样式视为普通的表示法来使用。
-h --no-filename # 在显示符合范本样式的那一列之前,不标示该列所属的文件名称。
-H --with-filename # 在显示符合范本样式的那一列之前,标示该列的文件名称。
-i --ignore-case # 忽略字符大小写的差别。
-l --file-with-matches # 列出文件内容符合指定的范本样式的文件名称。
-L --files-without-match # 列出文件内容不符合指定的范本样式的文件名称。
-n --line-number # 在显示符合范本样式的那一列之前,标示出该列的编号。
-P --perl-regexp # PATTERN 是一个 Perl 正则表达式
-q --quiet或--silent # 不显示任何信息。
-R/-r --recursive # 此参数的效果和指定“-d recurse”参数相同。
-s --no-messages # 不显示错误信息。
-v --revert-match # 反转查找。
-V --version # 显示版本信息。
-w --word-regexp # 只显示全字符合的列。
-x --line-regexp # 只显示全列符合的列。
-y # 此参数效果跟“-i”相同。
-o # 只输出文件中匹配到的部分。
-m <num> --max-count=<num> # 找到num行结果后停止查找,用来限制匹配行数

规则表达式

^    # 锚定行的开始 如:'^grep'匹配所有以grep开头的行。    
$ # 锚定行的结束 如:'grep$' 匹配所有以grep结尾的行。
. # 匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。
* # 匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。
.* # 一起用代表任意字符。
[] # 匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。
[^] # 匹配一个不在指定范围内的字符,如:'[^A-Z]rep' 匹配不包含 A-Z 中的字母开头,紧跟 rep 的行
\(..\) # 标记匹配字符,如'\(love\)',love被标记为1。
\< # 锚定单词的开始,如:'\<grep'匹配包含以grep开头的单词的行。
\> # 锚定单词的结束,如'grep\>'匹配包含以grep结尾的单词的行。
x\{m\} # 重复字符x,m次,如:'0\{5\}'匹配包含5个o的行。
x\{m,\} # 重复字符x,至少m次,如:'o\{5,\}'匹配至少有5个o的行。
x\{m,n\} # 重复字符x,至少m次,不多于n次,如:'o\{5,10\}'匹配5--10个o的行。
\w # 匹配文字和数字字符,也就是[A-Za-z0-9],如:'G\w*p'匹配以G后跟零个或多个文字或数字字符,然后是p。
\W # \w的反置形式,匹配一个或多个非单词字符,如点号句号等。
\b # 单词锁定符,如: '\bgrep\b'只匹配grep。

用例

# 在文件中搜索一个单词
grep "match_pattern" file_name
# 在多个文件中查找
grep "match_pattern" file_1 file_2 file_3 ...
# 输出除之外的所有行
grep -v "match_pattern" file_name
# 标记匹配颜色
grep "match_pattern" file_name --color=auto
# 使用正则表达式
grep -E "[1-9]+" . -r -n
grep -e "[0-9][0-9][0-9]" . -r -n
grep -E "[0-9]{3}" . -r -n
grep -E " [A-Za-z0-9]?ip" . -r -n
grep -E "www\.[a-z]+" . -r -n
grep -E "www\.[a-z]+" . -r --color=auto
# 使用正则表达式
grep -P "(\d{3}\-){2}\d{4}" file_name
# 统计包含匹配字符串的行数
grep -c "text" file_name
# 查找匹配文本在哪些文件
grep -l "text" file1 file2 file3...
# 在当前目录中对文本进行递归搜索
grep "text" . -r -n
# 选项 -e 制动多个匹配样式:
grep -e "is" -e "line" file1 file2 file3...
# 也可以使用 **-f** 选项来匹配多个样式,在样式文件中逐行写出需要匹配的字符。
grep -e "is" -e "line" file1 file2 file3... -f patfile
# 只在目录中所有的.php和.html文件中递归搜索字符"main()"
grep "main()" . -r --include *.{php,html}
# 在搜索结果中排除所有README文件
grep "main()" . -r --exclude "README"
# 在搜索结果中排除filelist文件列表里的文件
grep "main()" . -r --exclude-from filelist
# 显示匹配某个结果之后的3行,使用 -A 选项:
grep "match_pattern" file1 file2 file3... -A 3
# 显示匹配某个结果之前的3行,使用 -B 选项:
grep "match_pattern" file1 file2 file3... -B 3
# 显示匹配某个结果的前三行和后三行,使用 -C 选项:
grep "match_pattern" file1 file2 file3... -C 3

zgrep | egrep | fgrep

awk 工具

awk - 文本和数据进行处理的编程语言

介绍

awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。

命令格式和选项

# 语法形式
awk [options] 'script' var=value file(s)
awk [options] -f scriptfile var=value file(s)

# 常用命令选项
-F fs # fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:,默认的分隔符是连续的空格或制表符
-v var=value # 赋值一个用户定义变量,将外部变量传递给awk
-f scripfile # 从脚本文件中读取awk命令
-m[fr] val # 对val值设置内在限制,-mf选项限制分配给val的最大块数目;-mr选项限制记录的最大数目。这两个功能是Bell实验室版awk的扩展功能,在标准awk中不适用。

awk模式和操作

# awk脚本是由模式和操作组成的。

# 模式 模式可以是以下任意一个:
# /正则表达式/:使用通配符的扩展集。
# 关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试。
# 模式匹配表达式:用运算符~(匹配)和!~(不匹配)。
# BEGIN语句块、pattern语句块、END语句块:参见awk的工作原理

# 操作 操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,主要部分是:
# 变量或数组赋值
# 输出命令
# 内置函数
# 控制流语句

awk的工作原理

awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
# 第一步:执行BEGIN{ commands }语句块中的语句;
# 第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。
# 第三步:当读至输入流末尾时,执行END{ commands }语句块。

BEGIN语句块 在awk开始从输入流中读取行 之前 被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中。

END语句块 在awk从输入流中读取完所有的行 之后 即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块。

pattern语句块 中的通用命令是最重要的部分,它也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块。

用例

# nginx access日志转csv
# 待解析的样本:
46.153.148.68 - - [01/Mar/2024:06:31:37 +0800] "GET /dkhkidgjo HTTP/1.1" 302 5 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3.1 Mobile/15E148 Safari/604.1"
# 解析步骤1 - 分割:
# 按照(空格双+引号 》双引号+空格+双引号 》双引号+空格 》双引号) ' \"' > '\" \"' > '\" ' > '\"' 进行分割
awk 'BEGIN { FS=" \"|\" \"|\" |\""; OFS=" && " } {print $1, $2, $3, $4, $5}' access.log
# 分割结果:
46.153.148.68 - - [01/Mar/2024:06:31:37 +0800] && GET /dkhkidgjo HTTP/1.1 && 302 5 && - && Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3.1 Mobile/15E148 Safari/604.1
# 解析步骤2 - 对$1进行处理:
# 替换中括号(gsub( /\[|\]/, "", $1 )),空格分割$1(split( $1, fields1, " ")), 取分割后第一个和第四个结果
awk 'BEGIN { FS=" \"|\" \"|\" |\""; OFS=" && " } { gsub( /\[|\]/, "", $1 ); split( $1, fields1, " "); print fields1[4], fields1[1], $2, $3, $4, $5}' access.log
# 得到结果:
01/Mar/2024:06:31:37 && 46.153.148.68 && GET /dkhkidgjo HTTP/1.1 && 302 5 && - && Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3.1 Mobile/15E148 Safari/604.1
# 解析步骤3 - 对$2进行处理:
# 空格分割$2(split( $2, fields2, " ")), 取分割后第一、第二和第三个结果(请求方法、路径和HTTP版本)
awk 'BEGIN { FS=" \"|\" \"|\" |\""; OFS=" && " } { gsub( /\[|\]/, "", $1 ); split( $1, fields1, " "); split( $2, fields2, " "); print fields1[4], fields1[1], fields2[1], fields2[2], fields2[3], $3, $4, $5}' access.log
# 得到结果:
01/Mar/2024:06:31:37 && 46.153.148.68 && GET && /dkhkidgjo && HTTP/1.1 && 302 5 && - && Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3.1 Mobile/15E148 Safari/604.1
# 解析步骤4 - 对$3进行处理:
# 空格分割$3(split( $3, fields3, " ")), 取分割后第一和第二个结果(状态码和响应数据长度)
awk 'BEGIN { FS=" \"|\" \"|\" |\""; OFS=" && " } { gsub( /\[|\]/, "", $1 ); split( $1, fields1, " "); split( $2, fields2, " "); split( $3, fields3, " "); print fields1[4], fields1[1], fields2[1], fields2[2], fields2[3], fields3[1], fields3[2], $4, $5}' access.log
# 得到结果:
01/Mar/2024:06:31:37 && 46.153.148.68 && GET && /dkhkidgjo && HTTP/1.1 && 302 && 5 && - && Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3.1 Mobile/15E148 Safari/604.1
# 解析步骤5 - 生成CSV:UserAgent是复杂字符串 需要格式化用双引号括起来(sprintf("\"%s\"",$5))
awk 'BEGIN { FS=" \"|\" \"|\" |\""; OFS=","; print "Time","IP","Method","Path","Http","RspCode","RspLength","-","UA"; } { gsub( /\[|\]/, "", $1 ); split( $1, fields1, " "); split( $2, fields2, " "); split( $3, fields3, " "); print fields1[4], fields1[1], fields2[1], fields2[2], fields2[3], fields3[1], fields3[2], $4, sprintf("\"%s\"",$5) }' access.log > access.csv


# 统计nginx日志 ip访问数量: 输出ip》对ip排序》去重和统计》对统计数量倒序排序》输出前300
awk '{print $1}' ./access.log | sort | uniq -c | sort -nr | head -n 300
# 统计nginx日志 ip访问数量: sort -k2nr 对awk输出的结果进行排序。-k2 表示按第二列进行排序,n 表示按数值排序,r 表示逆序
awk '{ ip_count[$1]++ } END { for (ip in ip_count) print ip, ip_count[ip] }' ./access.log | sort -k2nr

其他相关命令汇总

文件查找

# 文件查找
#1 将tmp中超过15天的文件 移动到 s-store-copys/tmp文件夹中命令:
sudo find ./ -name "*.png" -mtime +15 -exec mv {} ../../s-store-copys/tmp \;
#2 查找文件 查找当前目录下面的Develop文件
find ./ Develop
#3.查找文件 当前目录以及子目录下面的Github文件 (-iname 忽略大小写)
find ./ -name Gith*
#4.查找文件夹 当前目录以及子目录下面的GitHub 文件夹
find ./ -name Github -type d
#5.查找当前目录以及子目录下面带Github关键字的路径
find ./ |grep Github
#6 查找目标文件:排除nginx目录【-not -path "./*/var/log/nginx/*"】+ 只查询子级目录【./*/】
find ./*/ -name "*.log" -type f -not -path "./*/var/log/nginx/*"
#-注 排除子级nginx目录:有效【-not -path "./*/var/log/nginx/*"】,无效【-path "./*/var/log/nginx/*" -prune】

# 打包压缩
#1. 将3天以外的log 移动到history
sudo find ./ -maxdepth 1 -name "*.log" -mtime +3 -exec mv {} history \;
#2. 去到history
cd history/
#3. 压缩打包
tar -zcvf 20200117_20200215log.tar.gz *.log

sort排序

# 概要
sort [OPTION]... [FILE]...
sort [OPTION]... --files0-from=F

# 选项
-b, --ignore-leading-blanks 忽略开头的空白。
-d, --dictionary-order 仅考虑空白、字母、数字。
-f, --ignore-case 将小写字母作为大写字母考虑。
-g, --general-numeric-sort 根据数字排序。
-i, --ignore-nonprinting 排除不可打印字符。
-M, --month-sort 按照非月份、一月、十二月的顺序排序。
-h, --human-numeric-sort 根据存储容量排序(注意使用大写字母,例如:2K 1G)
-n, --numeric-sort 根据数字排序。
-R, --random-sort 随机排序,但分组相同的行。
-r, --reverse 将结果倒序排列。
-V, --version-sort 文本中(版本)数字的自然排序。
-k, --key=KEYDEF 通过一个key排序;KEYDEF给出位置和类型。
-m, --merge 合并已排序文件,之后不再排序。
-o, --output=FILE 将结果写入FILE而不是标准输出。
-t, --field-separator=SEP 使用SEP作为列的分隔符。
-u, --unique 同时使用-c,严格检查排序;不同时使用-c,输出排序后去重的结果。
--help 显示帮助信息并退出。
--version 显示版本信息并退出。

# 例子
[root@mail text]# cat sort.txt
AAA:BB:CC
aaa:30:1.6
ccc:50:3.3
ddd:20:4.2
bbb:10:2.5
eee:40:5.4
eee:60:5.1

# 将CC列数字从大到小顺序排列:
# -n是按照数字大小排序,-r是以相反顺序,-k是指定需要排序的栏位,-t指定栏位分隔符为冒号
[root@mail text]# sort -nrk 3 -t: sort.txt
eee:40:5.4
eee:60:5.1
ddd:20:4.2
ccc:50:3.3
bbb:10:2.5
aaa:30:1.6
AAA:BB:CC



xxxx

# 文件查找
#1 将tmp中超过15天的文件 移动到 s-store-copys/tmp文件夹中命令:


xxxx

# 文件查找
#1 将tmp中超过15天的文件 移动到 s-store-copys/tmp文件夹中命令:
sudo find ./ -name "*.png" -mtime +15 -exec mv {} ../../s-store-copys/tmp \;