如何使用 bash 脚本和 jq 编辑此 JSON 文件

如何使用 bash 脚本和 jq 编辑此 JSON 文件

我的 config.json 文件包含以下内容:

[{
  "host0": "11.11.11.11",
  "host1": "22.22.22.22",
  "host2": "33.33.33.33",
  "host3": "44.44.44.44",
  "host4": "55.55.55.55",
  "host5": "66.66.66.66"
}]

我正在尝试创建一个可以从命令行启动的 .sh bash 脚本,并编辑该 config.json 文件以完全删除整个 host0 行及其值。然后我需要剩余的值“向上移动”——我的意思是重命名host1为host0,host2为host1,host3为host2,host4为host3,等等,但它们的值应该保持不变。最终结果应该是这样的:

[{
  "host0": "22.22.22.22",
  "host1": "33.33.33.33",
  "host2": "44.44.44.44",
  "host3": "55.55.55.55",
  "host4": "66.66.66.66"
}]

如何使用 bash 脚本和 jq 或任何其他可从命令行或/和 cron 启动的 bash 脚本的方法来完成此操作?在 .sh 脚本中进行硬编码,例如jq '.host0 = "22.22.22.22" config.json不是一个选择;我需要脚本来读取 config.json 并从 host1 复制值并将其粘贴到 host0 等。

答案1

以下jq表达式将转换应用于顶级数组的每个元素(问题中该数组中只有一个元素)。该转换涉及通过将最近添加的键+值的值替换为添加到末尾的元素的值来根据给定的键和值构建新数组。这将创建一个长度数组n+1(其中n是原始数组元素中键和值的数量),其中第一个值是11.11.11.11问题中应丢弃的值,最后一个键是应丢弃的键,host5在问题中。使用.[1:-1](也可以写成del(first,last)),我们丢弃第一个和最后一个元素,留下n-1键和值的列表。

jq 'map(to_entries | reduce .[] as $a ([{}]; last.value = $a.value | . += [$a]) | .[1:-1] | from_entries)' file

这是jq表达式本身,采用更易于阅读的格式:

map(
  to_entries |
  reduce .[] as $a (
    [{}];
    last.value = $a.value |
    . += [$a]
  ) |
  del(first, last) |
  from_entries
)

鉴于 中的以下 JSON 文档file

[
  {
    "host0": "11.11.11.11",
    "host1": "22.22.22.22",
    "host2": "33.33.33.33",
    "host3": "44.44.44.44",
    "host4": "55.55.55.55",
    "host5": "66.66.66.66"
  },
  {
    "server0": "Atlantis",
    "server1": "Gotham",
    "server2": "Rivendell",
    "server3": "Asgard",
    "server4": "Hogwarts",
    "server5": "Neverland"
  }
]

...该jq命令将产生以下结果:

[
  {
    "host0": "22.22.22.22",
    "host1": "33.33.33.33",
    "host2": "44.44.44.44",
    "host3": "55.55.55.55",
    "host4": "66.66.66.66"
  },
  {
    "server0": "Gotham",
    "server1": "Rivendell",
    "server2": "Asgard",
    "server3": "Hogwarts",
    "server4": "Neverland"
  }
]

请注意,没有对键或值字符串的解释。

答案2

使用jq和编辑该文件的脚本:

#!/bin/sh
set -eu

jq '[.[] | to_entries[1:] | map(.key |= sub("(?<n>\\d+)$";"\((.n|tonumber)-1)")) | from_entries]' "$1" > "$1.tmp"
mv "$1.tmp" "$1"

用法:

$ cat config.json
[{
  "host0": "11.11.11.11",
  "host1": "22.22.22.22",
  "host2": "33.33.33.33",
  "host3": "44.44.44.44",
  "host4": "55.55.55.55",
  "host5": "66.66.66.66"
}]

$ ./script.sh config.json

$ cat config.json
[
  {
    "host0": "22.22.22.22",
    "host1": "33.33.33.33",
    "host2": "44.44.44.44",
    "host3": "55.55.55.55",
    "host4": "66.66.66.66"
  }
]

格式略有变化,但这应该不是问题。

如果你想保留格式,或者想使用标准的 unix 工具,也可以使用awknot jq,但鲁棒性较差:

