Unix 包datamash
支持多种汇总操作的应用团体输入线数。例如1,此处datamash
用于计算第 1 列中每个值的第 2 列之和:
$ cat example.csv
1,10
1,5
2,9
2,11
$ datamash -t, -g 1 sum 2 < example.csv
1,15
2,20
虽然支持的功能也datamash
很广泛sum
(包括mean
、、、、、、等),stddev
但它不可扩展,AFAICT 。IOW,不支持用户提供他/她自己的摘要功能的任何机制。median
iqr
min
max
datamash
我的问题归结为:如何在zsh
2上通用地实现这种按组的命令应用程序?
下面尝试更准确地说明问题。 (希望这种精确的尝试不会使问题变得难以理解。)
首先,假设foo
代表一个(可能是复合的)命令,该命令发送到具有以下结构的 stdout 行:
我 分隔器 有效负载i j
...在哪里我,“组索引”,是某个整数,分隔器是一些常量分隔符序列(例如,
、 或$'\t'
),并且有效负载i j是一些任意文本(包括终止换行符)。此外,假设组索引我范围从 1 到氮,并且此输出中的行根据组索引排序。
对于每个整数 1 ≤ k ≤ 氮,让“k“第-组”指的是由片段组成的内容有效负载k jfoo
组索引为的所有行(在 的输出中)k。
接下来,假设bar
代表一个(可能是复合的)命令,该命令从 stdin 读取行并发出单行到标准输出。
现在,让结果kbar
表示应用到的输出k-th 组,让X<bar>
代表一些调用 的 shell 构造bar
。
我基本上是在寻找一种构造,X<bar>
使得管道
foo | X<bar>
发送到表单的 stdout 行
我 分隔器 结果我
编辑:
假设分隔器只是,
,那么以下似乎可以满足我的要求
TMPFILE=$( mktemp )
SEPARATOR=,
LASTGROUPID=
foo | (cat; echo) | while IFS= read -r LINE
do
GROUPID=${LINE%%$SEPARATOR*}
if [[ $GROUPID != $LASTGROUPID ]]
then
if [[ -n $LASTGROUPID ]]
then
echo -n "$LASTGROUPID$SEPARATOR"
cat $TMPFILE | bar
fi
LASTGROUPID=$GROUPID
: > $TMPFILE
fi
PAYLOAD=${LINE#*$SEPARATOR}
echo $PAYLOAD >> $TMPFILE
done
rm $TMPFILE
基本上,这用于$TMPFILE
收集下一组中的行。 (我宁愿避免临时文件,但我不知道该怎么做。)
现在我需要找出一种方法将其实现为一个函数,该函数可以将 表示的表达式bar
作为参数,并在上面给出的构造中稳健地使用它。
1此示例改编自datamash
手册页中给出的示例。
2虽然我主要对 感兴趣zsh
,但我bash
也有次要兴趣。
答案1
在我看来,这不像是一个 shell 的工作。我会这样做perl
......虽然这里可能就足够了:python
ruby
awk
$ cat sum
paste -sd + - | bc
$ sort -t , -k 1,1 input | awk -F, -v cmd=./sum '
function out() {printf "%s,", l;close(cmd)}
NR>1 && $1 != l {out()}
{print $2 | cmd; l=$1}
END {if (NR) out()}'
1,15
2,20
答案2
如果我知道您正在寻找什么:一个类似于从一组样本生成分布的脚本,但具有更多累积选项。我awk
为此写了一个脚本。
https://drive.google.com/open?id=0B0Kg_QLltwbNU21zbHFMY1hnSjQ
这并不完全是您想要的,但重叠必须很大。第一 - 索引可能不仅是整数,第二 - 唯一的累积方法是求和。但由于它只是一个脚本,因此您可以比 C 程序更容易地修改它。
最后,这样的脚本仅适用于数据集足够小的情况,对于较大的数据集,它太慢了!所以更专业的包是更好的(R
等等)。
PS 要添加其他累加器,请替换+=
为自定义函数(“monad”)。