¿Cómo formatear una cadena JSON como una tabla usando jq?

81

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?

Rienda
fuente
¿Podría agregar algún resultado de muestra de su jq -r ...comando?
Micha Wiedenmann
Su uso de echopuede evitarse jq -r '...' <<<$datao jr -r '...' < input-file.json.
Micha Wiedenmann
Tu pregunta es: tengo una cadena, "name1 value1 name2 value2 name3 value3"¿cómo puedo imprimirla como tabla?
Micha Wiedenmann

Respuestas:

67

¿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

12  George
18  Jack
19  Joe

Edición 1: para un formato de grano fino, utilice herramientas comoawk

 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

¿No hay forma de que pueda obtener una variable que contenga una matriz directamente de jq?

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]"
]
sjsam
fuente
Gracias por su respuesta. Esto funciona muy bien en este caso particular, las identificaciones tienen la misma longitud. Imagínese que cambiaría el orden de los campos, eso me daría algo que no parece una tabla práctica en absoluto. Realmente estoy buscando una solución que pueda usar en más conjuntos de datos. ¡Gracias por tu respuesta!
Rein
Ok lo tengo. ¿No hay forma de que pueda obtener una variable que contenga una matriz directamente de jq? ¿Siempre tengo que pasar de una cuerda?
Rein
Gracias por su ayuda, el resultado es exactamente como quería (excepto por las comillas alrededor de los nombres). Se sintió extraño pasar de una cadena en lugar de preparar una matriz lista para usar como lo haríamos, por ejemplo, en Python. Para mí, se siente torpe y sucio, pero supongo que solo yo tengo que acostumbrarme a las ideas de bash. Intentaré convertir esto en una función que pueda reutilizar, para poder usar esto para más cadenas JSON con diferentes encabezados.
Rein
@Rein: para un formateo detallado, necesita imprimir la salida en formato csv y luego usarla 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.
sjsam
1
Gracias por ese enlace y explicación, ¡super útil!
Rein
88

Usar el @tsvfiltro tiene mucho que recomendar, principalmente porque maneja numerosos "casos extremos" de manera estándar:

.[] | [.id, .name] | @tsv

Agregar los encabezados se puede hacer así:

jq -r '["ID","NAME"], ["--","------"], (.[] | [.id, .name]) | @tsv'

El resultado:

ID  NAME
--  ------
12  George
18  Jack
19  Joe

length*"-"

Para automatizar la producción de la línea de guiones:

jq -r '(["ID","NAME"] | (., map(length*"-"))), (.[] | [.id, .name]) | @tsv'
pico
fuente
El filtro @tsv es ni siquiera en la página del manual filtros básicos para JQ, hmm ... Me pregunto ¿qué otra cosa podría haber perdido :)
Ярослав Рахматуллин
14

¡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

  [.[]| with_entries( .key |= ascii_downcase ) ]
      |    (.[0] |keys_unsorted | @tsv)
         , (.[]|.|map(.) |@tsv)

como correr

$ < data jq -rf script  | column -t
name    id  email
George  12  [email protected]
Jack    18  [email protected]
Joe     19  [email protected]

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
1
column -thizo el truco para alinear los encabezados con la propia tabla. ¡Gracias!
Dimitris Moraitidis
0

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

name value
==========
name1 value1
name2 value2
Micha Wiedenmann
fuente
Estoy empezando a darme cuenta de que no puedo obtener una matriz directamente de jq, ¿es correcto? Entonces, ¿el camino a seguir es obtener una cadena de él (en un formato viable) e ir desde allí?
Rein