使用 AWK 读取列中的重复项

使用 AWK 读取列中的重复项

我希望比较访问我的网站的每个用户(IP 地址)查看一系列页面的时间,以识别网站上的非人类活动,从而从我的分析中排除 IP 地址。

我想尽可能使用 awk (我正在使用 GAWK),只是因为我正在学习它并且想要改进。不过,我愿意在 bash 中使用其他工具。

我有一个修改后的日志文件(output.csv),格式如下:

29/Oct/2020:07:41:42|111.111.111.111|200|/page-a/
29/Oct/2020:08:30:40|000.111.026.111|200|/page-a/
29/Oct/2020:08:30:44|000.111.026.111|200|/page-b/
29/Oct/2020:08:30:45|000.111.026.111|200|/page-c/
29/Oct/2020:08:30:47|000.111.026.111|200|/page-d/
29/Oct/2020:08:30:47|220.171.008.221|200|/page-h/
29/Oct/2020:08:30:48|000.111.026.111|200|/page-e/
29/Oct/2020:08:41:49|221.651.943.323|200|/page-a/
29/Oct/2020:08:41:52|060.121.125.144|200|/page-f/
29/Oct/2020:08:41:52|060.121.125.144|200|/page-g/
29/Oct/2020:08:41:54|000.111.026.111|200|/page-k/
29/Oct/2020:08:41:55|060.121.125.144|200|/page-l/
29/Oct/2020:08:41:57|060.121.125.144|200|/page-n/
29/Oct/2020:08:41:58|060.121.125.144|200|/page-s/

我想做以下事情:

  • 搜索每个唯一的IPoutput.csv
  • 如果此 IP 有 5 个或更多实例,则计算每行第一个和第五个日期/时间之间的秒数差异
  • 隔离在 15 秒内访问 5 个页面的 IP 地址
  • 将这些 IP 地址附加到file.txt

我尝试过的

为了获取特定数量的 IP 地址实例之间的时间差(以秒为单位),我使用了以下命令集:

egrep "000.111.000.111" output.csv | awk 'BEGIN{FS="|"; ORS=" "} NR==1 || NR==5 {print $1,$2}' | sed -e 's/[\/:]/\ /g' -e 's/Jan/1/g' -e 's/Feb/2/g' -e 's/Mar/3/g' -e 's/Apr/4/g' -e 's/May/5/g' -e 's/Jun/6/g' -e 's/Jul/7/g' -e 's/Aug/8/g' -e 's/Sep/9/g' -e 's/Oct/10/g' -e 's/Nov/11/g' -e 's/Dec/12/g' | awk '{print $3,$2,$1,$4,$5,$6 "," $10,$9,$8,$11,$12,$13","$14}' | awk -F, '{d2=mktime($2);d1=mktime($1);print d2-d1, $3}'  | awk '{if($1<15)print $2}' >> file.txt

如果给定 IP 地址在 15 秒内访问了 5 个页面,则上面的命令会将 IP 附加到文件中。

虽然这可行,但我正在寻找一种方法来在单个命令/脚本中跨所有唯一 IP 执行此操作。

我也愿意接受任何更优雅的方法,因为我发现我的方法很麻烦。

想要的结果

期望的结果是一个包含 IP 地址列表的文件,所有这些 IP 地址都以 14 秒内超过 5 页的速度访问服务器(时间可以调整)。

例如。file.txt根据上面的示例,的内容将是:

000.111.026.111
060.121.125.144

理想情况下,如果您能够逐步完成您的方法来解释其工作原理,我将不胜感激,因为这将有助于我学习。

答案1

使用 GNU awk 执行 mktime():

$ cat tst.awk
BEGIN { FS = "|" }
(++count[$2]) ~ /^[15]$/ {
    split($1,t,"[/:]")
    monthNr = (index("JanFebMarAprMayJunJulAugSepOctNovDec",t[2])+2)/3
    currSecs = mktime(t[3] " " monthNr " " t[1] " " t[4] " " t[5] " " t[6])

    if ( count[$2] == 1 ) {
        firstSecs[$2] = currSecs
    }
    else if ( (currSecs - firstSecs[$2]) < 15 ) {
        print $2
    }
}

$ awk -f tst.awk file
000.111.026.111
060.121.125.144

我认为它在做什么非常清楚,因此无需添加文字解释,但如果您有任何问题,请随时询问。

哦,您在评论中提到希望您知道一种将 IP 地址转换为虚拟值的方法,以便您可以发布更全面的示例,这是一种足以解决您的特定问题的方法:

$ awk '
    BEGIN { FS=OFS="|" }
    !($2 in map) { ip=sprintf("%012d",++cnt); gsub(/.../,"&.",ip); sub(/.$/,"",ip); map[$2]=ip }
    { $2=map[$2]; print }
