【问题标题】:Jq to replace text directly on file (like sed -i)Jq 直接替换文件上的文本(如 sed -i)
【发布时间】:2016-08-02 14:16:52
【问题描述】:

我有一个需要在特定条件下更新的 json 文件。

示例 json

{
   "Actions" : [
      {
         "value" : "1",
         "properties" : {
            "name" : "abc",
            "age" : "2",
            "other ": "test1"
          }
      },
      {
         "value" : "2",
         "properties" : {
            "name" : "def",
            "age" : "3",
            "other" : "test2"
          }
      }
   ]
}

我正在写一个脚本,它利用Jq来匹配一个值并更新,如下所示

cat sample.json |  jq '.Actions[] | select (.properties.age == "3") .properties.other = "no-test"'

输出(打印到终端)

{
  "value": "1",
  "properties": {
    "name": "abc",
    "age": "2",
    "other ": "test1"
  }
}
{
  "value": "2",
  "properties": {
    "name": "def",
    "age": "3",
    "other": "no-test"
  }
}

虽然此命令进行了所需的更改,但它会在终端上输出整个 json,并且不会对文件本身进行更改。

请告知是否可以让 jq 直接对文件进行更改(类似于 sed -i)。

【问题讨论】:

标签: bash jq in-place edit-in-place


【解决方案1】:

这篇文章解决了关于缺少与 sed 的“-i”选项等效的问题,特别是所描述的情况:

我有一堆文件,将每个文件写入一个单独的文件并不容易。

有多种选择,至少如果您在 Mac 或 Linux 或类似环境中工作。它们的优缺点在 http://backreference.org/2011/01/29/in-place-editing-of-files/ 所以我将只关注三种技术:

一个是简单地使用“&&”沿线:

jq ... INPUT > INPUT.tmp && mv INPUT.tmp INPUT

另一个是使用sponge 实用程序(GNU moreutils 的一部分):

jq ... INPUT | sponge INPUT

第三个选项可能很有用,如果它有利于避免在没有更改的情况下更新文件。这是一个说明这种功能的脚本:

#!/bin/bash

function maybeupdate {
    local f="$1"
    cmp -s "$f" "$f.tmp"
    if [ $? = 0 ] ; then
      /bin/rm $f.tmp
    else
      /bin/mv "$f.tmp" "$f"
    fi
}

for f
do
    jq . "$f" > "$f.tmp"
    maybeupdate "$f"
done

【讨论】:

  • 如果文档对于命令行来说不是太大,则可以避免使用文件:json="$( jq ... file.json )" 加上printf '%s\n' "$json" >file.json
【解决方案2】:

而不是sponge

cat <<< $(jq 'QUERY' sample.json) > sample.json

【讨论】:

  • cat真的可以代替sponge吗?这可以保证始终有效吗?
  • 这不适用于我在带有 jq 1.5.1 的 ubuntu 18.04 上。运行命令后 Sample.json 为空。
  • 是的,这很好,但最好不要覆盖源文件。如果出现问题并且 stdout 没有显示任何内容,它将为空。当您需要复制+修改到其他地方时,这非常有用。
  • 这对我很有用,但是如何编写格式化(漂亮)json?这个写在一行中。
  • 这会在 RHEL7 上产生一个空白文件
【解决方案3】:

您需要在不更改上下文的情况下更新操作对象。通过在那里放置管道,您正在将上下文更改为每个单独的操作。你可以用一些括号来控制它。

$ jq --arg age "3" \
'(.Actions[] | select(.properties.age == $age).properties.other) = "no-test"' sample.json

这应该会产生:

{
  "Actions": [
    {
      "value": "1",
      "properties": {
        "name": "abc",
        "age": "2",
        "other ": "test1"
      }
    },
    {
      "value": "2",
      "properties": {
        "name": "def",
        "age": "3",
        "other": "no-test"
      }
    }
  ]
}

您可以将结果重定向到文件以替换输入文件。它不会像 sed 那样对文件进行就地更新。

【讨论】:

  • 谢谢杰夫,这非常有帮助。您会推荐什么工具直接对文件进行有条件的 json 更改?我有一堆文件,将每个文件写入一个单独的文件并不容易。再次感谢。
  • 如果你需要在命令行中做,jq 很棒。你可以用它做很多事情。如果您需要通过更多控制进行更复杂的更新,我只需编写一个脚本来使用您最喜欢的脚本/编程语言进行更新。
【解决方案4】:

您遇到了两个问题:

  • 这是文本处理的常见问题,在基本 Linux 发行版中未解决。
  • jq 没有编写特殊的代码来解决这个问题。

一个很好的解决方案:

  • 使用brew install moreutils 或您最喜欢的包管理器安装moreutils。这包含方便的程序sponge,仅用于此目的。
  • 使用cat myfile | jq blahblahblah | sponge myfile。也就是说,运行 jq,捕获标准输出,当 jq 完成后,将标准输出写入 myfile(输入文件)。

