Petra Barus
Petra Barus

Reputation: 4013

Create Bash loop from JSON string using jq

I am writing a bash script that reads a JSON string then loop based on the JSON values to execute a CLI command.

#!/usr/bin/env bash

jq --version > /dev/null 2>&1 || { echo >&2 "jq is required but it's not installed. Aborting."; exit 1; }

read -r -d '' USER_ACTIONS << EOM
{
    "user1": [
        "action1"
    ],
    "user2": [
        "action2",
        "action3"
    ]
    
}
EOM

USERS= #TODO
for user in USERS; do
   ACTIONS= #TODO
   for action in ACTIONS; do
       echo "Executing ${command} ${user}-${action}"
   done   
done

If jq is present in the server, how do I populate the USERS and ACTIONS variable?

Upvotes: 0

Views: 324

Answers (3)

tshiono
tshiono

Reputation: 22012

Would you please try the following:

#!/bin/bash

declare -a users                        # array of users
declare -A actions                      # array of actions indexed by user

read -r -d '' user_actions << EOM
{
    "user1": [
        "action1"
    ],
    "user2": [
        "action2",
        "action3"
    ]
}
EOM

while IFS=$'\t' read -r key val; do
    users+=( "$key" )                   # add the user to the array "users"
    actions[$key]="$val"                # associate the actions with the user
done < <(jq -r 'to_entries[] | [.key, .value[]] | @tsv' <<< "$user_actions")

for user in "${users[@]}"; do           # loop over "users"
    IFS=$'\t' read -r -a vals <<< "${actions[$user]}"
    for action in "${vals[@]}"; do
        echo yourcommand "$user" "$action"
    done
done

Output:

yourcommand user1 action1
yourcommand user2 action2
yourcommand user2 action3

[Explanations]

  • The jq command outputs TSV which looks like:
user1\taction1
user2\taction2\taction3

where \t represents a tab character used as a field delimiter.

  • The first read builtin command assigns key to the first field and val to the remaining field(s). If val contains two or more fields, it will be splitted with tne next read builtin in the next loop.
  • if the output looks good, drop echo and replace the string yourcommand with the command name.

Upvotes: 0

pmf
pmf

Reputation: 36033

Depending on what command you want to execute, if it can be performed from within jq, it's easier to also move the loop inside. There are several ways to accomplish that. Here are some examples, all yielding the same output:

jq -r 'to_entries[] | "Executing command \(.key)-\(.value[])"' <<< "$USER_ACTIONS"
jq -r 'keys[] as $user | "Executing command \($user)-\(.[$user][])"' <<< "$USER_ACTIONS"
jq -r --stream '"Executing command \(.[0][0])-\(.[1]? // empty)"' <<< "$USER_ACTIONS"

Output:

Executing command user1-action1
Executing command user2-action2
Executing command user2-action3

Upvotes: 1

Gilles Qu&#233;not
Gilles Qu&#233;not

Reputation: 184965

It seems better to play with vanilla (nodejs):

const myvar={
    "user1": [
        "action1"
    ],
    "user2": [
        "action2",
        "action3"
    ]
};

for (let user in myvar) {
     myvar[user].forEach((action) => {
        console.log("Executing command " + user + "-" + action);
    });
}

Output

Executing command user1-action1
Executing command user2-action2
Executing command user2-action3

Usage

node script.js

Usage with bash

You can remove Executing string, then:

node script.js | bash

Upvotes: 0

Related Questions