jq 直接替换文件上的文本(如 sed -i)



我有一个 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(。

这篇文章解决了关于缺少 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

而不是sponge

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

您遇到了两个问题:

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

一个好的解决方案:

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

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

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

我使用yq,对于高级用户,需要此-i(就地更新(,希望添加到jq

yq -iP '.Email.Port=3030' config.json -o json
  • -i到位更新
  • -P漂亮的印花
  • -o输出应为 json
<小时 />
yq --version
yq (https://github.com/mikefarah/yq/) version 4.21.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"
  }
}

使用我对重复问题的回答

赋值

在执行赋值的情况下打印整个对象,以便您可以将新值分配给修改后的 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

可以执行以下操作:

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

因此,它使用 jq 在子外壳中获取文本并将其回显到"主"外壳中的文件。

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

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

最简单的方法是先将文件加载到变量中,然后将其发送到 jq 中。

content=$(cat sample.json) && 
jq '<your jq script>' <<<$content >sample.json

这仅在您的 JSON 文件适合 shell 变量时才有效。要找出系统上 bash 变量运行的最大大小,请执行以下操作:

getconf ARG_MAX

我的显示 2MB。

这个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"}'
}

sedperl非常相似,指定-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" )"
}

一行中:cat file.json | jq '.' | tee file.json.json >/dev/null

相关内容

  • 没有找到相关文章

最新更新