3

My goal is to output a JSON object using jq on the output of a find command in bash. It could either be a one-line command or a bash script.

I have this command which creates JSON objects from each line of output:

find ~/ -maxdepth 1 -name "D*" | \
while read line; \
do jq -n \
--arg name "$(basename "$line")" \
--arg path "$line" \
'{name: $name, path: $path}'; \
done

The output looks like this:

{
  "name": "Desktop",
  "path": "/Users/username/Desktop"
}
{
  "name": "Documents",
  "path": "/Users/username/Documents"
}
{
  "name": "Downloads",
  "path": "/Users/username/Downloads"
}

But I need these objects to be in an array, and I need the array to be the value of a a parent object's single key called items, like so:

{"items": [
    {
      "name": "Desktop",
      "path": "/Users/username/Desktop"
    },
    {
      "name": "Documents",
      "path": "/Users/username/Documents"
    },
    {
      "name": "Downloads",
      "path": "/Users/username/Downloads"
    }
  ]
}

I tried adding the square brackets to the jq output string for each line ('[{name: $name, path: $path}]';) and that adds the brackets but not the commas between the array elements.

I found possible solutions here but I could not figure out how to use them while looping through each line.

3 Answers 3

17

This trick with the jq 1.5 inputs streaming filter seems to do it

... | jq -n '.items |= [inputs]'

Ex.

$ find ~/ -maxdepth 1 -name "D*" | 
    while read line; do 
      jq -n --arg name "$(basename "$line")" \
            --arg path "$line" \
        '{name: $name, path: $path}'
    done | jq -n '.items |= [inputs]'
{
  "items": [
    {
      "name": "Downloads",
      "path": "/home/steeldriver/Downloads"
    },
    {
      "name": "Desktop",
      "path": "/home/steeldriver/Desktop"
    },
    {
      "name": "Documents",
      "path": "/home/steeldriver/Documents"
    }
  ]
}
1
  • 1
    Thank you, this works. Also, when seeing your example output I realized that I'd made a mistake in the example output that I included... it showed an invalid list of arrays for the value of the items field. I corrected the question just now. Your example produces a single array of objects, which is what I needed.
    – Employee
    Commented Feb 26, 2020 at 21:34
2

Calling jq directly from find, and then collecting the resulting data with jq to construct the final output, without any shell loops:

find ~ -maxdepth 1 -name '[[:upper:]]*' \
    -exec jq -n --arg path {} '{ name: ($path|sub(".*/"; "")), path: $path }' \; |
jq -n -s '{ items: inputs }'

The jq that is being executed via -exec creates a JSON object per found pathname. It strips off everything in the pathname up to the last slash for the name value, and uses the pathname as is for the path value.

The final jq reads the data from find into an array with -s, and simply inserts it as the items array in a new JSON object. The final jq invocation could also be written jq -n '{ items: [inputs] }.

Example result (note that I was using [[:upper:]* in place of D* for the -name pattern with find):

{
  "items": [
    {
      "name": "Documents",
      "path": "/home/myself/Documents"
    },
    {
      "name": "Mail",
      "path": "/home/myself/Mail"
    },
    {
      "name": "Work",
      "path": "/home/myself/Work"
    }
  ]
}
-1

If you dont mind using a programming language, this is pretty simple. Here is an example with PHP:

<?php
$m1 = [
   'items' => []
];
$a1 = glob('~/D*');
foreach ($a1 as $s1) {
   $m1['items'][] = [
      'name' => basename($s1),
      'path' => $s1
   ];
}
echo json_encode($m1, JSON_PRETTY_PRINT);

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .