ChatGPT解决这个技术问题 Extra ChatGPT

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)。

有关“如何就地更改文件”的一些通用解决方案,另请参见stackoverflow.com/questions/6696842/…
FWIW,这里有一个功能请求:github.com/stedolan/jq/issues/105

p
peak

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

而不是 sponge

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

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

你遇到了两个问题:

这是文本处理的常见问题,在基本 Linux 发行版中没有解决。

jq 没有编写特殊的代码来克服这个问题。

一个很好的解决方案:

使用 brew install moreutils 或您最喜欢的包管理器安装 moreutils。这包含方便的程序海绵,仅用于此目的。

使用 cat myfile | jq blahblahblah |海绵我的文件。也就是说,运行 jq,捕获标准输出,当 jq 完成时,然后将标准输出写入 myfile(输入文件)。


J
Jeff Mercado

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

$ 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 很棒。你可以用它做很多事情。如果您需要通过更多控制进行更复杂的更新,我只需编写一个脚本来使用您最喜欢的脚本/编程语言进行更新。
C
Community

Using my answer to a duplicate question

分配打印整个对象并执行分配,因此您可以为修改后的 Actions 数组的 .Actions 分配一个新值 .Actions=([.Actions[] | if .properties.age == "3" then .properties.other = "no-test" else . end]) 我使用了 if 语句,但我们可以使用您的代码来做同样的事情 .Actions=[.Actions[] |选择 (.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/… 中的解释有很多不错的选择,因此请相应地调整您的回复。
J
James Hopbourn

使用 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 文件
t
tamerlaha

可以执行以下操作:

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


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

我使用 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

O
Orwellophile

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