如何在正则表达式中使用awk变量?

42

我有一个名为domain的文件,其中包含一些域名。例如:

google.com
facebook.com
...
yahoo.com

我还有另一个文件叫做site,其中包含一些网站的URL和数字。例如:

image.google.com   10
map.google.com     8
...
photo.facebook.com  22
game.facebook.com   15
..

现在我要统计每个域名有多少个URL。例如:google.com10+8 个URL。所以我写了一个像这样的 awk 脚本:

BEGIN{
  while(getline dom < "./domain" > 0) {
    domain[dom]=0;
  }
  for(dom in domain) {
    while(getline < "./site" > 0) {
      if($1 ~/$dom$)   #if $1 end with $dom {
        domain[dom]+=$2;
      }
    }
  }
}

但是代码if($1 ~/$dom$)不像我想要的那样运行。因为正则表达式中的变量$dom被字面解释了。所以,第一个问题是:

是否有办法在正则表达式中使用变量$dom

接下来,由于我刚开始编写脚本,

是否有更好的方法来解决我的问题?

5个回答

55

awk可以匹配变量,如果您不使用//正则表达式标记。

if ( $0 ~ regex ){ print $0; }

在这种情况下,将所需的正则表达式作为字符串构建

regex = dom"$"

然后根据变量regex进行匹配。

if ( $1 ~ regex ) {
  domain[dom]+=$2;
}

24
首先,变量是 dom 而不是 $dom -- 将 $ 视为提取存储在变量 dom 中的列号的运算符。
其次,awk 不会插值 // 之间的内容 -- 那只是其中的一个字符串。
你需要使用 match() 函数,在该函数的第二个参数中可以使用被视为正则表达式的字符串:
if (match($1, dom "$")) {...}

我会编写以下这样的解决方案:

awk '
  FNR == NR {domain[$1] = 0; next}
  {
    for (dom in domain) {
      if (match($1, dom "$")) {
        domain[dom] += $2
        break
      }
    }
  }
  END {for (dom in domain) {print dom, domain[dom]}}
' domain site 

1
对于那些谈论变量不需要以 $ 为前缀的人,更好的解释(在我看来)是 $ 在 awk 中表示字段,因此 $dom 表示字段无论 dom 的值是什么。在 awk 中,变量未被引用且没有 $。这不是 shell! - Graham Nicholls

2

使用 awk 脚本的一种方法:

BEGIN {
    FS = "[. ]"
    OFS = "."
}

FNR == NR {
    domain[$1] = $0
    next
}

FNR < NR {
    if ($2 in domain) {
        for ( i = 2; i < NF; i++ ) {
            if ($i != "") {
                line = (line ? line OFS : "") $i
            }
        }
        total[line] += $NF
        line = ""
    }
}

END {
    for (i in total) {
        printf "%s\t%s\n", i, total[i]
    }
}

运行方式:

awk -f script.awk domain.txt site.txt

结果:

facebook.com    37
google.com  18

如果在site文件中出现类似于“first.second.example.com”这样的域名,那么这种方法将行不通。 - glenn jackman
@glennjackman,是的,你说得对。我没有考虑到 :-( - Steve

1

你显然想要只读取一次site文件,而不是每个domain条目都读取一次。修复这个问题非常简单。

同样,在awk中的变量(除了字段$0.. $9等)不需要加上前缀$。特别地,$dom是由变量dom标识的字段号码(通常,这将是0,因为域字符串不能转换为任何其他数字)。

我认为你需要找到一种方法从读取的site文件中获取域名。如果你不确定是否需要处理带有国家域名的网站,例如bbc.co.uk以及GTLDs中的网站(如google.com等),假设你不需要处理国家域名,你可以使用以下代码:

BEGIN {
    while (getline dom < "./domain" > 0) domain[dom] = 0
    FS = "[ .]+"
    while (getline  < "./site" > 0)
    {
        topdom = $(NF-2) "." $(NF-1)
        domain[topdom] += $NF          
    }
    for (dom in domain) print dom "  " domain[dom]
}

在第二个while循环中,有NF个字段;$NF包含计数,$1 .. $(NF-1)包含域的组件。因此,topdom最终包含顶级域名,然后用于索引第一个循环中初始化的数组。
给定问题中的数据(减去点行),输出为:
yahoo.com  0
facebook.com  37
google.com  18

0
以上答案的问题在于,如果您使用字符串而不是正则表达式/.../,则无法使用“元字符”(例如,在单词开头使用\<表示单词边界)。 如果您有一个域xyz.com和两个站点ab.xyz.com和cd.prefix_xyz.com,则这两个站点条目的数字将添加到xyz.com中。
以下是使用awk的管道和sed命令的解决方案:...
for(dom in domain) {
    while(getline < "./site" > 0) {
        # let sed replaces occurence of the domain at the end of the site
        cmd = "echo '" $1 "' | sed 's/\\<'" dom "'$/NO_VALID_DOM/'"
        cmd | getline x
        close(cmd)
        if (match(x, "NO_VALID_DOM")) { 
          domain[dom]+=$2;
        }
    }
    close("./site") # this misses in original code
}

...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接