【问题标题】:Changing values in a JSON data file from shell从 shell 更改 JSON 数据文件中的值
【发布时间】:2014-11-28 13:36:47
【问题描述】:

我创建了一个 JSON 文件,在这种情况下包含:

{"ipaddr":"10.1.1.2","hostname":"host2","role":"http","status":"active"},
{"ipaddr":"10.1.1.3","hostname":"host3","role":"sql","status":"active"},
{"ipaddr":"10.1.1.4","hostname":"host4","role":"quad","status":"active"},

另一方面,我有一个变量,例如:

arr="10.1.1.2 10.1.1.3"

例如来自对服务器状态的后续检查。对于这些值,我想将状态字段更改为"inactive"。换句话说就是grep主机并改变它的"status"值。

预期输出:

{"ipaddr":"10.1.1.2","hostname":"host2","role":"http","status":"inactive"},
{"ipaddr":"10.1.1.3","hostname":"host3","role":"sql","status":"inactive"},
{"ipaddr":"10.1.1.4","hostname":"host4","role":"quad","status":"active"},

【问题讨论】:

  • 考虑使用jq解析JSON
  • 你的代码是什么语言的?你的代码在哪里?你遇到了什么问题?
  • 我正在使用 awk sed 和 bash 创建 json 文件
  • 请注意,根据jsonlint.com,这不是有效的 JSON 格式。这意味着 Perl 和 Python 中的标准模块无法解析它。
  • 它是一个实际的应用程序还是只是一个快速解析/查找/替换?如果是后者,那么带有jq ...perl -MJSON -E '...;' 的oneliner 可能是最简单的。如果是前者,那么有关您要完成的工作的更多详细信息将有助于提供帮助。您是否正在从网页或 JSON 推送服务读取数据以用于其他应用程序?或者您正在构建一个应用程序来显示结果以及维护 JSON 数据中的数据?

标签: json bash perl shell awk


【解决方案1】:
$ arr="10.1.1.2 10.1.1.3"
$ awk -v arr="$arr" -F, 'BEGIN { gsub(/\./,"\\.",arr); gsub(/ /,"|",arr) }
    $1 ~ "\"(" arr ")\"" { sub(/active/,"in&") } 1' file
{"ipaddr":"10.1.1.2","hostname":"host2","role":"http","status":"inactive"},
{"ipaddr":"10.1.1.3","hostname":"host3","role":"sql","status":"inactive"},
{"ipaddr":"10.1.1.4","hostname":"host4","role":"quad","status":"active"},

【讨论】:

  • 对所有答案投反对票的任何特殊原因?
【解决方案2】:

这是一个快速的perl“环绕单行”:它使用了JSON 模块和slurps with the -0 switch

perl -MJSON -n0E '$j = decode_json($_); 
   for (@{$j->{hosts}}){$_->{status}=inactive if $_->{ipaddr}=~/2|3/} ; 
   say to_json( $j->{hosts}, {pretty=>1} )' status_data.json

可能会更好或可能违反 PBP 对map 的建议:

perl -MJSON -n0E '$j = decode_json($_); 
   map { $_->{status}=inactive if $_->{ipaddr}=~/2|3/ } @{ $j->{hosts} }  ;
   say to_json( $j->{hosts} )' status_data.json

使用jq 重置状态的shell 脚本也是可能的。这是使用jq 解析和输出JSON 更改的快速方法:

cat status_data.json| jq -r '.hosts |.[] |
select(.ipaddr == "10.1.1.2"//.ipaddr == "10.1.1.3" )' |jq '.status = "inactive"'

编辑在较早的评论中,我不确定 OP 是否对应用程序更感兴趣,而不是快速搜索和替换(关于短语“另一方面... em>”和“检查服务器状态”)。这是脚本形式的(仍然很简单)perl 方法:

use v5.16; #strict, warnings, say
use JSON ;
use IO::All;

my $status_data < io 'status_data.json';
my $network = JSON->new->utf8->decode($status_data) ;
my @changed_hosts= qw/10.1.1.2 10.1.1.3/;

sub status_report {
  foreach my $host ( @{ $network->{hosts} }) {
     say "$host->{hostname} is $host->{status}";
  }
}

sub change_status {
  foreach my $host ( @{ $network->{hosts} }){
    foreach (@changed_hosts) {
      $host->{status} = "inactive" if $host->{ipaddr} eq $_ ;
    }
  }
  status_report;
}

defined $ENV{CHANGE_HAPPENED} ? change_status : status_report ;

脚本读取 JSON 文件 status_data.json(使用 IO::All,这很有趣)然后使用 JSON 将其解码为哈希。很难判断这是否是一个完整的解决方案,因为如果您正在“监控”主机状态,那么我们应该定期检查 JSON 数据文件并将其与我们的哈希值进行比较,然后在发生更改时运行脚本的主体.

要模拟发生的变化,您可以在您的环境中使用export CHANGE_HAPPENED=1(或setenv,如果在tcsh 中)和unset CHANGE_HAPPENED 定义/取消定义unset CHANGE_HAPPENED,然后脚本将更新消息和哈希或“报告”。为了完成这一点,我们的哈希中的数据应该定期更新以匹配数据文件,或者在事件发生时。可以更改status_report() 子例程,以便当update_status() 告诉它这样做时,它会构建@inactive_hosts@active_hosts 的数组:if ( something_happened() ) { update_status() }等。

希望对您有所帮助。

status_data.json

{
  "hosts":[
        {"ipaddr":"10.1.1.2","hostname":"host2","role":"http","status":"active"},
        {"ipaddr":"10.1.1.3","hostname":"host3","role":"sql","status":"active"},
        {"ipaddr":"10.1.1.4","hostname":"host4","role":"quad","status":"active"}
  ]
}

输出:

~/ % perl network_status_json.pl
host2 is active
host3 is active
host4 is active
~/ % export CHANGE_HAPPENED=1   
~/ % perl network_status_json.pl
host2 is inactive
host3 is inactive
host4 is active

【讨论】:

  • 这是一个很好的解决方案!我不知道IO::All.. 似乎是一个很棒的模块:)