awk -F\" 'BEGIN{OFS=FS} $2=="host0"{next} $2~/^host/{$2="host" substr($2,5)-1} 1' "$1" > "$1.tmp"

答案3

如果被允许:

除此之外的想法是,已经有一种专门的语言可以解析作为内置又名
JavaScript对象表示法=)

#!/bin/sh

node -e '
    function renameKey (obj, oldKey, newKey) {
      obj[newKey] = obj[oldKey];
      delete obj[oldKey];
    }
    var j = require("./file.json"); // mandatory ./
    delete j[0]["host0"];
    let count = Object.keys(j[0]).length;
    for (let i = 0; i < count; i++) {
        let oldv = "host"+(i+1);
        let newv = "host"+i;
        renameKey(j[0], oldv, newv)
    }
    console.log(JSON.stringify(j, null, 4))
'

或者

#!/bin/sh

node -p '
    JSON.stringify(
        require("./file.json")
        .map(x => {
            let ok;
            for (const [k, v] of Object.entries(x)) {
                if (ok)
                    x[ok] = v;
                ok = k;
            }
            delete x[ok];
            return x;
        })
        , null
        , 4
    )
'

emanuele6 的学分来自[电子邮件受保护]

输出

[
  {
    "host0": "22.22.22.22",
    "host1": "33.33.33.33",
    "host2": "44.44.44.44",
    "host3": "55.55.55.55",
    "host4": "66.66.66.66"
  }
]

另一种方式,使用rhino

$ dpkg -s rhino
Installed-Size: 55
[...]
Description: JavaScript engine written in Java
 Rhino is an implementation of the JavaScript language written
 entirely in Java. It is typically embedded into Java applications to
 provide scripting to end users.
Original-Maintainer: Debian Java Maintainers <[email protected]>
Homepage: http://www.mozilla.org/rhino/

(hacky方式,不要在生产中使用)

rhino -e "
function renameKey (obj, oldKey, newKey) {
  obj[newKey] = obj[oldKey];
  delete obj[oldKey];
}
j = $(< file.json); // shell hack to read file without import fs lib
delete j[0]['host0'];
let count = Object.keys(j[0]).length;
for (let i = 0; i < count; i++) {
    let oldv = 'host'+(i+1);
    let newv = 'host'+i;
    renameKey(j[0], oldv, newv)
}
print(JSON.stringify(j, null, 4))
"

答案4

使用(以前称为 Perl_6)

~$ raku -e 'my @json = lines[1..*-2]>>.split(/ <[:,]> /, :skip-empty); put qb[\[\{];  \
            @json = join ",\n", ([Z] @json[0..*]>>.[0], @json[1..*]>>.[1])>>.join(":");  \
            .put for @json; put qb[\}\]];'   file 

我犹豫是否要写这个答案,但在我看来,OP 可能会遇到排序错误。很高兴jq已经发布的答案似乎保持了输入顺序,但至少一篇 StackOverflow 帖子说这不能保证。从RFC_8259:

> An object is an unordered collection of zero or more name/value   
> pairs, where a name is a string and a value is a string, number,   
> boolean, null, object, or array.

上面,Raku 代码对文本采用字面方法,读lines入数组,同时[1..*-2]删除第一个和最后一个括号行(如果您的 JSON 格式化程序将括号/大括号放在两行而不是一行上,则调整此索引)。每行都按<[:,]>由冒号和逗号组成的自定义字符类进行拆分,并:skip-empty删除空元素。以这种方式,每个@数组位置包含两个元素:主机和IP。

方括号/大括号又put回到单独的语句中。该@json数组被[Z]“压缩”在一起,删除第一个“IP”元素(通过从而不是@json[1..*]>>.[1]从 开始。当任何项目用完元素时,压缩就会停止。最后,冒号、逗号和换行符会被重新插入。1..0..join

输入示例:

[{
  "host0": "11.11.11.11",
  "host1": "22.22.22.22",
  "host2": "33.33.33.33",
  "host3": "44.44.44.44",
  "host4": "55.55.55.55",
  "host5": "66.66.66.66"
}]

示例输出:

[{
  "host0": "22.22.22.22",
  "host1": "33.33.33.33",
  "host2": "44.44.44.44",
  "host3": "55.55.55.55",
  "host4": "66.66.66.66"
}]

https://raku.org

相关内容