Jq to replace text directly on file (like sed -i)

大兔子大兔子 提交于 2019-11-26 07:44:39

问题


I have a json file that needs to be updated on a certain condition.

Sample json

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

I am writing a script that makes use of Jq to match a value and update, as shown below

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

Output (printed to terminal)

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

While this command makes the needed change, it outputs the entire json on the terminal and does not make change to the file itself.

Please advise if there is an option to have jq make changes on the file directly (similar to sed -i).


回答1:


This post addresses the question about the absence of the equivalent of sed's "-i" option, and in particular the situation described:

I have a bunch of files and writing each one to a separate file wouldn't be easy.

There are several options, at least if you are working in a Mac or Linux or similar environment. Their pros and cons are discussed at http://backreference.org/2011/01/29/in-place-editing-of-files/ so I'll focus on just three techniques:

One is simply to use "&&" along the lines of:

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

Another is to use the sponge utility (part of GNU moreutils):

jq ... INPUT | sponge INPUT

The third option might be useful if it is advantageous to avoid updating a file if there are no changes to it. Here is a script which illustrates such a function:

#!/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



回答2:


You'll want to update the action objects without changing the context. By having the pipe there, you're changing the context to each individual action. You can control that with some parentheses.

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

This should yield:

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

You can redirect the results to a file to replace the input file. It won't do in-place updates to a file as sed does.




回答3:


Using my answer to a duplicate question

Assignment prints the whole object with the assignment executed so you could assign a new value to .Actions of the modified Actions array

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

I used an if statement but we can use your code to do the same thing

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

The above will output the entire json with .Actions edited. jq does not had sed -i like functionality, but all you need to do is pipe it back into a sponge to the file with | sponge

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


来源:https://stackoverflow.com/questions/36565295/jq-to-replace-text-directly-on-file-like-sed-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!