【问题标题】:Bash script group and count by a specific fieldBash 脚本分组并按特定字段计数
【发布时间】:2015-03-16 02:55:01
【问题描述】:

抱歉,如果我打开一个新问题,但它与前一个问题无关,因为现在我需要一个 bash 命令来分析输出。

我有一个查询的输出存储在这样的文件中:

3277654321    333011123456789
3277654321    333015123456789
3277654321    333103123456789
3277654321    333201123456789
3291234567    333991123456789
3291234567    333991123456789
3291234567    333011123456789

我需要一个 bash 命令来计算具有相同前 5 位数字的 field1 和 field2 并报告如下输出:

3277654321=4;33301=2;33310=1;33320=1    
3291234567=3;33399=2;33301=1

谢谢 卢卡斯。

【问题讨论】:

  • 这对awk 来说不会太具有挑战性,而且毫无疑问,最终会有人提供代码编写服务,尽管 SO 并不是“请为我编写代码”服务。但在我看来,直接从数据库生成您想要的报告会更好,使用实际生成最终结果的查询而不是中间列表。
  • 如果你是从数据库中查询,直接做可能会更容易。
  • 直接从数据库生成这样的报告是各种混乱。只要对数据进行了排序(数据库可以做到),那么后处理就不是不合理的。并且数据库可以而且应该为您做更多的工作:它可以合理地生成第一个字段、第二个字段的前 5 个字符以及条目数:SELECT field1, SUBSTR(field2, 1, 5) AS field2, COUNT(*) AS number FROM TheTable GROUP BY field1, field2 ORDER BY field1, field2。这样,通过网络传输的数据就会减少,如果数据库是远程的,这会很有帮助。
  • @JonathanLeffler:以下似乎在 sqlite3 中运行良好;对于 mysql,您需要将逗号更改为 group_concat 中的 SEPARATOR 一词:select field1||"="||SUM(count2)||";"||group_concat(field2||"="||count2,";") as fields FROM (select field1, SUBSTR(field2,1,5) AS field2, COUNT(*) as count2 from tmp GROUP BY field1, field2 ORDER BY field1, field2) GROUP BY field1 ORDER BY field1;。这不是凌乱,恕我直言。
  • @rici:是的,正如您所展示的,使用非标准的 GROUP_CONCAT 聚合(并且 ORDER BY 在子查询中可用,也是非标准的),还不错。 (非标准,如“不属于 ISO 标准 SQL”,AFAIK。)

标签: bash


【解决方案1】:

在原始数据上使用awk

您正在寻找的是一份失控报告。这一次,维基百科条目对这个主题没有太大帮助。样本数据显示排序;此解决方案假定数据已排序,因此(但如果未排序,则在 awk 脚本之前添加排序操作很简单;OTOH,由于数据来自数据库,DBMS 可以很好地对数据)。

出于测试目的,我创建了一个文件awk.script,其中包含:

{   f1 = $1
    f2 = substr($2, 1, 5)
    if (oldf1 != f1)
    {
        if (oldf1 != 0)
        {
            summary = summary ";" oldf2 "=" f2_count
            printf("%s=%d%s\n", oldf1, f1_count, summary)
        }
        oldf1 = f1
        f1_count = 0
        oldf2 = f2
        f2_count = 0
        summary = ""
    }
    else if (oldf2 != f2)
    {
        summary = summary ";" oldf2 "=" f2_count
        oldf2 = f2
        f2_count = 0
    }
    f1_count++
    f2_count++
}
END {
    if (oldf1 != 0)
    {
        summary = summary ";" oldf2 "=" f2_count
        printf("%s=%d%s\n", oldf1, f1_count, summary)
    }
}

并将七行样本数据放入一个名为data的文件中,然后运行:

$ awk -f awk.script data
3277654321=4;33301=2;33310=1;33320=1
3291234567=3;33399=2;33301=1
$

让 DBMS 做更多的工作

目前,数据类似于查询的输出,例如:

SELECT Field1, Field2
  FROM SomeTable
 ORDER BY Field1, Field2

通过让 DBMS 生成第一个字段、第二个字段的前 5 个字符以及条目数的计数,可以为您的报告提供更好的输出:

SELECT field1, SUBSTR(field2, 1, 5) AS field2, COUNT(*) AS number
  FROM SomeTable
 GROUP BY field1, field2
 ORDER BY field1, field2

这样,通过网络传输的数据就会减少,如果数据库是远程的,这会很有帮助。您还有一个更简单的报告。数据文件变为(data2):

