Home > Blockchain >  How to update a json file by the contents read from other files using jq?
How to update a json file by the contents read from other files using jq?

Time:10-14

I have several text files, each one has a title inside. For example:

echo 'title: hello' > 1.txt
echo 'title: world' > 2.txt
echo 'title: good' > 3.txt

And I have a JSON file called abc.json generated by a shell script like this:

{
  "": [
    {
      "title": "",
      "file": "1"
    },
    {
      "title": "",
      "file": "2"
    },
    {
      "title": "",
      "file": "3"
    }
  ]
}

What I want is to update the title value in the abc.json by the title in the respective text file, like this:

{
  "": [
    {
      "title": "hello",
      "file": "1"
    },
    {
      "title": "world",
      "file": "2"
    },
    {
      "title": "good",
      "file": "3"
    }
  ]
}

The text files and the JSON files are in the same directory like this:

➜  tmp.uFtH6hMC ls
1.txt    2.txt    3.txt    abc.json

Thank you very much!

CodePudding user response:

You can use a shell loop to iterate over your files, extract the second column, create each array element and then transform the stream of array elements into your final object:

for f in *.txt; do
  cut -d' ' -f2- "$f" | jq -R --arg file "$f" '{title:.,file:($file/"."|first)}';
done | jq -s '{"":.}'

It is also possible to remove the file extension in shell directly, which makes the jq filter a little bit simpler:

for f in *.txt; do
  cut -d' ' -f2 "$f" | jq -R --arg file "${f%.txt}" '{title:.,$file}';
done | jq -s '{"":.}'

CodePudding user response:

Since the .title and .files has the same number, we can use that to index it from the input.

So using cut we can read all the *.txt files, split on and then get the second to last field, this gives:

cat *.txt | cut -d ' ' -f 1-
hello
world
good

(titles with spaces will work due to the -f 1-)


Using --arg we pass that to jq, which we then parse into an array:

($inputs | split("\n")) as $parsed

Now that $parsed looks like:

[
  "hello",
  "world",
  "good"
]

To update the value, loop over each object in the "" array, then get the matching value from $parsed by using .file | tonumber - 1 (since array are 0-indexed)

jq --arg inputs "$(cat *.txt | cut -d ' ' -f 1-)" \
    '($inputs | split("\n")) as $parsed
        | .""[]
        |= (.title = $parsed[.file | tonumber - 1])' \
abc.json

Output:

{
  "": [
    {
      "title": "hello",
      "file": "1"
    },
    {
      "title": "world",
      "file": "2"
    },
    {
      "title": "good",
      "file": "3"
    }
  ]
}

CodePudding user response:

Use input_filename to access the filename, and you can accomplish everything in one go:

jq -Rn --argfile base abc.json '
  reduce (inputs | [ltrimstr("title: "), (input_filename | rtrimstr(".txt"))])
  as [$title, $file] ($base; (.[""][] | select(.file == $file)).title = $title)
' *.txt
{
  "": [
    {
      "title": "hello",
      "file": "1"
    },
    {
      "title": "world",
      "file": "2"
    },
    {
      "title": "good",
      "file": "3"
    }
  ]
}
  • Related