【讨论】:

    【解决方案5】:

    Using my answer to a duplicate question

    Assignment 打印整个对象并执行分配,以便您可以为修改后的 Actions 数组的 .Actions 分配一个新值

    .Actions=([.Actions[] | if .properties.age == "3" then .properties.other = "no-test" else . end])
    

    我使用了 if 语句,但我们可以使用您的代码来做同样的事情

    .Actions=[.Actions[] | select (.properties.age == "3").properties.other = "no-test"]
    

    上面将输出编辑.Actions的整个json。 jq 没有类似sed -i 的功能,但您需要做的就是通过管道将其返回到sponge 到带有| sponge 的文件中

     jq '.Actions=([.Actions[] | if .properties.age == "3" then .properties.other = "no-test" else . end])' sample.json | sponge sample.json
    

    【讨论】:

    • 管道输出到输入沿 `CMD FILE' 或等效的行通常被严重弃用,例如stackoverflow.com/questions/3055005/… 中的解释有很多不错的选择,因此请相应地调整您的响应。
    【解决方案6】:

    使用 tee 命令

    ➜ cat config.json|jq '.Actions[] | select (.properties.age == "3") .properties.other = "no-test"'|tee config.json
    {
      "value": "1",
      "properties": {
        "name": "abc",
        "age": "2",
        "other ": "test1"
      }
    }
    {
      "value": "2",
      "properties": {
        "name": "def",
        "age": "3",
        "other": "no-test"
      }
    }
    
    ➜ cat config.json
    {
      "value": "1",
      "properties": {
        "name": "abc",
        "age": "2",
        "other ": "test1"
      }
    }
    {
      "value": "2",
      "properties": {
        "name": "def",
        "age": "3",
        "other": "no-test"
      }
    }
    

    【讨论】:

    • 如果你把这个命令弄错了,你最终会得到一个空的 config.json 文件
    【解决方案7】:

    可以这样做:

    echo "$(jq '. + {"registry-mirrors": ["https://docker-mirror"]}' /etc/docker/daemon.json)" > /etc/docker/daemon.json
    

    所以它使用 jq 在子 shell 中获取文本并将其回显到“主”shell 中的文件。

    注意:这里的主要思想是说明如何在没有像sponge 这样的额外工具的情况下实现它。除了echo,您可以使用任何可以写入标准输出的命令,例如printf '%s' "$(jq ... file)" &gt; file.

    jq 项目中的 P.S 问题仍未解决:https://github.com/stedolan/jq/issues/105

    【讨论】:

    • 将从 {"transform": {"^.+\\.tsx?$": "ts-jest"}} 这样的内容中删除 \ -> {"transform": {"^.+\.tsx?$": "ts-jest"}}
    • 正如我所说,这是一种可能的方法,抱歉我没有解决您的问题,但您是否尝试使用 printf 而不是 echo
    【解决方案8】:

    这个bash(可能与sh兼容)函数jqi会处理一切。

    用法:jqi [-i] &lt;filename&gt; [jq options] &lt;jq filter&gt;

    例如:

    fix-node-sass() 
    { 
        jqi -i package.json '.resolutions += {"node-sass": "6.0.1"}' \
                      '| .devDependencies += {"node-sass": "6.0.1"}'
    
    }
    

    很像sedperl,指定-i 作为强制重写原始文件的主要参数。如果未指定-i,它将是“试运行”,输出将转到stdout

    如果你出于某种神秘的原因想做一些奇怪的事情,比如:

    cat in.json | jq -i - &gt; out.json

    然后out.json 将保存结果,或者在出错时保存in.json 的原始内容——即,out.json 应该是有效的 json。

    注意:少于 7 个字符的输出(例如 null)被视为错误,不会被覆盖。如果您愿意,可以禁用此安全功能。

    jqi () 
    { 
        local filename=$1;
        shift;
        local inplace=;
        local stdin=;
        if [[ $filename == "-i" ]]; then
            echo "jqi: in-place editing enabled" 1>&2;
            inplace=y;
            filename=$1;
            shift;
        fi;
        if [[ $filename == "-" ]]; then
            echo "jqi: reading/writing from stdin/stdout" 1>&2;
            if [ -n "$inplace" ]; then
                stdin=y;
                inplace=;
            fi;
            filename="/dev/stdin";
        fi;
        local tempname="$( mktemp --directory --suffix __jq )/$( dirname "$filename" ).$$.json";
        local timestamp="${tempname%json}timestamp";
        local -i error=0;
        cat "$filename" > "$tempname";
        touch "$timestamp";
        while :; do
            if jq "${*}" "$filename" > "$tempname"; then
                if test "$tempname" -nt "$timestamp"; then
                    local ls_output=($( ls -Lon "$tempname" ));
                    filesize=${ls_output[3]};
                    if [[ $filesize -lt 7 ]]; then
                        echo "jqi: read only $filesize bytes, not overwriting" 1>&2;
                        error=1;
                        break;
                    fi;
                    if [ -n "$inplace" ]; then
                        cat "$tempname" > "$filename";
                    else
                        echo "jqi: output from dry run" 1>&2;
                        cat "$tempname";
                    fi;
                    error=0;
                    break;
                else
                    echo "jqi: output not newer, not overwriting" 1>&2;
                    error=1;
                    break;
                fi;
            else
                echo "jqi: jq error, not overwriting" 1>&2;
                error=1;
                break;
            fi;
        done;
        if [ -n "$stdin" ] && [ $error -eq 1 ]; then
            echo "jqi: output original to stdout" 1>&2;
            cat "$filename";
        fi;
        rm "$tempname" "$timestamp";
        rmdir "$( dirname "$tempname" )"
    }
    

    【讨论】:

      猜你喜欢
      • 2022-10-21
      • 2011-09-25
      • 2022-11-14
      • 2012-10-14
      • 2014-12-22
      • 1970-01-01
      • 1970-01-01
      • 2018-08-21
      相关资源
      最近更新 更多