3277654321 33301 2
3277654321 33310 1
3277654321 33320 1
3291234567 33399 2
3291234567 33301 1

awk 脚本变为 (awk.script2):

{   
    if (oldf1 != $1)
    {
        if (oldf1 != 0)
            printf("%s=%d%s\n", oldf1, f1_count, summary)
        oldf1 = $1
        f1_count = 0
        summary = ""
    }
    summary = summary ";" $2 "=" $3
    f1_count += $3
}
END {
    if (oldf1 != 0)
        printf("%s=%d%s\n", oldf1, f1_count, summary)
}

示例运行:

$ awk -f awk.script2 data2
3277654321=4;33301=2;33310=1;33320=1
3291234567=3;33399=2;33301=1
$

让 DBMS 做更多的工作

根据您的 DBMS 以及它是否支持子查询中的 GROUP_CONCATORDER BY 子句,您可以注意到 rici suggested “这不是那么混乱,恕我直言”。

以下似乎在 SQLite3 中运行良好;对于 MySQL,您需要将 GROUP_CONCAT 中的逗号更改为 SEPARATOR:

SELECT field1 || "=" || SUM(count2) || ";" ||
           group_concat(field2 || "=" || count2, ";") AS fields
  FROM (SELECT field1, SUBSTR(field2, 1, 5) AS field2, COUNT(*) AS count2
          FROM tmp
         GROUP BY field1, field2
         ORDER BY field1, field2
       )
 GROUP BY field1
 ORDER BY field1

请注意,据我所知,子查询中的GROUP_CONCATORDER BY 子句都不是由 ISO 标准 SQL 定义的,因此并非所有 DBMS 都支持这些功能。 (出于某种原因,ORDER BY 功能被省略了,但该推理不包括对“正交性”的考虑。)

如果 DBMS 以您需要的格式生成数据,则无需 awk 脚本对其进行后处理。什么是最好的最终将取决于你在做什么。通常,在有意义的地方使用 DBMS 进行计算。 IMO,不要将 DBMS 用于所有格式——我希望在 DBMS 之外完成带有分页等的报告生成——但如果可以说服它生成你需要的数据,那么一定要让它完成工作。

【讨论】:

  • 真棒乔纳森,查询的问题是我需要完整的 field2 来报告回其他日志文件。但是,该方法很酷,但是如何在不使用外部脚本文件的情况下在代码中实现呢?
  • 如果您在其他地方需要完整的 field2,那么您需要使用第一个选项。您可以通过将脚本用单引号括起来代替命令行中的“-f awk.script”来避免单独的脚本文件。如果你喜欢(我不喜欢,但你可能对这个主题有不同的看法),你可以将整个程序扁平化为一行,只要你添加适当的分号。不过,我强烈不建议这样做。您需要代码可读。
  • 您可以将两个脚本合并为一个。例如,您的第一个脚本可能是 gawk '{arr[$1][substr($2,0,5)]++}END{for (i in arr) for(j in arr[i]) print i, j, arr[i][j]}' groupTest 。您可以在此基础上进行修改以获得一个脚本。
【解决方案2】:

各位,我想分享一个“优雅”的解决方案。感谢其他社区用户,他们驱使我提出一些建议。

awk     'NR>0   {C1[$1]++
                 C2[$1,substr($2,1,5)]++
                }
         END {for (c2 in C2) {split (c2, cx, SUBSEP); print cx[1] "=" C1[cx[1]] ";" cx[2] "=" C2[c2]}}
        ' SUBSEP=";" out.txt | sort | awk     '$1 != L        {printf "%s%s", LT, $1; L=$1; LT="\n"}
                        {printf ";%s", $2}
         END {printf "\n"}
        ' FS=";"

3277654321=4;33301=2;33310=1;33320=1
3291234567=3;33399=2;33301=1

还有 rici,这不是我请人为我编写代码的情况。这是一个大脚本的一小部分,所以我只是就如何做一件小事寻求帮助。我对不同的方法很感兴趣,这就是为什么我在没有提供任何代码示例的情况下提出问题。感谢所有参与此问题的 SO 用户,我仍然愿意尝试不同的方法。

【讨论】:

    猜你喜欢
    • 2022-01-08
    • 2014-11-05
    • 1970-01-01
    • 1970-01-01
    • 2022-11-24
    • 1970-01-01
    • 2012-12-31
    • 2014-12-19
    • 1970-01-01
    相关资源
    最近更新 更多