将命令应用于标准输入中的行组

将命令应用于标准输入中的行组

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,不支持用户提供他/她自己的摘要功能的任何机制。medianiqrminmaxdatamash

我的问题归结为:如何在zsh2上通用地实现这种按组的命令应用程序?


下面尝试更准确地说明问题。 (希望这种精确的尝试不会使问题变得难以理解。)

首先,假设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......虽然这里可能就足够了:pythonrubyawk

$ 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”)。

相关内容