【问题标题】:Parsing JSON-like configuration file using R or AWK使用 R 或 AWK 解析类似 JSON 的配置文件
【发布时间】:2014-06-11 05:27:00
【问题描述】:

我需要你的帮助,因为我多年前曾与 AWK 一起工作,现在我的知识已经生疏了。尽管通过阅读一些指南让我的记忆有所恢复,但我确信我的代码包含一些错误。我在 SO 上阅读的大多数相关问题都涉及解析标准JSON,因此该建议不适用于我的情况。唯一接近我正在寻找的答案是这个 SO 问题的公认答案:using awk sed to parse update puppet file。但是我正在尝试实现两次解析,而我在那个答案中看不到它(或者理解不够)。

在考虑了其他选项(从R 本身到m4 以及介于两者之间的各种模板引擎)之后,我考虑过通过jsonlitestringr 包在R 中实现该解决方案,但这并不优雅。我决定编写一个简短的AWK 脚本来解析我的R 项目的数据收集配置文件,然后我的R 代码将读取它们。这样的文件大部分是JSON 文件,但有一些补充:

1) 它包含参数嵌入变量,指的是同一文件中JSON元素的值(为简单起见,我决定将其放在根目录中JSON 树);

2) 参数通过在相应元素的名称之前放置一个星号 ('*') 来表示。

最初我计划了两种 嵌入变量的类型,您可以在此处看到它们 - internal(引用同一文件中的 JSON 元素,格式:${var})和外部(用户提供,格式:%{var})。但是,我仍然不清楚为外部参数传递值的机制和好处,所以目前我只专注于解析只包含内部变量的配置文件。所以,请暂时忽略外部变量。

示例配置文件

{
   "*source":"SourceForge",
   "*action":"import",
   "*schema":"sf0314",
   "data":[
      {
         "indicatorName":"test1",
         "indicatorDescription":"Test Indicator 1",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT * FROM sf0305.users WHERE user_id < 100"
      },
      {
         "indicatorName":"test2",
         "indicatorDescription":"Test Indicator 2",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT * 
                       FROM sf1104.users a, sf1104.artifact b 
                       WHERE a.user_id = b.submitted_by AND b.artifact_id = 304727"
      },
      {
         "indicatorName":"totalProjects",
         "indicatorDescription":"Total number of unique projects",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT COUNT(DISTINCT group_id) FROM ${schema}.user_group"
      },
      {
         "indicatorName":"totalDevs",
         "indicatorDescription":"Total number of developers per project",
         "indicatorType":"numeric",
         "resultType":"data.frame",
         "requestSQL":"SELECT COUNT(*) FROM ${schema}.user_group WHERE group_id = %{group_id}"
      }
   ]
}

AWK 脚本

#!/usr/bin/awk -f

BEGIN {
  first_pass = true;
  param = "\"\*[a-zA-Z^0-9]+?\"";
  regex = "\$\{[a-zA-Z^0-9]+?\}";
  params[""] = 0;
}

{
  if (first_pass)
    if (match($0, param)) {
      print(substr($0, RSTART, RLENGTH));
      params[param] = substr($0, RSTART, RLENGTH);
    }
  else
      gsub(regex, params[regex], $0);
}

END {
  if (first_pass) {
    ARGC++;
    ARGV[ARGIND++] = FILENAME;
    first_pass = false;
    nextfile;
  }
}

任何帮助将不胜感激!谢谢!

更新(基于 G. Grothendieck 的回答)

以下代码(包装在一个函数中,对原始答案稍作修改)行为不正确,意外输出所有标记(带有“_”)配置键的值,而不仅仅是引用的:

generateConfig <- function(configTemplate, configFile) {

  suppressPackageStartupMessages(suppressWarnings(library(tcltk)))
  if (!require(gsubfn)) install.packages('gsubfn')
  library(gsubfn)

  regexKeyValue <- '"_([^"]*)":"([^"]*)"'
  regexVariable <- "[$]{([[:alpha:]][[:alnum:].]*)}"

  cfgTmpl <- readLines(configTemplate)

  defns <- strapplyc(cfgTmpl, regexKeyValue, simplify = rbind)
  dict <- setNames(defns[, 2], defns[, 1])
  config <- gsubfn(regexVariable, dict, cfgTmpl)

  writeLines(config, con = configFile)
}

