如何阻止 SED 取消转义输出?

How to stop SED from un-escaping the output?

有一百万个与 sed 相关的问题,但我找不到这个具体案例。如果事实证明我是一个糟糕的 googler,我会很乐意接受纠正。

我有一个包含特殊字符和换行符的文件我们称它为query.kql:

Metrics
| where $__timeFilter(TimeGenerated)
| where ResourceProvider == "MICROSOFT.NETWORK"
| order by TimeGenerated asc

我还有一个 json 文件。它被称为 data.json:

{
"analytics": {
            "query": "{{query.kql}}",
            "resource": "$GlobalDataSource",
            "resultFormat": "time_series"
          }
}

我想做的是将 query.kql 的内容插入到 data.json 中的 {{query.kql}} 占位符中,以转义形式 (换行符->\n, " ->", 等等)

这给了我所需格式的 query.kql 的内容(有效):

q=$(sed -e "N;s/\n/\\n/" -e 's|["]|\"|g' query.kql)
#q: AzureMetrics\n| where $__timeFilter(TimeGenerated) | where ResourceProvider == \"MICROSOFT.NETWORK\"\n| order by TimeGenerated asc

我尝试过的:

# This does not work, because sed chokes on the result of the shell substitution:
sed -e "s/{{query.kql}}/$q/g" data.json
# Output: sed: -e expression #1, char 79: unterminated `s' command
# This works, but the output is wrong:
sed -e "s/{{query.kql}}/`echo $q`/g" data.json

# Output is unescaped and makes the json structure invalid:
"analytics": {
            "query": "AzureMetrics
| where $__timeFilter(TimeGenerated) | where ResourceProvider == "MICROSOFT.NETWORK"
| order by TimeGenerated asc",
            "resource": "$GlobalDataSource",
            "resultFormat": "time_series"
          },

我希望输出的是插入的 q 的确切内容:

{
"analytics": {
            "query": "AzureMetrics\n| where $__timeFilter(TimeGenerated) | where ResourceProvider == \"MICROSOFT.NETWORK\"\n| order by TimeGenerated asc",
            "resource": "$GlobalDataSource",
            "resultFormat": "time_series"
          }
}

如何让 sed 在输出中保持 $q 的原始内容? 我也乐于接受使用 awk、perl 或任何通常可从 bash 脚本获得的建议。

更新

原来我的主要问题是以正确转义的方式将文件内容读入 $q 变量。如果做得对,也不需要在第二个 sed 命令中使用 echo $q。 我最终完成了这项工作:

# The first part escapes quotes and backslashes, the second part replaces the newlines by \n
query=$( sed -z 's#["\]#\\\&#g;s/\n/\\n/g' query.kql)

# I had to do some playing around before I found a suitable separator char, but turns out ~ does the trick in this specific case.
sed -i -e "s~{{query.kql}}~$query~g" $data.json

看起来你快到了。我想如果你尝试双重转义字符串你会得到你想要的。请尝试以下操作:

q=$(cat query.kql | sed -e ':a;N;$!ba;s/\n/\\n/g' -e 's#["]#\\"#g')
sed -e "s/{{query.kql}}/$q/g" data.json

这是我的输出:

{
"analytics": {
            "query": "Metrics\n| where $__timeFilter(TimeGenerated)\n| where ResourceProvider == \"MICROSOFT.NETWORK\"\n| order by TimeGenerated asc",
            "resource": "$GlobalDataSource",
            "resultFormat": "time_series"
          }
}

编辑: 顺便说一句,在转义任何其他内容之前,您还应该转义反斜杠“\”。否则,您最终可能会将原始反斜杠解释为最终结果中的转义。 sed -e 's/\/\\/g' 就在所有其他替换之前。

使用sed

$ q=$(sed '2s/|/\\n&/;s/"/\\&/g;4s/|/\\n&/' query.kql)
$ sed "s/{{query.kql}}/`echo $q`/" data.json
{
"analytics": {
            "query": "AzureMetrics \n| where (TimeGenerated) | where ResourceProvider == \"MICROSOFT.NETWORK\" \n| order by TimeGenerated asc",
            "resource": "",
            "resultFormat": "time_series"
          }
}

要处理 shell 中的 JSON 你应该使用 jq:

jq --arg kql "$(< query.kql)" '.analytics.query = $kql' data.json
{
  "analytics": {
    "query": "Metrics\n| where $__timeFilter(TimeGenerated)\n| where ResourceProvider == \"MICROSOFT.NETWORK\"\n| order by TimeGenerated asc",
    "resource": "$GlobalDataSource",
    "resultFormat": "time_series"
  }
}

更新

由于 OP 事先不知道 JSON 结构,因此最好使用其核心库中具有 JSON 编码器的语言。

  • 与ruby;替换所有出现的 {{query.kql}}:
ruby -rjson -pe 'BEGIN {kql = File.read("query.kql").to_json[1..-2]}; gsub("{{query.kql}}", kql)' < data.json
  • jq;更新值为 "{{query.kql}}":
  • 的所有键
jq --arg kql "$(< query.kql)" '.. |= if (. == "{{query.kql}}") then . =  $kql else . end' data.json

这可能对你有用 (GNU sed):

sed '/{{\([^}]*\)}}/{
      s//\n\n/
      h
      s/.*\n\(.*\)\n.*//
      s/.*/cat "&"/e
      s/\n/\n/g
      s/"/\"/g
      H
      g
      s/\n.*\n\(.*\)\n\(.*\)//
      s/^/\n/
      D}' file

确定包含要插入的文件的行,即 {{}} 之间的字符串。

文件名用换行分隔。

复制该行。

删除文件名以外的所有内容。

用内容替换文件名。

转义换行符和双引号。

将修改后的文件内容字符串追加到原始行。

用修改后的内容替换文件名。

重复直到失败。