jq结合shell遍历JSON键时如何在循环内获取嵌套属性值
问题背景
你有一份存储EC2实例元数据的JSON文件instances_metadata.json,结构如下:
{ "instance-01": { "instance_aws_az": "eu-west-1a", "instance_aws_dns_name": "<ip>eu-west-1.compute.amazonaws.com", "instance_ec2_tags": { "Name": "2ed97a79c27be6bca883", "Serial_number": "22000528" }, "instance_sg_groups": [] }, "instance-29": { "instance_aws_az": "eu-west-1a", "instance_aws_dns_name": "<ip>eu-west-1.compute.amazonaws.com", "instance_ec2_tags": { "Name": "2ed97a79c27be6bca773", "Serial_number": "22000578" }, "instance_sg_groups": [] } }
需求为遍历文件中所有顶层实例ID(如instance-01、instance-29),在shell循环中读取每个实例对应的嵌套属性(比如标签里的Serial_number),针对指定实例执行aws s3 cp等自定义操作。原有脚本存在逻辑错误,无法正确读取对应实例的字段值。
原脚本问题说明
- Shell不会自动解析JSON路径,直接写
$i.instance_ec2_tags.Serial_number只是普通字符串拼接,根本无法获取JSON内的字段值 - 循环内的jq查询逻辑完全写反:
.[].instance_ec2_tags.Serial_number | select(. == "instance-01")是筛选序列号等于instance-01的条目,而非查询键名为instance-01的实例对应的序列号 - 逐次读取键再反复调用jq扫描整个文件,会产生不必要的IO开销,执行效率低
- 直接在jq表达式中拼接shell变量存在特殊字符转义、代码注入的风险
注:你最初提供的示例JSON存在不符合规范的尾逗号(对象最后一个属性后多余的逗号),如果执行jq时报解析错误,先删掉属性行末多余的逗号即可。
推荐实现方案
优先采用单次jq调用输出所有所需字段、再由shell循环读取的方案,执行效率最高,逻辑也最清晰:
#!/bin/bash # 一次性提取所有实例的需要字段,用制表符分隔避免字段含空格导致解析错误 jq -r 'to_entries[] | "\(.key)\t\(.value.instance_ec2_tags.Serial_number)\t\(.value.instance_aws_az)\t\(.value.instance_aws_dns_name)"' instances_metadata.json | while IFS=$'\t' read -r instance_id serial az dns_name; do echo "Instance $instance_id: Serial Number is: $serial" echo " Availability Zone: $az" echo " DNS Name: $dns_name" # 针对指定实例执行自定义逻辑 if [ "$instance_id" = "instance-01" ]; then echo "Start processing target instance $instance_id, serial number: $serial" # 在此处补充aws s3 cp等操作,直接调用$serial等变量即可 # 例:aws s3 cp ./config.yaml s3://your-bucket/instance-config/$serial/config.yaml fi done
方案原理
- 借助jq的
to_entries函数,将原始顶层对象转换为[{"key":"实例ID", "value":{实例属性对象}}]的结构化数组,遍历过程中可以同时拿到顶层键名和对应的所有嵌套属性 - 所有字段一次性提取完成,全程只调用一次jq,不需要循环内重复扫描文件
- 用制表符作为字段分隔符,避免字段值包含空格时出现read拆分错误
如果需要保留先遍历键名、再按需查询字段的逻辑,可以修正jq传参方式实现:
jq -r 'keys[]' instances_metadata.json | while read -r instance_id; do # 通过--arg参数将shell变量安全传入jq,查询当前实例ID对应的序列号 serial_number=$(jq -r --arg inst "$instance_id" '.[$inst].instance_ec2_tags.Serial_number' instances_metadata.json) echo "Instance $instance_id: Serial Number is: $serial_number" if [ "$instance_id" = "instance-01" ]; then echo "Processing target instance, serial: $serial_number" # 补充自定义操作 fi done
注意:不要直接在jq的过滤表达式里拼接shell变量,必须使用
--arg参数传值,避免特殊字符导致的语法错误和安全问题。
内容的提问来源于stack exchange,提问作者VCM
相关产品推荐
相关产品推荐