函数调用如下:

if (updateNeeded()) {
  <...>
  generateConfig(SRDA_TEMPLATE, SRDA_CONFIG)
}

更新 2(根据 G. Grothendieck 的要求)

函数updateNeeded()检查两个文件的存在和修改时间,然后根据逻辑决定是否需要(重新)生成配置。文件(返回boolean)。

以下是模板配置文件(SRDA_TEMPLATE &lt;- "./SourceForge.cfg.tmpl")的内容:

{
   "_source":"SourceForge",
   "_action":"import",
   "_schema":"sf0314",
   "data":[
      {
         "indicatorName":"test1",
         "indicatorDescription":"Test Indicator 1",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT * FROM sf0305.users WHERE user_id < 100"
      },
      {
         "indicatorName":"test2",
         "indicatorDescription":"Test Indicator 2",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT * 
                       FROM sf1104.users a, sf1104.artifact b 
                       WHERE a.user_id = b.submitted_by AND b.artifact_id = 304727"
      },
      {
         "indicatorName":"totalProjects",
         "indicatorDescription":"Total number of unique projects",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT COUNT(DISTINCT group_id) FROM ${schema}.user_group"
      },
      {
         "indicatorName":"totalDevs",
         "indicatorDescription":"Total number of developers per project",
         "indicatorType":"numeric",
         "resultType":"data.frame",
         "requestSQL":"SELECT COUNT(*) FROM ${schema}.user_group WHERE group_id = 78745"
      }
   ]
}

以下是自动生成的配置文件(SRDA_CONFIG &lt;- "./SourceForge.cfg.json")的内容:

{
   "_source":"SourceForge",
   "_action":"import",
   "_schema":"sf0314",
   "data":[
      {
         "indicatorName":"test1",
         "indicatorDescription":"Test Indicator 1",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT * FROM sf0305.users WHERE user_id < 100"
      },
      {
         "indicatorName":"test2",
         "indicatorDescription":"Test Indicator 2",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT * 
                       FROM sf1104.users a, sf1104.artifact b 
                       WHERE a.user_id = b.submitted_by AND b.artifact_id = 304727"
      },
      {
         "indicatorName":"totalProjects",
         "indicatorDescription":"Total number of unique projects",
         "indicatorType":"numeric",
         "resultType":"numeric",
         "requestSQL":"SELECT COUNT(DISTINCT group_id) FROM SourceForge import sf0314.user_group"
      },
      {
         "indicatorName":"totalDevs",
         "indicatorDescription":"Total number of developers per project",
         "indicatorType":"numeric",
         "resultType":"data.frame",
         "requestSQL":"SELECT COUNT(*) FROM SourceForge import sf0314.user_group WHERE group_id = 78745"
      }
   ]
}

通知SourceForgeimport,在sf0314 之前意外填充。

非常感谢答案作者的帮助!

【问题讨论】:

  • 这里的问题是什么?您可以控制输入配置文件的格式吗?
  • @EtanReisner:问题是我的 AWK 代码不起作用。相应地,问题是如何修复代码。是的,我控制着配置文件。无论我是否会采用 G. Grothendieck 提供的解决方案,我都乐意接受有关将我的代码修复为学习经验的建议。
  • [a-zA-Z^0-9] 应该做什么?匹配数字、字母和^ 字符?当我运行您的 awk 脚本时,我在END 部分收到四个关于不必要转义字符的警告和对nextfile 的投诉。什么对你“不起作用”?
  • 所以在尝试让你的 awk 工作时,我发现了很多问题。您正在尝试使用正则表达式模式作为参数表中的键,但它们永远不会改变,因此根本不会得到您想要的。您的正则表达式本身需要调整(至少在此处),以便 awk 抛出关于它们的警告。您将变量名称设置为参数表中的值(因为您从不处理该行的右侧)。
  • ^ 是方括号中的第一个字符时的补码范围。所以[0-9] 表示匹配 0 到 9 之间的任何数字,而 [^0-9] 表示匹配 不是 0 到 9 之间的任何数字。

