使用Bash脚本更改JSON文件

100

我可以帮忙翻译以下问题: 我有一个长得像这样的JSON文件:

{
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"
}

如何通过Bash脚本添加和删除新键(即"key4": "value4")?我还注意到在添加或删除新键之前,需要处理文件中最后一个键的结尾逗号问题。

谢谢


1
它必须是bash或者可以使用Node.js吗?这似乎是一个很好的使用node解决的问题。 - Patrick Gunderson
6个回答

163

你最好的选择是使用像jq这样的JSON CLI:

  • 在基于Debian的系统(如Ubuntu)上,您可以通过以下方式安装它:
    sudo apt-get install jq
  • 在macOS上,如果已安装Homebrew(http://brew.sh/),请使用以下命令:
    brew install jq

示例,基于以下输入字符串 - 输出为stdout

jsonStr='{ "key1": "value1", "key2": "value2", "key3": "value3" }'
移除 "key3":
jq 'del(.key3)' <<<"$jsonStr"
添加属性"key4",值为"value4":
jq '. + { "key4": "value4" }' <<<"$jsonStr"
将现有属性“key1”的值更改为“new-value1”:
jq '.key1 = "new-value1"' <<<"$jsonStr"

更为强大的替代方案感谢Lars Kiesow :
如果您使用--arg传递新值,jq会负责正确转义该值:

jq '.key1 = $newVal' --arg newVal '3 " of rain' <<<"$jsonStr"

如果你想要在原地更新一个JSON文件(就概念而言),可以使用删除“key3”的例子:
# Create test file.
echo '{ "key1": "value1", "key2": "value2", "key3": "value3" }' > test.json

# Remove "key3" and write results back to test.json (recreate it with result).
jq -c 'del(.key3)' test.json > tmp.$$.json && mv tmp.$$.json test.json

你不能直接替换输入文件,因此结果会被写入一个临时文件,成功后替换输入文件。

请注意-c选项,它会生成紧凑的JSON而不是漂亮的格式化JSON。

有关所有选项和命令,请参阅https://jqlang.github.io/jq/manual/中的手册


我从一个脚本中接收到了一个输出,它实际上是紫色的。当我在文件中替换它时,它被设置为在\u001b[0;1;34m\u001b[0;m之间。请问jq能够忽略字符串的颜色吗? - alper
1
谢谢@mklement0,我通过使用以下答案(https://dev59.com/BmUq5IYBdhLWcg3wNtyB#14693789)解决了问题,以删除ANSI字符。 - alper
1
你可以使用sponge(来自moreutils包)直接编辑文件jq -c 'del(.key3)' test.json | sponge.exe test.json它也是具有破坏性的,所以请小心。 - poboxy

55

对于每个人来说都不是最佳答案,但如果你已经在系统中安装了NodeJs,你可以使用它来轻松地操作JSON。

例如:

#!/usr/bin/env bash
jsonFile=$1;

node > out_${jsonFile} <<EOF
//Read data
var data = require('./${jsonFile}');

//Manipulate data
delete data.key3
data.key4 = 'new value!';

//Output data
console.log(JSON.stringify(data));

EOF

如果您只需要进行JSON操作,并且您有Node(即:您不需要任何其他Bash功能),则可以直接编写使用Node作为解释器的脚本:

#! /usr/bin/env node
var data = require('./'+ process.argv[2]);
/*manipulate*/
console.log(JSON.stringify(data));

console.log() 应该将数据写入文件吗,还是我需要使用 fs? - Shawn Mclean
2
将输出打印到标准输出(stdout)。然后可以将其重定向到文件中。 例如:./myScript.js myInput.json > outfile.txt - Lenny Markus
任何带有JSON库的编程语言都可以做到完全相同的事情。使用bash的主要优点是您不依赖于尚未安装在系统上的软件。如果这不是必需的,则没有理由建议使用Node而不是其他任何语言,仅仅因为您可以从bash脚本中内联运行它,并不意味着它是一个bash答案。 - user5359531
17
我在回答开头就写了“并非对所有人都适用的答案”,正是因为这个原因。有许多方法可以解决问题,在我发布这个答案三年的时间里,NodeJS一直在不断增长,所以很多人已经安装了它。它提供本地JSON支持,因此不需要使用库也很好。 总之,我分享了一些知识,帮助了16个人。这让我很高兴 :) - Lenny Markus
对于那些未必安装了相同工具的团队来说,这是一个绝佳的想法。 - OldBuildingAndLoan

21

在 Lenny 的答案的基础上,我们可以使用 node 的 -p 选项,该选项评估给定的脚本并将输出写入 stdout

使用展开运算符进行简单修改,如下:

node -p "JSON.stringify({...require('./data.json'), key4: 'value4'}, null, 2)" > data.json

这个对 Lenny 回答的跟进在我的情况下起了作用。我喜欢它只是一个一行代码,不需要关心脚本顶部的 shebang。然而,对于更复杂的操作,保持更接近 Lenny 的多行版本可能更合适。 - Zaphod Beeblebrox

8
要在原地更改文件,请使用 sponge 命令。
echo '{ "k": "old value" }' >f.json

cat f.json | jq '.k = $v' --arg v 'new value' | sponge f.json

参见: jq问题 在原地编辑文件#105

jq的替代方案: jaq

echo '{ "k": "old value" }' >f.json

jaq -i '.k = $v' --arg v 'new value' f.json

...但是jaqjq的功能更少。


3
对于像我一样被sponge这个词所迷惑的人:https://joeyh.name/code/moreutils/。 - Federico klez Culloca
2
安装 Sponge 需要执行以下命令:sudo apt install moreutils - Achintha Isuru

3
这里是一个纯 bash 的示例,包括“逗号问题”。
#!/bin/bash
# This bash script just uses the sed command to 
#   replace/insert a new key at/before/after an 
#   existing key in a json file 
# The comma issue:
# - replace: with/without, as previous entry
# - before: always add
# - after: add before, if there was none
SED_CMD="/tmp/sed_cmd.tmp"
JSFILE1="./data1.json"
JSFILE2="./data2.json"
JSFILE3="./data3.json"
SEARCH_KEY="key3"
# create json input file
echo -e '{\n\t"key1": "value1",\n\t"key2": "value2",\n\t"key3": "value3"\n}' > $JSFILE1
echo -e "input:"
cat $JSFILE1
# duplicate twice
cp $JSFILE1 $JSFILE2 && cp $JSFILE1 $JSFILE3
# find the SEARCH_KEY and store the complete line to SEARCH_LINE 
SEARCH_LINE=`cat data.json | grep $SEARCH_KEY`
echo "SEARCH_LINE=>$SEARCH_LINE<"
# replace SEARCH_LINE
IS_COMMA=`echo $SEARCH_LINE | grep ","`
[ -z "$IS_COMMA" ] && \
    echo "s+$SEARCH_LINE+\t\"keyNew\": \"New\"+g" > $SED_CMD || \
    echo "s+$SEARCH_LINE+\t\"keyNew\": \"New\",+g" > $SED_CMD
sed -i -f $SED_CMD $JSFILE1
echo -e "replace:"
cat $JSFILE1
# insert before SEARCH_LINE
echo "s+$SEARCH_LINE+\t\"keyNew\": \"New\",\n$SEARCH_LINE+g" > $SED_CMD
sed -i -f $SED_CMD $JSFILE2
echo -e "before:"
cat $JSFILE2
# insert after SEARCH_LINE
IS_COMMA=`echo $SEARCH_LINE | grep ","`
[ -z "$IS_COMMA" ] && \
    echo "s+$SEARCH_LINE+$SEARCH_LINE,\n\t\"keyNew\": \"New\"+g" > $SED_CMD || \
    echo "s+$SEARCH_LINE+$SEARCH_LINE\n\t\"keyNew\": \"New\",+g" > $SED_CMD
sed -i -f $SED_CMD $JSFILE3
echo -e "after:"
cat $JSFILE3
exit 0

看起来运行得相当不错。只是作为一个提示,这个例子脚本期望有一个 data.json 文件,并生成 data1.json、data2.json 和 data3.json(每个用例一个)。 - Saturas

-1
如何通过Bash脚本添加和删除新的键(即"key4": "value4")?
使用专用的JSON工具,例如,比使用纯Bash函数更好。

添加新的属性-值对

xidel -s '{"a":1,"b":2,"c":3}' -e '($json).d:=4'                 # dot notation
xidel -s '{"a":1,"b":2,"c":3}' -e '{|$json,{"d":4}|}'            # JSONiq (deprecated)
xidel -s '{"a":1,"b":2,"c":3}' -e 'map:put($json,"d",4)'         # XQuery
xidel -s '{"a":1,"b":2,"c":3}' -e 'map:merge(($json,{"d":4}))'   # XQuery
{
  "a": 1,
  "b": 2,
  "c": 3,
  "d": 4
}

移除属性值对"c":3

xidel -s '{"a":1,"b":2,"c":3}' --xmlns:jnlib="http://jsoniq.org/function-library" -e 'jnlib:remove-keys($json,"c")'   # JSONiq (deprecated)
xidel -s '{"a":1,"b":2,"c":3}' -e 'map:remove($json,"c")'   # XQuery
{
  "a": 1,
  "b": 2
}

"c"属性的值更改为4

xidel -s '{"a":1,"b":2,"c":3}' -e '($json).c:=4'
xidel -s '{"a":1,"b":2,"c":3}' -e 'map:put($json,"c",4)'
xidel -s '{"a":1,"b":2,"c":3}' -e 'map:merge(($json,{"c":4}),{"duplicates":"use-last"})'
{
  "a": 1,
  "b": 2,
  "c": 4
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接