awk FS vs FPAT 拼图和计算单词但不是空白字段

awk FS vs FPAT puzzle and counting words but not blank fields

假设我有文件:

$ cat file
This, that;
this-that or this.

(行尾的标点并不总是存在...)

现在我想计算 个单词(单词被定义为一个或多个不区分大小写的 ascii 字母。)在典型的 POSIX *nix 中,您可以这样做:

sed -nE 's/[^[:alpha:]]+/ /g; s/ $//p' file | tr ' ' "\n"  | tr '[:upper:]' '[:lower:]' | sort | uniq -c
   1 or
   2 that
   3 this

使用 grep,您可以将其缩短一点以仅匹配您定义为单词的内容:

grep -oE '[[:alpha:]]+' file | tr '[:upper:]' '[:lower:]' | sort | uniq -c
# same output

使用 GNU awk,您可以使用 FPAT 仅复制您想要的匹配(忽略排序...):

gawk -v FPAT="[[:alpha:]]+" '
{for (i=1;i<=NF;i++) {seen[tolower($i)]++}}
END {for (e in seen) printf "%4s %s\n", seen[e], e}' file
   3 this
   1 or
   2 that

现在尝试在 POSIX 中复制 awk 我试过:

awk 'BEGIN{FS="[^[:alpha:]]+"}
{ for (i=1;i<=NF;i++) seen[tolower($i)]++ }
END {for (e in seen) printf "%4s %s\n", seen[e], e}' file
   2 
   3 this
   1 or
   2 that

注意 2 顶部有空白。这是因为第 1 行末尾的 ; 和第 2 行末尾的 . 中有空白字段。如果删除行尾的标点符号,此问题就会消失。

您可以通过在 awk 中设置 RS="" 来部分修复它(除了最后一行),但最后(唯一)行仍然会出现空白字段。

我也可以这样解决:

awk 'BEGIN{FS="[^[:alpha:]]+"}
{ for (i=1;i<=NF;i++) if ($i) seen[tolower($i)]++ }
END {for (e in seen) printf "%4s %s\n", seen[e], e}' file

这似乎有点不直接。

是否有我缺少的惯用修复程序来使 POSIX awk 的行为类似于 GNU awk 的 FPAT 解决方案?

对于 POSIX awk,我会使用 match 和内置的 RSTART 和 RLENGTH 变量:

#!awk
{
    s = [=10=]
    while (match(s, /[[:alpha:]]+/)) {
        word = substr(s, RSTART, RLENGTH)
        count[tolower(word)]++
        s = substr(s, RSTART+RLENGTH)
    }
}
END {
    for (word in count) print count[word], word
}
$ awk -f countwords.awk file
1 or
3 this
2 that

在我的 Mac 上使用默认的 BSD awk。

改用RS

$ gawk -v RS="[^[:alpha:]]+" '  # [^a-zA-Z] or something for some awks
[=10=] {                            # remove possible leading null string
    a[tolower([=10=])]++
}
END {
    for(i in a)
        print i,a[i]
}' file

输出:

this 3
or 1
that 2

在 gawk 和 Mac awk(版本 20200816)以及使用 [^a-zA-Z]

在 mawk 和 busybox awk 上成功测试

使用您显示的示例,请尝试以下 awk 代码。在 GNU awk 中编写和测试,以防您可以使用 RS 方法执行此操作。

awk -v RS='[[:alpha:]]+' '
RT{
  val[tolower(RT)]++
}
END{
  for(word in val){
    print val[word], word
  }
}
' Input_file

解释: 简单的解释就是,使用awkRS变量来使记录分隔符为[[:alpha:]] 然后在主程序中创建索引为 RT 变量的数组 val,并继续计算它在数组 val 中相对于相同索引的出现次数。在此程序的 END 块中遍历数组并打印索引及其各自的值。

GNU awk 使用 patsplit() 和第二个数组进行计数,你可以试试这个:

awk 'patsplit([=10=], a, /[[:alpha:]]+/) {for (i in a) b[ tolower(a[i]) ]++} END {for (j in b) print b[j], j}' file
3 this
1 or
2 that

这应该适用于 POSIX/BSD 或任何版本的 awk:

awk -F '[^[:alpha:]]+' '
{for (i=1; i<=NF; ++i) ($i != "") && ++count[tolower($i)]}
END {for (e in count) printf "%4s %s\n", count[e], e}' file

   1 or
   3 this
   2 that
  • 通过使用 -F '[^[:alpha:]]+',我们在任何非字母字符上拆分字段。
  • ($i != "") 条件将确保只计算 seen.
  • 中的非空字段