【问题标题】:How to stop SED from un-escaping the output?如何阻止 SED 取消转义输出?
【发布时间】:2022-01-10 10:32:05
【问题描述】:

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

我有一个包含特殊字符和换行符的文件让我们称之为 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 的内容以转义形式(换行符->\n、“->”等)插入到 data.json 中的 {{query.kql}} 占位符中

这为我提供了所需格式的 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

【问题讨论】:

  • 你真的需要用\n替换换行符吗?
  • ,1 使用双引号即可:q="$(sed ...)"
  • .2 你不知道`echo $q`

标签: json bash sed


【解决方案1】:

要在 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 编码器的语言。

  • 红宝石;替换所有出现的{{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

【讨论】:

  • 谢谢,请记住这一点!在这种情况下不是最合适的,因为脚本在替换时不知道确切的 json 结构。
  • 哦!我更新了我的答案以反映这一点;-)
【解决方案2】:

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

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' 就在所有其他替换之前。

【讨论】:

    【解决方案3】:

    使用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"
              }
    }
    

    【讨论】:

    • 我认为在最后一个 RHS 中使用 echo 没有意义,看起来 $q 应该足够了。
    • @WiktorStribiżew 确实如此。但是,OP 提到他/她已经尝试过,但对他/她不起作用。我使用了一个似乎对他们有用的解决方案。
    • 输入字符串正确转义后,不再需要回显。谢谢!
    【解决方案4】:

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

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

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

    用换行符分隔文件名。

    复制该行。

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

    将文件名替换为其内容。

    转义换行符和双引号。

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

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

    重复直到失败。

    【讨论】:

    • 是 POSIX sed
    • @Fravadona 不,它在评估 RHS 的替换命令中使用e 标志,即cat file
    猜你喜欢
    • 1970-01-01
    • 2018-12-02
    • 2010-10-18
    • 1970-01-01
    • 2011-01-13
    • 1970-01-01
    • 1970-01-01
    • 2017-04-05
    • 1970-01-01
    相关资源
    最近更新 更多