我有来自管道的以下输出。
Lorem dolor sit amet consectetur
Lorem ipsum dolor sit ,
Lorem dolor sit amet ,
Lorem dolor ipsum sit !
编辑
列数由我/用户指定,但不太可能超过 10。我目前最大的数据集如下所示:
prev_command | wc -l
$ 23805483
如果我没记错的话,有超过 300,000 个独特的 token 。
字段由空格分隔。字段可能包含各种特殊符号(→æ€...ð),但不能包含空格或制表符。我不一定知道列数。
对于每一列(字段),我需要对字数进行排序:本质上是 sort |唯一的-c |排序-nr
。期望的输出:
4 Lorem 3 dolor 2 sit 2 amet 2 ,
1 ipsum 1 dolor 2 sit 1 consectetur
1 ipsum 1 !
理想情况下,值和键由空格分隔,每个 val-key 对由制表符分隔。由于长度不同,第 3 行实际上是 \s\t\s\t1\sipsum\t\s\t1\s!
。
到目前为止,我所拥有的是一个可以工作的 shell 脚本,但有几件事我不喜欢它。以下是相关行。
#!/usr/bin/env sh
tmp="$(mktemp -d)"
trap 'rm -rf -- "$tmp"' EXIT
data="${1:-/dev/stdin}"
[ "$data" != "$1" ] && cat $data > $tmp/table && data="$tmp/table"
count() {
sort | uniq -c | sort -rn \
| awk -v x=" " '{ print $1 x $2 }'
}
# infer column number from first line
ncol=$(head -1 $data | wc -w)
# loop over columns in parallel and save each in temporary file
for i in $(seq $ncol); do
cut -f $i -d " " $data | count > $tmp/col$i &
done
wait
paste $tmp/col[0-9]*
- 如何直接在标准输入上获取数据流,并且仍然可以选择获取文件参数而不会损失太多速度?如果我删除带有临时文件的步骤,并将原始管道通过管道传输到脚本中,我仍然会得到输出,但不是预期的输出。不知道发生了什么。我尝试了
while read -ra...
,但不知道在循环中要做什么。 - 有什么办法可以避免整个临时文件业务?在某些情况下,输入可能太大而无法保存在内存中。使用 paste 将输出转换为正确的格式也很痛苦,因为它是参差不齐的。当我只想要标签时,我目前将它通过管道传输到
sed -e 's/\t\t/\t\t\t/g' -e 's/^\t/\t\t/g '
。布莱尔。与具有前导空格的uniq -c
相同。 - 如何加快速度?我确实有一个 perl 版本的
count()
,但它本质上是在做同样的事情,而不是处理 i/o。
我更喜欢 bash/awk 解决方案而不是 perl,因为我不太了解 perl,并且更喜欢我理解的解决方案(并且还可以最大限度地减少依赖关系)。但如果有人知道一种令人难以置信的快速 perl 方法,我很乐意接受。 :D
这个任务对我来说似乎很简单,但我找不到任何可以处理可变列和管道输入的东西。 提前感谢您的任何提示! :)
最佳答案
另一个awk
解决方案
$ awk 'NR==FNR {for(i=1;i<=NF;i++) c[i,$i]++;next}
{f=line="";
for(i=1;i<=NF;i++)
{k=i SUBSEP $i;
if(k in c)
{f=1; line=line sprintf("%d %s",c[k],$i); delete c[k]};
line=line "\t"}
if(f) print line}' file{,}
4 Lorem 3 dolor 2 sit 2 amet 1 consectetur
1 ipsum 1 dolor 2 sit 2 ,
1 ipsum 1 !
由 Ed Morton 编辑以显示由 gawk -o-
pretty-print 的相同脚本:
NR == FNR {
for (i = 1; i <= NF; i++) {
c[i, $i]++
}
next
}
{
f = line = ""
for (i = 1; i <= NF; i++) {
k = i SUBSEP $i
if (k in c) {
f = 1
line = line sprintf("%d %s", c[k], $i)
delete c[k]
}
line = line "\t"
}
if (f) {
print line
}
}
关于linux - 在 Bash/Awk/Perl 中有效地按列计算 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65886868/