' file
29/Oct/2020:07:41:42|000.000.000.001|200|/page-a/
29/Oct/2020:08:30:40|000.000.000.002|200|/page-a/
29/Oct/2020:08:30:44|000.000.000.002|200|/page-b/
29/Oct/2020:08:30:45|000.000.000.002|200|/page-c/
29/Oct/2020:08:30:47|000.000.000.002|200|/page-d/
29/Oct/2020:08:30:47|000.000.000.003|200|/page-h/
29/Oct/2020:08:30:48|000.000.000.002|200|/page-e/
29/Oct/2020:07:41:49|000.000.000.004|200|/page-a/
29/Oct/2020:08:41:52|000.000.000.005|200|/page-f/
29/Oct/2020:08:41:52|000.000.000.005|200|/page-g/
29/Oct/2020:08:41:54|000.000.000.002|200|/page-k/
29/Oct/2020:08:41:55|000.000.000.005|200|/page-l/
29/Oct/2020:08:41:57|000.000.000.005|200|/page-n/
29/Oct/2020:08:41:58|000.000.000.005|200|/page-s/

编辑:您可以通过以下方式开始调查我的脚本生成的输出与您运行的 Daves 脚本版本生成的输出之间的差异:

$ awk -f morton-botfilter.awk.txt output3test.csv > morton.out
$ awk -f dave-botfilter.awk.txt output3test.csv > dave.out
$ ip=$(comm -13 <(sort morton.out) <(sort dave.out) | head -1)
$ grep "$ip" output3test.csv | head -5
03/Nov/2020:07:52:55|000.000.000.007|200|/page-7/
03/Nov/2020:08:05:32|000.000.000.007|200|/page-11/
03/Nov/2020:11:28:56|000.000.000.007|200|/page-77/
03/Nov/2020:13:52:32|000.000.000.007|200|/page-143/
03/Nov/2020:13:52:33|000.000.000.007|200|/page-144/

请注意,上面的第一个时间戳和最后一个时间戳之间的间隔远远超过 15 秒,这表明 dave-botfilter.awk.txt 中的脚本已损坏。请参阅下面的评论以获取更多信息。

答案2

既然你想学习 awk,并且显然已经GNUawk (gawk),awk -f script <logfile其中script包含

BEGIN{ split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec",n2m);
  for(i=1;i<=12;i++) m2n[n2m[i]]=i; FS="|"; }
function fixtime(str ,tmp){ split(str,tmp,"[:/]");
  return mktime(tmp[3] OFS m2n[tmp[2]] OFS tmp[1] OFS tmp[4] OFS tmp[5] OFS tmp[6]) }
++count[$2]==1 { first[$2]=fixtime($1) }
count[$2]==5 && fixtime($1)-first[$2]<15 { print $2 }

前两行设置了一个数组 m2n(月份到数字),它将 Jan 映射到 1,Feb 映射到 2 等,并将字段分隔符设置为|。 (它可以代替做m2n["Jan"]=1; m2n["Feb"]=2;等等,但这更乏味。

接下来的两行定义了一个函数,该函数使用 all/:作为分隔符来分割时间格式(无需首先将它们转换为空格),将月份名称转换为数字,根据需要重新排序并提供给mktime()(仅限 gawk)。您可以使用文字来代替 OFS(默认为一个空格且未更改)," "但我发现这更难看。

第五行和第六行找到第一的出现任何 IPaddr 并记住其时间戳,以及第五检测是否出现相同的 IPaddr,并将其时间戳与记住的时间戳进行比较,看间隔是否小于 15 秒。有些人会;next在第五行的操作中添加一个,以明确第五和第六脚本行不会在同一记录(即数据行)上执行,但我没有打扰。

优质教育基金。

如果您愿意,可以将整个脚本放在命令行中,'...'而不是使用脚本文件,但我不喜欢这样做超过 100 个字符。

答案3

#!/bin/bash
awk -v mon=$(locale abmon) -v FS='[/:|]' '
BEGIN           {for(n=split(mon, M, ";"); n; n--) Mn[M[n]]=n}
!A[$7]++        {IP[$7] = mktime($3" "Mn[$2]" "$1" "$4" "$5" "$6)}
A[$7]==5 && mktime($3" "Mn[$2]" "$1" "$4" "$5" "$6) - IP[$7] < 15 {print $7}
' file > bot_ip

-v mon=$(locale abmon)- 该变量mon被分配以下行:Jan;Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec
for(n=split(mon, M, ";"); n; n--)- 该函数返回我们在循环split中启动计数器的数组元素的数量for

答案4

您可能不只是查看第二个字段的第 1 次和第 5 次出现,而是希望查看相隔 5 次出现的所有情况。滑动窗口方法:

awk '
{
    n = c[$7] = ++c[$7] % 4
    m = index("..JanFebMarAprMayJunJulAugSepOctNovDec",$2)/3
    s = mktime($3 " " m " " $1 " " $4 " " $5 " " $6)
    if (s - t[$7,n] < 15 && !seen[$7]++) {
        print
    }
    t[$7,n] = s
}
' FS='[/:|]' output.csv

相关内容