【问题标题】:awk FS vs FPAT puzzle and counting words but not blank fieldsawk FS vs FPAT 拼图和计数单词但不是空白字段
【发布时间】:2021-12-26 20:31:59
【问题描述】:

假设我有文件:

$ 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 解决方案的惯用修复方法?

【问题讨论】:

  • 该模式不需要-P,它没有特定于pcre的内容:-E 可以。
  • 注意:并非所有行的末尾都有非字母,因此空白字段不会总是存在...

标签: bash awk


【解决方案1】:

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

#!awk
{
    s = $0
    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。

【讨论】:

    【解决方案2】:

    改用RS

    $ gawk -v RS="[^[:alpha:]]+" '  # [^a-zA-Z] or something for some awks
    $0 {                            # remove possible leading null string
        a[tolower($0)]++
    }
    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 上成功测试

    【讨论】:

    • 这是一个非常好的 g?awk 解决方案。
    • 请注意,POSIX awk 不支持多字符 RS。只需使用 gawk --posix ... 运行此程序,即可看到截然不同的行为。
    • 请注意,POSIX awk 不支持多字符 嗯。它确实在 Mac 默认 awk 上工作,但随后在 the docs say 上工作 如果 RS 包含多个字符,则结果未指定。 并运行 gawk--posix确实打破了这个......
    【解决方案3】:

    使用您显示的示例,请尝试遵循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:]],然后在主程序中创建其索引的数组val是 RT 变量,并根据数组 val 中的相同索引继续计算其出现次数。在此程序的END 块中,遍历数组并使用其各自的值打印索引。

    【讨论】:

    • 创意!但是,RT 变量只是 GNU awk,不是吗?如果我知道它是 GNU,我会使用 FPAT。
    • @dawg,感谢您的鼓励。老实说,我没有 posix awk,所以我无法对其进行测试,这就是为什么我提到在 GNU awk 中编写和测试的原因,如果你想请求测试一次,我们都可以了解它是否有效:) 干杯。
    • 它在 POSIX awk 上不起作用,但在 Mac 上的 gawk 上起作用。
    【解决方案4】:

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

    awk 'patsplit($0, 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
    

    【讨论】:

    • 谢谢!但真正寻找非 GNU 解决方案,因为 FPAT 解决方案效果很好。寻找一个完全 POSIX / GNU 中立的解决方案。
    【解决方案5】:

    这应该适用于 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 中的非空字段。

    【讨论】:

    • 如果您删除示例中行尾的标点符号,现在合法词不再包含在字数中。从示例中删除 ;. 会将 this 减少到 2,将 that 减少到 1。
    • 啊,这很好。答案已更新以解决此问题
    • 现在类似于我已经拥有的{ for (i=1;i&lt;=NF;i++) if ($i) seen[tolower($i)]++ } 的解决方案,这是问题的核心:这是最佳交叉g?awk 解决方案吗?
    • 确实非常相似(抱歉,我在多个代码示例中迷失了方向)。我认为这是一个相当理想的解决方案,适用于所有 awk 版本。
    • 我回到最初触发问题的变体......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    相关资源
    最近更新 更多