【解决方案3】:

版本 1: 使用简单的基于正则表达式的转换。这可以通过多种方式完成。从最初的问题来看,ipaddr 的列表在arr 的变量中。使用 Bash 环境变量的示例:

$ export var="... ..."

通过命令行参数提供此信息是一种可能的解决方案。

#!/usr/bin/perl
my %inact;                    # ipaddr to inactivate
my $arr=$ENV{arr} ;           # from external var (export arr=...)
## $arr=shift;                # from command line arg

for( split(/\s+/, $arr)){ $inact{$_}=1 }

while(<>){                    # one "json" line at the time
   if(/"ipaddr":"(.*?)"/ and $inact{$1}){
       s/"active"/"inactive"/}
   print $_;
}

第 2 版

使用 Json 解析器我们可以进行更复杂的转换;由于输入不是真正的 JSON,我们将处理一行“几乎是 json”:

use JSON;

use strict;
my ($line, %inact);
my $arr=$ENV{arr} ; 

for( split(/\s+/, $arr)){ $inact{$_}=1 }

while(<>){              # one "json" line at the time
   if(/^\{.*\},/){
      s/,\n//;
      $line = from_json( $_);
      if($inact{$line->{ipaddr}}){
         $line->{status} = "inactive" ;}
      print to_json($line), ",\n"; }
   else { print $_;}
}

【讨论】:

  • 第 2 版,如果输入是完整的 JSON 兼容文件,则可以简化。
  • 您能告诉我我的解决方案有什么问题吗?它会产生错误的结果吗?
  • 您没有做错任何事 - 似乎有人不喜欢这里的任何答案。但别担心——随着时间的推移,他们要么会被投票赞成。
【解决方案4】:
#!/bin/ksh

# your "array" of IP
arr="10.1.1.2 10.1.1.3"


# create and prepare temporary file for sed action
SedAction=/tmp/Action.sed

# --- for/do generating SedAction --------
echo "#sed action" > ${SedAction}

#take each IP from the arr variable one by one
for IP in ${arr}
 do
   # prepare for a psearch pattern use
   IP_RE="$( echo "${IP}" | sed 's/\./\\./g' )"

   # generate sed action in temporary file.
   # final action will be like: 
   #   s/\("ipaddr":"10\.1\.1\.2".*\)"active"}/\1"inactive"}/;t
   # escape(double) \ for in_file espace, escape(simple) " for this line interpretation
   echo "s/\\\(\"ipaddr\":\"${IP_RE}\".*\\\)\"active\"}/\\\1\"inactive\"}/;t" >> ${SedAction}
 done

# --- sed generating sed action ---------------
echo "${arr}" \
 | tr " " "\n" \
 | sed 's/\./\\./g
        s#.*#s/\\("ipaddr":"&".*\\)"active"}/\\1"inactive"}/;t#
        ' \
 > ${SedAction}


# core of the process (use -i for inline editing or "double" redirection for non GNU sed)
sed -f ${SedAction} YourFile

# clean temporary file
rm ${SedAction}

自我注释,在 ksh/AIX 中测试。 2 种生成 SedAction 的方法,具体取决于您还想执行的操作(如果有)。你只需要一个工作,我更喜欢第二个

【讨论】:

    【解决方案5】:

    这在 Perl 中确实非常简单,使用 JSON 模块。

    use strict;
    use warnings;
    
    use JSON qw/ from_json to_json /;
    
    my $json = JSON->new;
    
    my $data = from_json(do { local $/; <DATA> });
    
    my $arr = "10.1.1.2 10.1.1.3";
    my %arr = map { $_ => 1 } split ' ', $arr;
    
    for my $item (@$data) {
      $item->{status} = 'inactive' if $arr{$item->{ipaddr}};
    }
    
    print to_json($data, { pretty => 1 }), "\n";
    
    __DATA__
    [
    {"ipaddr":"10.1.1.2","hostname":"host2","role":"http","status":"active"},
    {"ipaddr":"10.1.1.3","hostname":"host3","role":"sql","status":"active"},
    {"ipaddr":"10.1.1.4","hostname":"host4","role":"quad","status":"active"}
    ]
    

    输出

    [
       {
          "role" : "http",
          "hostname" : "host2",
          "status" : "inactive",
          "ipaddr" : "10.1.1.2"
       },
       {
          "hostname" : "host3",
          "role" : "sql",
          "ipaddr" : "10.1.1.3",
          "status" : "inactive"
       },
       {
          "ipaddr" : "10.1.1.4",
          "status" : "active",
          "hostname" : "host4",
          "role" : "quad"
       }
    ]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-08
      • 1970-01-01
      相关资源
      最近更新 更多