我有一个需要在特定条件下更新的 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
cat
真的可以代替 sponge
吗?这可以保证始终有效吗?
你遇到了两个问题:
这是文本处理的常见问题,在基本 Linux 发行版中没有解决。
jq 没有编写特殊的代码来克服这个问题。
一个很好的解决方案:
使用 brew install moreutils 或您最喜欢的包管理器安装 moreutils。这包含方便的程序海绵,仅用于此目的。
使用 cat myfile | jq blahblahblah |海绵我的文件。也就是说,运行 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 那样对文件进行就地更新。
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
使用 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"
}
}
可以执行以下操作:
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
?
我使用 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
这个 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" )"
}
json="$( jq ... file.json )"
加printf '%s\n' "$json" >file.json