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
解释: 简单的解释就是,使用awk
的RS
变量来使记录分隔符为[[: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
. 中的非空字段
假设我有文件:
$ 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]
使用您显示的示例,请尝试以下 awk
代码。在 GNU awk
中编写和测试,以防您可以使用 RS
方法执行此操作。
awk -v RS='[[:alpha:]]+' '
RT{
val[tolower(RT)]++
}
END{
for(word in val){
print val[word], word
}
}
' Input_file
解释: 简单的解释就是,使用awk
的RS
变量来使记录分隔符为[[: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
. 中的非空字段