Jq 直接替换文件中的文本(如 sed-i)

我有一个 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)。

84097 次浏览

您需要在不更改上下文的情况下更新操作对象。通过在那里使用管道,您可以将上下文更改为每个单独的操作。你可以用括号控制它。

$ 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 那样对文件进行就地更新。

这篇文章解决了关于 sed 的“-i”选项不存在的问题,特别是描述的情况:

我有一大堆文件,要把每个文件写到一个单独的文件中并不容易。

至少如果您在 Mac 或 Linux 或类似的环境中工作,有几种选择。他们的利弊在 http://backreference.org/2011/01/29/in-place-editing-of-files/ 所以我只关注三个技巧:

一种是简单地使用“ & &”,大意如下:

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

用我对重复问题的回答

赋值将打印执行赋值的整个对象,这样您就可以为修改后的 Actions 数组的 .Actions赋一个新值

.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"]

上面的代码将输出整个 json,并编辑 .Actions。 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

instead of sponge :

cat <<< $(jq 'QUERY' sample.json) > sample.json

你遇到了两个问题:

  • This is a common problem for text processing, not solved in the base Linux distribution.
  • Jq 没有编写特殊的代码来克服这个问题。

一个不错的解决办法:

  • 使用 brew install moreutils或您最喜欢的软件包管理器安装 Moreutils
  • 使用 cat myfile | jq blahblahblah | sponge myfile。也就是说,运行 JQ,捕获标准输出,当 JQ完成时,然后通过 myfile(输入文件)写入标准输出。

可以这样做:

echo "$(jq '. + {"registry-mirrors": ["https://docker-mirror"]}' /etc/docker/daemon.json)" > /etc/docker/daemon.json

因此,它使用 jq 在 subshell 中获取文本,并将其回显到‘ main’shell 中的文件。

注意: 这里的主要思想是说明如何在没有诸如 sponge之类的额外工具的情况下实现它。你可以使用任何可以写入标准输出的命令来代替 echo,例如 printf '%s' "$(jq ... file)" > file

P.S Issue in jq project is still open: https://github.com/stedolan/jq/issues/105

使用 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"
}
}

这个 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"}'


}

sedperl非常相似,指定 -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" )"
}

我用 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