91

我有一个需要在特定条件下更新的 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)。

4

8 回答 8

63

这篇文章解决了关于缺少与 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
于 2016-04-12T15:19:51.153 回答
38

而不是sponge

cat <<< $(jq 'QUERY' sample.json) > sample.json
于 2020-03-18T17:27:19.333 回答
12

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

$ 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 那样对文件进行就地更新。

于 2016-04-12T07:04:37.753 回答
12

你遇到了两个问题:

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

一个很好的解决方案:

  • 使用或您最喜欢的包管理器安装moreutils 。brew install moreutils这包含了方便的程序sponge,仅用于此目的。
  • 使用cat myfile | jq blahblahblah | sponge myfile. 也就是说,运行jq,捕获标准输出,当jq完成时,然后将标准输出写入myfile(输入文件)。
于 2020-03-27T21:14:52.447 回答
3

使用我对重复问题的回答

分配打印整个对象并执行分配,以便您可以.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"]

以上将输出编辑后的整个 json .Actions。jq 没有sed -i类似的功能,但您需要做的就是将它通过管道传送回海绵中的文件| sponge

 jq '.Actions=([.Actions[] | if .properties.age == "3" then .properties.other = "no-test" else . end])' sample.json | sponge sample.json
于 2016-04-15T19:50:40.467 回答
1

可以执行以下操作:

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

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

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

jq项目中的PS问题仍然开放:https ://github.com/stedolan/jq/issues/105

于 2021-10-20T22:59:50.003 回答
1

使用 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"
  }
}
于 2021-10-28T10:52:10.143 回答
-1

这个bash(可能sh兼容的)功能jqi将处理一切。

用法:jqi [-i] <filename> [jq options] <jq filter>

例如:

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

}

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

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

cat in.json | jq -i - > 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" )"
}
于 2021-11-05T17:32:19.377 回答