我有一个 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"}'
}
与sed
或perl
非常相似,指定-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