Acabo de comenzar con las secuencias de comandos Bash y tropecé con jq para trabajar con JSON.
Necesito transformar una cadena JSON como la siguiente en una tabla para la salida en la terminal.
[{
"name": "George",
"id": 12,
"email": "[email protected]"
}, {
"name": "Jack",
"id": 18,
"email": "[email protected]"
}, {
"name": "Joe",
"id": 19,
"email": "[email protected]"
}]
Lo que quiero mostrar en la terminal:
ID Name
=================
12 George
18 Jack
19 Joe
Observe cómo no quiero mostrar la propiedad de correo electrónico para cada fila, por lo que el comando jq debería implicar algún filtrado. Lo siguiente me da una lista simple de nombres e identificaciones:
list=$(echo "$data" | jq -r '.[] | .name, .id')
printf "$list"
El problema con eso es que no puedo mostrarlo como una tabla. Sé que jq tiene algunas opciones de formato, pero no tan buenas como las opciones que tengo al usar printf
. Creo que quiero obtener estos valores en una matriz que luego puedo recorrer yo mismo para hacer el formateo ... Las cosas que probé me dieron resultados variables, pero nunca lo que realmente quiero.
¿Alguien me puede apuntar en la dirección correcta?
jq -r ...
comando?echo
puede evitarsejq -r '...' <<<$data
ojr -r '...' < input-file.json
."name1 value1 name2 value2 name3 value3"
¿cómo puedo imprimirla como tabla?Respuestas:
¿Por qué no algo como:
echo '[{ "name": "George", "id": 12, "email": "[email protected]" }, { "name": "Jack", "id": 18, "email": "[email protected]" }, { "name": "Joe", "id": 19, "email": "[email protected]" }]' | jq -r '.[] | "\(.id)\t\(.name)"'
Salida
Edición 1: para un formato de grano fino, utilice herramientas como
awk
echo '[{ "name": "George", "id": 12, "email": "[email protected]" }, { "name": "Jack", "id": 18, "email": "[email protected]" }, { "name": "Joe", "id": 19, "email": "[email protected]" }]' | jq -r '.[] | [.id, .name] | @csv' | awk -v FS="," 'BEGIN{print "ID\tName";print "============"}{printf "%s\t%s%s",$1,$2,ORS}' ID Name ============ 12 "George" 18 "Jack" 19 "Joe"
Edición 2: en respuesta a
Por qué no?
Un ejemplo un poco complicado (de hecho modificado del suyo) donde el correo electrónico se cambia a una matriz demuestra esto
echo '[{ "name": "George", "id": 20, "email": [ "[email protected]" , "[email protected]" ] }, { "name": "Jack", "id": 18, "email": [ "[email protected]" , "[email protected]" ] }, { "name": "Joe", "id": 19, "email": [ "[email protected]" ] }]' | jq -r '.[] | .email'
Salida
[ "[email protected]", "[email protected]" ] [ "[email protected]", "[email protected]" ] [ "[email protected]" ]
fuente
awk
, pero tenga en cuenta que los casos complicados pueden fallar. Para su segundo comentario, vea la última edición y léalo junto con [esta] respuesta.Usar el
@tsv
filtro tiene mucho que recomendar, principalmente porque maneja numerosos "casos extremos" de manera estándar:Agregar los encabezados se puede hacer así:
jq -r '["ID","NAME"], ["--","------"], (.[] | [.id, .name]) | @tsv'
El resultado:
length*"-"
Para automatizar la producción de la línea de guiones:
jq -r '(["ID","NAME"] | (., map(length*"-"))), (.[] | [.id, .name]) | @tsv'
fuente
¡Definir encabezados a mano no es óptimo! Omitir encabezados también es subóptimo.
TL; DR
datos
[{ "name": "George", "id": 12, "email": "[email protected]" }, { "name": "Jack", "id": 18, "email": "[email protected]" }, { "name": "Joe", "id": 19, "email": "[email protected]" }]
guión
como correr
Encontré esta pregunta mientras resumía algunos datos de los servicios web de Amazon. El problema en el que estaba trabajando, en caso de que quieras otro ejemplo:
$ aws ec2 describe-spot-instance-requests | tee /tmp/ins | jq --raw-output ' # extract instances as a flat list. [.SpotInstanceRequests | .[] # remove unwanted data | { State, statusCode: .Status.Code, type: .LaunchSpecification.InstanceType, blockPrice: .ActualBlockHourlyPrice, created: .CreateTime, SpotInstanceRequestId} ] # lowercase keys # (for predictable sorting, optional) | [.[]| with_entries( .key |= ascii_downcase ) ] | (.[0] |keys_unsorted | @tsv) # print headers , (.[]|.|map(.) |@tsv) # print table ' | column -t
Salida:
state statuscode type blockprice created spotinstancerequestid closed instance-terminated-by-user t3.nano 0.002000 2019-02-24T15:21:36.000Z sir-r5bh7skq cancelled bad-parameters t3.nano 0.002000 2019-02-24T14:51:47.000Z sir-1k9s5h3m closed instance-terminated-by-user t3.nano 0.002000 2019-02-24T14:55:26.000Z sir-43x16b6n cancelled bad-parameters t3.nano 0.002000 2019-02-24T14:29:23.000Z sir-2jsh5brn active fulfilled t3.nano 0.002000 2019-02-24T15:37:26.000Z sir-z1e9591m cancelled bad-parameters t3.nano 0.002000 2019-02-24T14:33:42.000Z sir-n7c15y5p
Entrada:
$ cat /tmp/ins { "SpotInstanceRequests": [ { "Status": { "Message": "2019-02-24T15:29:38+0000 : 2019-02-24T15:29:38+0000 : Spot Instance terminated due to user-initiated termination.", "Code": "instance-terminated-by-user", "UpdateTime": "2019-02-24T15:31:03.000Z" }, "ActualBlockHourlyPrice": "0.002000", "ValidUntil": "2019-03-03T15:21:36.000Z", "InstanceInterruptionBehavior": "terminate", "Tags": [], "InstanceId": "i-0414083bef5e91d94", "BlockDurationMinutes": 60, "SpotInstanceRequestId": "sir-r5bh7skq", "State": "closed", "ProductDescription": "Linux/UNIX", "LaunchedAvailabilityZone": "eu-north-1a", "LaunchSpecification": { "Placement": { "Tenancy": "default", "AvailabilityZone": "eu-north-1a" }, "ImageId": "ami-6d27a913", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "VirtualName": "root", "NoDevice": "", "Ebs": { "Encrypted": false, "DeleteOnTermination": true, "VolumeType": "gp2", "VolumeSize": 8 } } ], "EbsOptimized": false, "SecurityGroups": [ { "GroupName": "default" } ], "Monitoring": { "Enabled": false }, "InstanceType": "t3.nano", "AddressingType": "public", "NetworkInterfaces": [ { "DeviceIndex": 0, "Description": "eth-zero", "NetworkInterfaceId": "", "DeleteOnTermination": true, "SubnetId": "subnet-420ffc2b", "AssociatePublicIpAddress": true } ] }, "Type": "one-time", "CreateTime": "2019-02-24T15:21:36.000Z", "SpotPrice": "0.008000" }, { "Status": { "Message": "Your Spot request failed due to bad parameters.", "Code": "bad-parameters", "UpdateTime": "2019-02-24T14:51:48.000Z" }, "ActualBlockHourlyPrice": "0.002000", "ValidUntil": "2019-03-03T14:51:47.000Z", "InstanceInterruptionBehavior": "terminate", "Tags": [], "Fault": { "Message": "Invalid device name /dev/sda", "Code": "InvalidBlockDeviceMapping" }, "BlockDurationMinutes": 60, "SpotInstanceRequestId": "sir-1k9s5h3m", "State": "cancelled", "ProductDescription": "Linux/UNIX", "LaunchedAvailabilityZone": "eu-north-1a", "LaunchSpecification": { "Placement": { "Tenancy": "default", "AvailabilityZone": "eu-north-1a" }, "ImageId": "ami-6d27a913", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda", "VirtualName": "root", "NoDevice": "", "Ebs": { "Encrypted": false, "DeleteOnTermination": true, "VolumeType": "gp2", "VolumeSize": 8 } } ], "EbsOptimized": false, "SecurityGroups": [ { "GroupName": "default" } ], "Monitoring": { "Enabled": false }, "InstanceType": "t3.nano", "AddressingType": "public", "NetworkInterfaces": [ { "DeviceIndex": 0, "Description": "eth-zero", "NetworkInterfaceId": "", "DeleteOnTermination": true, "SubnetId": "subnet-420ffc2b", "AssociatePublicIpAddress": true } ] }, "Type": "one-time", "CreateTime": "2019-02-24T14:51:47.000Z", "SpotPrice": "0.011600" }, { "Status": { "Message": "2019-02-24T15:02:17+0000 : 2019-02-24T15:02:17+0000 : Spot Instance terminated due to user-initiated termination.", "Code": "instance-terminated-by-user", "UpdateTime": "2019-02-24T15:03:34.000Z" }, "ActualBlockHourlyPrice": "0.002000", "ValidUntil": "2019-03-03T14:55:26.000Z", "InstanceInterruptionBehavior": "terminate", "Tags": [], "InstanceId": "i-010442ac3cc85ec08", "BlockDurationMinutes": 60, "SpotInstanceRequestId": "sir-43x16b6n", "State": "closed", "ProductDescription": "Linux/UNIX", "LaunchedAvailabilityZone": "eu-north-1a", "LaunchSpecification": { "Placement": { "Tenancy": "default", "AvailabilityZone": "eu-north-1a" }, "ImageId": "ami-6d27a913", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "VirtualName": "root", "NoDevice": "", "Ebs": { "Encrypted": false, "DeleteOnTermination": true, "VolumeType": "gp2", "VolumeSize": 8 } } ], "EbsOptimized": false, "SecurityGroups": [ { "GroupName": "default" } ], "Monitoring": { "Enabled": false }, "InstanceType": "t3.nano", "AddressingType": "public", "NetworkInterfaces": [ { "DeviceIndex": 0, "Description": "eth-zero", "NetworkInterfaceId": "", "DeleteOnTermination": true, "SubnetId": "subnet-420ffc2b", "AssociatePublicIpAddress": true } ] }, "Type": "one-time", "CreateTime": "2019-02-24T14:55:26.000Z", "SpotPrice": "0.011600" }, { "Status": { "Message": "Your Spot request failed due to bad parameters.", "Code": "bad-parameters", "UpdateTime": "2019-02-24T14:29:24.000Z" }, "ActualBlockHourlyPrice": "0.002000", "ValidUntil": "2019-03-03T14:29:23.000Z", "InstanceInterruptionBehavior": "terminate", "Tags": [], "Fault": { "Message": "Addressing type must be 'public'", "Code": "InvalidParameterCombination" }, "BlockDurationMinutes": 60, "SpotInstanceRequestId": "sir-2jsh5brn", "State": "cancelled", "ProductDescription": "Linux/UNIX", "LaunchedAvailabilityZone": "eu-north-1a", "LaunchSpecification": { "Placement": { "Tenancy": "default", "AvailabilityZone": "eu-north-1a" }, "ImageId": "ami-6d27a913", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda", "VirtualName": "root", "NoDevice": "", "Ebs": { "Encrypted": false, "DeleteOnTermination": true, "VolumeType": "gp2", "VolumeSize": 8 } } ], "EbsOptimized": false, "SecurityGroups": [ { "GroupName": "default" } ], "Monitoring": { "Enabled": false }, "InstanceType": "t3.nano", "AddressingType": "", "NetworkInterfaces": [ { "DeviceIndex": 0, "Description": "eth-zero", "NetworkInterfaceId": "", "DeleteOnTermination": true, "SubnetId": "subnet-420ffc2b", "AssociatePublicIpAddress": true } ] }, "Type": "one-time", "CreateTime": "2019-02-24T14:29:23.000Z", "SpotPrice": "0.011600" }, { "Status": { "Message": "Your spot request is fulfilled.", "Code": "fulfilled", "UpdateTime": "2019-02-24T15:37:28.000Z" }, "ActualBlockHourlyPrice": "0.002000", "ValidUntil": "2019-03-03T15:37:26.000Z", "InstanceInterruptionBehavior": "terminate", "Tags": [], "InstanceId": "i-0a29e9de6d59d433f", "BlockDurationMinutes": 60, "SpotInstanceRequestId": "sir-z1e9591m", "State": "active", "ProductDescription": "Linux/UNIX", "LaunchedAvailabilityZone": "eu-north-1a", "LaunchSpecification": { "Placement": { "Tenancy": "default", "AvailabilityZone": "eu-north-1a" }, "ImageId": "ami-6d27a913", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "VirtualName": "root", "NoDevice": "", "Ebs": { "Encrypted": false, "DeleteOnTermination": true, "VolumeType": "gp2", "VolumeSize": 8 } } ], "EbsOptimized": false, "SecurityGroups": [ { "GroupName": "default" } ], "Monitoring": { "Enabled": false }, "InstanceType": "t3.nano", "AddressingType": "public", "NetworkInterfaces": [ { "DeviceIndex": 0, "Description": "eth-zero", "NetworkInterfaceId": "", "DeleteOnTermination": true, "SubnetId": "subnet-420ffc2b", "AssociatePublicIpAddress": true } ] }, "Type": "one-time", "CreateTime": "2019-02-24T15:37:26.000Z", "SpotPrice": "0.008000" }, { "Status": { "Message": "Your Spot request failed due to bad parameters.", "Code": "bad-parameters", "UpdateTime": "2019-02-24T14:33:43.000Z" }, "ActualBlockHourlyPrice": "0.002000", "ValidUntil": "2019-03-03T14:33:42.000Z", "InstanceInterruptionBehavior": "terminate", "Tags": [], "Fault": { "Message": "Invalid device name /dev/sda", "Code": "InvalidBlockDeviceMapping" }, "BlockDurationMinutes": 60, "SpotInstanceRequestId": "sir-n7c15y5p", "State": "cancelled", "ProductDescription": "Linux/UNIX", "LaunchedAvailabilityZone": "eu-north-1a", "LaunchSpecification": { "Placement": { "Tenancy": "default", "AvailabilityZone": "eu-north-1a" }, "ImageId": "ami-6d27a913", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda", "VirtualName": "root", "NoDevice": "", "Ebs": { "Encrypted": false, "DeleteOnTermination": true, "VolumeType": "gp2", "VolumeSize": 8 } } ], "EbsOptimized": false, "SecurityGroups": [ { "GroupName": "default" } ], "Monitoring": { "Enabled": false }, "InstanceType": "t3.nano", "AddressingType": "public", "NetworkInterfaces": [ { "DeviceIndex": 0, "Description": "eth-zero", "NetworkInterfaceId": "", "DeleteOnTermination": true, "SubnetId": "subnet-420ffc2b", "AssociatePublicIpAddress": true } ] }, "Type": "one-time", "CreateTime": "2019-02-24T14:33:42.000Z", "SpotPrice": "0.011600" } ] }
fuente
column -t
hizo el truco para alinear los encabezados con la propia tabla. ¡Gracias!Si los valores no contienen espacios, esto podría ser útil:
read -r -a data <<<'name1 value1 name2 value2' echo "name value" echo "==========" for ((i=0; i<${#data[@]}; i+=2)); do echo ${data[$i]} ${data[$((i+1))]} done
Salida
fuente