标签: json r parsing awk template-engine


【解决方案1】:

我假设目标是将每个出现的${...} 替换为星线上给出的定义。在帖子中,它表明您正在查看 awk,因为 R 解决方案并不优雅,但我认为这可能是由于使用 R 采用的方法,我假设 R 解决方案仍然可以接受,如果使用不同的方法它会产生一个相当紧凑的解决方案。

这里config.json 是输入 json 文件的名称,config.out.json 是替换定义的输出文件。

我们读入文件并使用strapplyc 提取出定义的两列矩阵defns。我们将其改造成一个向量dict,其值是变量的值,其名称是变量的名称。然后我们使用gsubfn 插入使用dict 列表的定义。最后我们把它写回来。

library(gsubfn)

Lines <- readLines("config.json")

defns <- strapplyc(Lines, '"\\*([^"]*)":"([^"]*)"', simplify = rbind)
dict <- setNames(as.list(defns[, 2]), defns[, 1])
Lines.out <- gsubfn("[$]{([[:alpha:]][[:alnum:].]*)}", dict, Lines)

writeLines(Lines.out, con = "config.out.json")

REVISED dict 应该是一个列表,而不是一个命名的字符向量。

【讨论】:

  • 非常感谢您的解决方案!你是对的 - R 很好,解决方案似乎比我的 AWK 代码更优雅(很可能可以重写以大致匹配你的 R 解决方案)。在您回答之前,我从未听说过 gsubfn 包 - 通常人们推荐 plyr 或 R base *apply 函数。但是,我理解你为什么推荐gsubfn :-)。再次感谢您!
  • 奇怪的是,这段代码(稍作修改)开始无法正常工作,意外地将其他(未引用)配置键的值放在引用的之前。我将添加代码作为您答案的临时更新。
  • 我刚刚用我的代码更新了你的答案以供调查,但它神秘地消失了......
  • 在我的问题底部而不是您的答案中发布了我的更新,并请求您提供帮助。希望现在更新不会消失。期待您的来信!
  • 代码中存在错误。在这种情况下,gsubfn 要求 dict 是一个列表。 (如果它的字符那么它减少到gsub)。已更正代码中的dict&lt;- 行。
【解决方案2】:

我相信:

#!/usr/bin/awk -f

BEGIN {
  param = "\"\\*([a-zA-Z]+?)\":\"([^\"]*)\"";
  regex = "\\${([a-zA-Z]+?)}";
}

NR == FNR {
    if (match($0, param, a)) {
      params[a[1]] = a[2]
    }
    next
}

match($0, regex, a) {
  gsub(regex, params[a[1]], $0);
}
1

为给定的输入做你想做的事(当以awk -f file.awk input.conf input.conf 运行时)。

【讨论】:

  • 你用的是什么awk?它使用两个。 NR == FNR 是对第一个输入文件的测试。因此该块第一次执行(并且next 在那里停止处理脚本)并且第二次通过另一个块有机会匹配。如果您可以保证会有一个标记,或者在第一次使用后不会出现任何定义,那么您可以一次完成所有操作。
  • 所以我相信上面的脚本是特定的(匹配的第三个参数)。我不确定下一个问题是什么,但这可能是另一个兼容问题(或者只是缺少分号,您使用的 [mn]awk 中的任何一个都认为 gawk 不需要)。
  • 要进行上述单次传递,只需删除 next 行。 (如果您愿意,您也可以删除NR == FNR 外部块并使模式匹配部分看起来像正则表达式匹配部分。)然后只使用列出一次的输入文件运行它。
  • 如果同一行有两个不同的 ${...},此代码会将它们都替换为与第一个关联的值。
  • @G.Grothendieck 是的。它可能应该在gsub 的第一个参数中使用a[1] 来仅替换最初匹配的内容。虽然这仍然不能正确处理多个替换。为此,您还需要循环调用以匹配。
猜你喜欢
  • 2014-01-30
  • 1970-01-01
  • 2014-06-17
  • 2017-01-18
  • 2019-03-01
  • 2017-11-10
  • 2018-10-27
  • 2023-03-12
相关资源
最近更新 更多