在Linux系统管理和数据处理领域,grep、sed和awk这三个工具被广大开发者誉为"文本处理三剑客"。它们各自有着独特的功能定位,却又能够完美配合,几乎可以解决日常工作中遇到的所有文本处理需求。
我使用这些工具已经有十多年了,从最初的简单搜索替换,到现在能够处理复杂的日志分析和数据转换任务。这些工具的强大之处在于它们的简洁性和组合性——每个工具都专注于做好一件事,通过管道(pipe)将它们组合起来,就能实现复杂的数据处理流程。
grep 是全局正则表达式打印(Global Regular Expression Print)的缩写,主要用于文本搜索和过滤。它的核心功能是快速查找文件中匹配特定模式的行。在日常工作中,我经常用它来:
sed(Stream Editor)是一个流编辑器,擅长对文本进行基于行的编辑操作。它最常见的用途是文本替换,但实际上它能做的远不止于此。我常用的sed场景包括:
awk(以三位创始人Aho、Weinberger和Kernighan的姓氏首字母命名)是一个完整的文本处理编程语言。它特别适合处理结构化文本数据(如CSV、日志文件等)。我主要用它来:
在当今这个充斥着各种高级编程语言和复杂数据处理框架的时代,为什么我们仍然需要掌握这些"古老"的命令行工具?根据我的经验,主要有以下几个原因:
对于初学者,我建议按照以下顺序来掌握这些工具:
记住,这些工具的学习曲线是相当平缓的。你不需要一开始就掌握所有功能,可以从最常用的20%功能开始,这已经能解决80%的日常问题了。
grep最基本的用法是在文件中搜索包含特定模式的行。让我们从一个简单的例子开始:
bash复制# 在app.log文件中搜索包含"error"的行
grep "error" app.log
这个简单的命令背后其实有很多可以调整的参数,下面是一些我最常用的选项:
bash复制# 忽略大小写搜索
grep -i "error" app.log
# 显示匹配行的行号(对调试非常有用)
grep -n "error" app.log
# 显示匹配行的前后上下文(A-after, B-before, C-context)
grep -A 3 "error" app.log # 显示匹配行及其后3行
grep -B 2 "error" app.log # 显示匹配行及其前2行
grep -C 1 "error" app.log # 显示匹配行及其前后各1行
# 递归搜索目录中的文件
grep -r "TODO" src/
# 只显示包含匹配项的文件名(而不是具体行)
grep -l "error" *.log
# 统计匹配行的数量(而不是显示具体行)
grep -c "error" app.log
# 反向匹配(显示不包含模式的行)
grep -v "debug" app.log
提示:使用
-l选项结合-r可以快速找出哪些文件包含特定内容,这在大型项目中特别有用。
grep的真正威力在于它与正则表达式的结合。grep支持三种正则表达式语法:
-E选项-P选项(某些系统可能需要安装pcregrep)bash复制# 匹配以"start"开头的行
grep "^start" file
# 匹配以"end"结尾的行
grep "end$" file
# 匹配"a"和"b"之间有任意一个字符的行
grep "a.b" file
# 匹配"a"后面跟着零个或多个"b"的行
grep "ab*" file
bash复制# 使用-E启用扩展正则表达式
# 匹配"error"或"warn"
grep -E "error|warn" file
# 匹配"a"后面跟着一个或多个"b"
grep -E "ab+" file
# 匹配"a"后面跟着零个或一个"b"
grep -E "ab?" file
# 匹配2到4个连续的"a"
grep -E "a{2,4}" file
# 匹配IP地址(简化版)
grep -E "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" file
bash复制# 使用-P启用Perl正则表达式(需要支持)
# 匹配日期格式YYYY-MM-DD
grep -P "\d{4}-\d{2}-\d{2}" file
# 使用零宽断言匹配"user:"后面的单词
grep -P "(?<=user:)\w+" file
# 匹配引号内的内容
grep -P '"(.*?)"' file
注意:不是所有系统都默认支持
-P选项,如果不可用,可以考虑安装pcregrep或使用其他工具如perl、awk代替。
在实际工作中,grep的应用场景非常广泛。下面分享一些我经常使用的实用技巧:
bash复制# 搜索日志中的错误信息,并显示最后20条
grep -E "(ERROR|FATAL)" app.log | tail -20
# 搜索特定时间段的日志
grep "2025-01-08 10:" app.log
# 统计每个错误类型的出现频率
grep -Eo "(ERROR|WARN|INFO) [a-zA-Z]+" app.log | sort | uniq -c | sort -rn
bash复制# 从访问日志中提取所有IP地址
grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" access.log
# 统计每个IP的访问次数(按访问量降序排列)
grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" access.log | sort | uniq -c | sort -rn | head
# 查找可能的SQL注入尝试
grep -iE "union.*select|1=1|sleep\(|benchmark\(" access.log
bash复制# 搜索多个必须同时出现的关键词
grep "error" app.log | grep "database"
# 在Python文件中搜索但不包括测试文件
grep -r "password" --include="*.py" --exclude="*test*" src/
# 只显示匹配的部分(而不是整行)
grep -o "user_id=[0-9]*" app.log
# 查找TODO注释并显示上下文
grep -rn -A 2 "TODO" src/
处理大文件时,grep的性能可能会成为瓶颈。以下是一些优化建议:
bash复制# 使用LC_ALL=C提升ASCII处理速度
LC_ALL=C grep "pattern" huge_file.log
# 并行处理(需要安装parallel)
cat huge_file.log | parallel --pipe grep "pattern"
# 使用更快的替代工具(如ripgrep)
rg "pattern" huge_file.log
# 分块处理大文件
split -l 1000000 huge_file.log chunk_
for f in chunk_*; do
grep "pattern" "$f" > "$f.out" &
done
wait
cat chunk_*.out > result.txt
rm chunk_*
经验分享:在处理超过1GB的日志文件时,使用
LC_ALL=C可以将grep的速度提高2-3倍。这是因为跳过了本地化字符处理的额外开销。
sed最基本的用途是文本替换,其语法结构为s/原模式/替换模式/标志。让我们从最简单的替换开始:
bash复制# 替换每行中第一个匹配的old为new
sed 's/old/new/' file
# 替换所有匹配(全局替换)
sed 's/old/new/g' file
# 忽略大小写进行替换
sed 's/old/new/gi' file
# 直接修改文件(谨慎使用!)
sed -i 's/old/new/g' file
# 修改文件同时创建备份(推荐做法)
sed -i.bak 's/old/new/g' file
重要提示:使用
-i选项会直接修改原文件,建议始终先不加-i测试命令,确认无误后再添加-i。或者使用-i.bak同时创建备份。
sed允许我们指定替换操作的行范围:
bash复制# 只替换第5行的内容
sed '5s/old/new/g' file
# 替换1-10行的内容
sed '1,10s/old/new/g' file
# 替换匹配特定模式的行
sed '/pattern/s/old/new/g' file
# 替换从匹配开始到文件末尾的所有行
sed '/start_pattern/,$ s/old/new/g' file
当处理包含斜杠的文本(如路径)时,可以使用其他分隔符避免转义:
bash复制# 使用|作为分隔符
sed 's|/usr/local|/opt|g' file
# 使用#作为分隔符
sed 's#/usr/local#/opt#g' file
# 使用:作为分隔符
sed 's:/usr/local:/opt:g' file
除了替换,sed还擅长删除特定行或内容:
bash复制# 删除第5行
sed '5d' file
# 删除1-10行
sed '1,10d' file
# 删除匹配模式的行
sed '/pattern/d' file
# 删除空行
sed '/^$/d' file
# 删除注释行(以#开头)
sed '/^#/d' file
# 删除行首空白字符(空格/tab)
sed 's/^[ \t]*//' file
# 删除行尾空白字符
sed 's/[ \t]*$//' file
# 同时删除行首行尾空白
sed 's/^[ \t]*//;s/[ \t]*$//' file
sed可以在指定位置插入或追加内容:
bash复制# 在第3行后追加一行"new content"
sed '3a\new content' file
# 在第3行前插入一行"new content"
sed '3i\new content' file
# 在匹配pattern的行后追加
sed '/pattern/a\new content' file
# 替换整行内容
sed '3c\new line content' file
sed '/pattern/c\new line content' file
sed支持使用\(...\)进行分组,并通过\1,\2等引用:
bash复制# 交换冒号两边的内容
sed 's/\(.*\):\(.*\)/\2:\1/' file
# 在匹配的单词前后添加括号
sed 's/\b\(word\)\b/(\1)/g' file
# 格式化日期从YYYYMMDD到YYYY-MM-DD
sed 's/\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1-\2-\3/' file
可以在一个sed命令中执行多个操作:
bash复制# 使用-e执行多个命令
sed -e 's/a/A/g' -e 's/b/B/g' file
# 使用分号分隔多个命令
sed 's/a/A/g; s/b/B/g' file
# 对同一行执行多个操作
sed '/pattern/ {s/old/new/; s/foo/bar/}' file
sed默认会打印所有行,使用-n可以抑制默认输出,然后通过p命令选择性打印:
bash复制# 只打印第5行
sed -n '5p' file
# 打印5-10行
sed -n '5,10p' file
# 打印匹配模式的行
sed -n '/pattern/p' file
# 打印两个模式之间的行
sed -n '/start/,/end/p' file
在脚本中使用变量时,注意使用双引号而不是单引号:
bash复制old="foo"
new="bar"
sed "s/$old/$new/g" file
bash复制# 修改SSH端口
sed -i 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config
# 批量注释掉配置项
sed -i '/^setting/ s/^/#/' config.ini
# 取消注释特定配置
sed -i '/^#setting/ s/^#//' config.ini
bash复制# 批量重命名文件中的字符串
find . -name "*.txt" -exec sed -i 's/old/new/g' {} +
# 提取HTML标签中的内容
sed 's/<[^>]*>//g' file.html
# 提取两个标记之间的内容
sed -n '/START/,/END/p' file
# 在每行前添加行号
sed = file | sed 'N;s/\n/\t/'
# 将DOS格式转换为Unix格式(去除\r)
sed -i 's/\r$//' file
bash复制# 提取特定时间段的日志
sed -n '/2025-01-08 10:00:00/,/2025-01-08 11:00:00/p' app.log
# 删除日志中的敏感信息(如密码)
sed 's/password=[^ &]*/password=***/g' access.log
# 格式化杂乱的日志输出
sed 's/\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\) \([0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}\)/\1T\2Z /' logfile
经验分享:处理XML或HTML时,sed虽然可以工作,但对于复杂结构,建议使用专门的工具如xmlstarlet或python的BeautifulSoup。sed更适合处理行格式的文本。
awk不仅仅是一个命令,而是一门完整的编程语言,专门设计用于处理结构化文本数据。其基本工作模式是:
code复制pattern { action }
awk会逐行读取输入,如果行匹配pattern,就执行对应的action。让我们从最简单的例子开始:
bash复制# 打印文件的每一行(类似于cat)
awk '{print}' file
# 打印每行的第一个字段(默认以空白字符分隔)
awk '{print $1}' file
# 打印多个字段
awk '{print $1, $3}' file
# 打印最后一列
awk '{print $NF}' file
# 打印倒数第二列
awk '{print $(NF-1)}' file
awk默认以空白字符(空格/tab)分隔字段,但可以自定义:
bash复制# 使用冒号作为分隔符(处理/etc/passwd)
awk -F':' '{print $1}' /etc/passwd
# 处理CSV文件(注意某些CSV可能包含逗号在引号内)
awk -F',' '{print $1, $2}' data.csv
# 使用正则表达式作为分隔符
awk -F'[:,\t]' '{print $1}' file
awk提供了许多有用的内置变量:
| 变量 | 描述 |
|---|---|
| $0 | 当前行的全部内容 |
| $1-$n | 当前行的第1到第n个字段 |
| NF | 当前行的字段数量 |
| NR | 当前处理的行号(所有文件累计) |
| FNR | 当前文件中的行号 |
| FS | 字段分隔符(同-F参数) |
| OFS | 输出字段分隔符(默认为空格) |
| RS | 记录分隔符(默认为换行) |
| ORS | 输出记录分隔符(默认为换行) |
| FILENAME | 当前处理的文件名 |
使用示例:
bash复制# 打印行号及内容
awk '{print NR, $0}' file
# 打印第5行
awk 'NR==5' file
# 打印5-10行
awk 'NR>=5 && NR<=10' file
# 打印文件总行数
awk 'END {print NR}' file
# 设置输出字段分隔符为逗号
awk 'BEGIN {OFS=","} {print $1, $2, $3}' file
BEGIN和END是特殊的模式,分别在处理开始前和处理结束后执行:
bash复制# 打印开始和结束标记
awk 'BEGIN {print "Start Processing"} {print} END {print "End Processing"}' file
# 计算数字列的总和
awk 'BEGIN {sum=0} {sum+=$1} END {print "Total:", sum}' file
# 计算平均值
awk '{sum+=$1; count++} END {print "Average:", sum/count}' file
# 设置表头
awk 'BEGIN {print "Name\tAge\tScore"} {print $1, $2, $3}' data.txt
awk支持完整的编程结构,包括条件判断和循环:
bash复制# 简单if条件
awk '{if ($3>100) print $1, "High"; else print $1, "Low"}' file
# 根据字段值分类处理
awk '{
if ($2 >= 90) grade="A";
else if ($2 >= 80) grade="B";
else if ($2 >= 70) grade="C";
else grade="D";
print $1, grade
}' scores.txt
bash复制# for循环处理每个字段
awk '{for(i=1; i<=NF; i++) print $i}' file
# while循环示例
awk '{
i=1
while(i<=3) {
print $i
i++
}
}' file
# 遍历数组
awk '{
for(k in counts) {
print k, counts[k]
}
}' file
awk的数组功能非常强大,特别是关联数组(即键值对):
bash复制# 统计每行第一个字段的出现次数
awk '{count[$1]++} END {for(k in count) print k, count[k]}' file
# 统计不同状态码的数量
awk '{status[$9]++} END {for(s in status) print s, status[s]}' access.log
# 多维数组模拟(使用SUBSEP分隔键)
awk '{stats[$1,$2]++} END {for(k in stats) print k, stats[k]}' file
bash复制# 统计HTTP状态码分布
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# 使用awk直接统计(更高效)
awk '{status[$9]++} END {for(s in status) print s, status[s]}' access.log
# 计算平均响应时间
awk '{sum+=$NF; count++} END {print "Avg:", sum/count, "ms"}' access.log
# 找出响应时间最长的请求
awk 'BEGIN {max=0} {if($NF>max) {max=$NF; line=$0}} END {print line}' access.log
bash复制# 统计每小时请求量
awk -F'[: ]' '{hour[$5]++} END {for(h in hour) print h":00", hour[h]}' access.log | sort
# 生成简单的报表
awk 'BEGIN {printf "%-15s %10s %10s\n", "User", "Count", "Total"}
{users[$1]++; total[$1]+=$2}
END {for(u in users) printf "%-15s %10d %10d\n", u, users[u], total[u]}' data.txt
# 格式化输出
awk '{printf "| %-20s | %10d | %8.2f |\n", $1, $2, $3}' table.txt
bash复制# CSV转TSV
awk -F',' 'BEGIN {OFS="\t"} {$1=$1; print}' data.csv
# 数据归一化(将每列除以最大值)
awk 'NR==1 {print; next}
NR==2 {for(i=1;i<=NF;i++) max[i]=$i}
{
for(i=1;i<=NF;i++) {
if($i>max[i]) max[i]=$i
}
}
END {
for(i=1;i<=NF;i++) printf "%.2f ", max[i]
printf "\n"
}' data.txt
bash复制# 分析Nginx访问日志:统计每个IP的请求量和流量
awk '{
ip=$1
requests[ip]++
bytes[ip]+=$10
}
END {
printf "%-20s %10s %15s\n", "IP", "Requests", "Traffic(MB)"
for(ip in requests) {
printf "%-20s %10d %15.2f\n", ip, requests[ip], bytes[ip]/1024/1024
}
}' access.log | sort -k2 -rn | head -20
# 实时监控错误日志
tail -f error.log | awk '/ERROR/ {print strftime("%Y-%m-%d %H:%M:%S"), $0}'
# 多文件关联处理(类似SQL join)
awk -F',' 'NR==FNR {a[$1]=$2; next} {print $0, a[$1]}' users.csv orders.csv
专业建议:对于非常复杂的文本处理任务,可以考虑将awk脚本保存到单独的文件中,使用
-f选项调用,提高可读性和可维护性。例如:bash复制# script.awk BEGIN {FS=","; OFS="\t"} $3 > 100 {print $1, $2, $3*1.1} # 执行 awk -f script.awk data.csv
真正的威力在于将这些工具通过Unix管道(|)组合使用。下面是一些我常用的组合模式:
bash复制# 先grep过滤,再用awk处理
grep "ERROR" app.log | awk '{print $1, $2, $NF}'
# 先awk提取字段,再用sed格式化
awk -F',' '{print $1, $3}' data.csv | sed 's/ /,/g'
# 多级过滤处理
cat logfile | grep "WARN" | grep -v "test" | awk '{print $3}' | sort | uniq -c
bash复制# 查找所有包含特定内容的文件并替换
grep -l "old_string" *.txt | xargs sed -i 's/old_string/new_string/g'
# 查找并处理特定类型的文件
find . -name "*.log" -exec grep -l "error" {} + | xargs awk '{print FILENAME, $0}'
bash复制# 完整的日志分析流程
zcat access.log.*.gz | # 解压多个日志文件
grep "POST /api" | # 过滤API请求
awk '$9 == 200 {print $1}' | # 提取成功的请求IP
sort | uniq -c | # 统计IP出现次数
sort -rn | head -20 # 排序取前20
bash复制#!/bin/bash
# nginx_log_analysis.sh
LOG_FILE="${1:-/var/log/nginx/access.log}"
echo "=== Nginx访问日志分析 ==="
echo "分析文件: $LOG_FILE"
echo ""
# 总请求量
echo "【总请求量】"
wc -l < "$LOG_FILE"
echo ""
# 状态码分布
echo "【HTTP状态码分布】"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn
echo ""
# 最活跃IP
echo "【最活跃的10个IP】"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo ""
# 最常访问的URL
echo "【最常访问的10个URL】"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo ""
# 错误请求分析
echo "【4xx/5xx错误请求】"
awk '$9~/^[45]/ {print $9, $7, $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -20
echo ""
# 带宽使用统计
echo "【带宽使用统计】"
awk '{
total_bytes += $10
if ($10 > max_bytes) {
max_bytes = $10
max_line = $0
}
}
END {
print "总传输数据:", total_bytes/1024/1024 "MB"
print "平均每次请求:", total_bytes/NR/1024 "KB"
print "最大单次请求:", max_bytes/1024 "KB"
print "对应请求详情:", max_line
}' "$LOG_FILE"
bash复制#!/bin/bash
# update_config.sh
CONFIG_DIR="/etc/myapp"
OLD_DB_HOST="localhost"
NEW_DB_HOST="db-cluster.example.com"
# 安全检查:先显示哪些文件会被修改
echo "以下配置文件将被修改:"
grep -rl "$OLD_DB_HOST" "$CONFIG_DIR"
echo ""
# 确认
read -p "确认将 $OLD_DB_HOST 替换为 $NEW_DB_HOST?(y/n) " confirm
if [[ "$confirm" == "y" ]]; then
# 执行替换并创建备份
grep -rl "$OLD_DB_HOST" "$CONFIG_DIR" | xargs sed -i.bak "s/$OLD_DB_HOST/$NEW_DB_HOST/g"
# 验证更改
echo ""
echo "修改完成,验证新配置:"
grep -r "$NEW_DB_HOST" "$CONFIG_DIR"
echo ""
echo "原始配置已备份为.bak文件"
else
echo "操作已取消"
fi
bash复制#!/bin/bash
# log_monitor.sh
LOG_FILE="/var/log/app/error.log"
ERROR_PATTERNS=("ERROR" "Exception" "Failed" "Timeout")
CHECK_INTERVAL=60 # 检查间隔(秒)
ALERT_THRESHOLD=5 # 触发告警的阈值
ALERT_EMAIL="admin@example.com"
# 初始化上次检查位置
LAST_SIZE=$(stat -c %s "$LOG_FILE")
while true; do
CURRENT_SIZE=$(stat -c %s "$LOG_FILE")
# 如果日志文件被轮转或截断,重置LAST_SIZE
if [ $CURRENT_SIZE -lt $LAST_SIZE ]; then
LAST_SIZE=0
fi
# 检查新日志内容
if [ $CURRENT_SIZE -gt $LAST_SIZE ]; then
# 提取新增日志
NEW_CONTENT=$(dd if="$LOG_FILE" bs=1 skip=$LAST_SIZE 2>/dev/null)
# 统计各错误模式出现次数
declare -A COUNTS
for pattern in "${ERROR_PATTERNS[@]}"; do
COUNT=$(echo "$NEW_CONTENT" | grep -c "$pattern")
if [ $COUNT -gt 0 ]; then
COUNTS["$pattern"]=$COUNT
fi
done
# 如果有错误,输出统计信息
if [ ${#COUNTS[@]} -gt 0 ]; then
echo "$(date) - 发现新错误:"
for pattern in "${!COUNTS[@]}"; do
echo " $pattern: ${COUNTS[$pattern]}次"
# 如果超过阈值,发送告警
if [ ${COUNTS[$pattern]} -ge $ALERT_THRESHOLD ]; then
echo " [告警] $pattern 出现 ${COUNTS[$pattern]} 次,超过阈值 $ALERT_THRESHOLD" | \
mail -s "应用错误告警: $pattern" "$ALERT_EMAIL"
fi
done
# 输出匹配的日志样例(最多5行)
echo " 样例:"
echo "$NEW_CONTENT" | grep -E "$(IFS="|"; echo "${ERROR_PATTERNS[*]}")" | head -5 | sed 's/^/ /'
echo ""
fi
fi
# 更新最后检查位置
LAST_SIZE=$CURRENT_SIZE
# 等待下一次检查
sleep $CHECK_INTERVAL
done
bash复制# 并行处理大文件
parallel --pipepart --block 100M -a huge_file.log awk '{print $1}'
# 分块处理
split -l 1000000 huge_file.log chunk_
for f in chunk_*; do
awk '{print $1}' "$f" > "$f.out" &
done
wait
cat chunk_*.out > result.txt
rm chunk_*
# 使用更高效的工具组合
rg "pattern" huge_file.log | awk '{print $1}'
bash复制# 错误:多次读取同一文件
count=$(grep -c "error" file)
lines=$(grep "error" file)
# 正确:一次读取多次处理
result=$(grep "error" file)
count=$(echo "$result" | wc -l)
lines="$result"
# 错误:不必要的cat
cat file | grep "pattern"
# 正确:直接读取
grep "pattern" file
# 错误:在循环中调用外部命令
while read line; do
echo "$line" | grep "pattern"
done < file
# 正确:使用内置过滤
awk '/pattern/' file
# 或
grep "pattern" file
bash复制# 将复杂awk脚本保存到单独文件
# stats.awk
BEGIN {
print "开始处理..."
OFS="\t"
}
$3 > 100 {
count++
sum += $3
print $1, $2, $3*1.1
}
END {
print "处理完成"
print "总计:", count
print "平均值:", sum/count
}
# 执行
awk -f stats.awk data.txt
# 使用函数组织复杂逻辑
awk '
function calculate(value) {
return value * 1.1
}
{
print $1, calculate($2)
}' data.txt
经验总结:在实际工作中,我经常遇到需要快速分析日志或处理数据的情况。掌握这些工具的组合使用可以节省大量时间。一个实用的建议是:将常用的分析模式保存为脚本或别名,建立个人工具库。例如,我的~/.bashrc中有这样的别名:
bash复制alias topips="awk '{print \$1}' | sort | uniq -c | sort -rn | head" alias sum="awk '{sum+=\$1} END {print sum}'" alias avg="awk '{sum+=\$1; count++} END {print sum/count}'"这样,我可以简单地使用
cat access.log | topips来快速查看最活跃的IP。