Bash: Read JSON File to Add Many Users

📅 February 22, 2024
“Can Bash parse a JSON file?”

Yes, but not natively like Python can using the json module.

Parsing a JSON file from a Bash script usually involves writing custom code that parses strings and such, but there is a handy program call jq that handles the details of JSON parsing for use.

Suppose we want to add a batch of user accounts, each user with a unique password and assignment to a Linux specific group. This involves more user information that a simple username with a temporary common password. We can store this user information in a JSON file, and then let a Bash script process the user data from the file to create the users.

Here is one way to achieve this.

Install jq

Rather than writing custom functions and complex string parsing code, we will use the program jq that parses JSON data for us. jq must be installed since it is not present by default.

sudo apt install jq

JSON File Creation

We will assume that all data in the JSON file is correct, valid, and properly formatted for use with Linux. Meaning, once we have the JSON file, we can add users from it. Our Bash script will not perform sanitization in order to keep it simple. The trick is creating this file with appropriate data.

This can be edited by hand, but I chose to write a Python script that reads nicknames from a custom text file (one name per line), and formats the name into a user, generates a password, and then picks one of two groups at random.

genusernames.py

#!/usr/bin/env python3

# Generate list of usernames as json data

import json, random

inFile = 'nicknames.txt'
outFile = 'usernames.json'
userCount = 25

# Target groups must already exist on the Linux system
groups = [
    'gamedev',
    'betatester'
]

userSet = []


# User class
class User:
    def __init__(self, username):
        self.username = self.filterUsername(username)
        self.password = self.genPassword()
        self.group = random.choice(groups)

    # Sanitize username
    def filterUsername(self, username):
        return username.lower().strip()

    # Generate password from username
    def genPassword(self):
        return self.username[:8] + str(random.randint(10000, 99999))

# Gather nicknames into list for random
with open(inFile, 'rt') as f:
    names = f.readlines()

# Create user objects
for i in range(userCount):
    name = ''.join([c if c.isalnum() else '' for c in random.choice(names).strip()])
    userSet.append(User(name))

# Write JSON data to file
with open(outFile, 'wt') as f:
    json.dump([vars(user) for user in userSet], f)

 

This is actually longer than the Bash script. How you create the JSON file is up to you, but this works for this demonstration.

  • The source nicknames.txt file contains hundreds of names, but only 25 are chosen at random.
  • Ensure that the groups gamedev and betatester already exist on the system.
  • Output file is usernames.json, and it contains a list of User objects whose properties are properly formatted as JSON data. Our Bash script will read and parse this JSON file.
  • Instead of sharing a common password with all users and instructing them to change it later, we take the first 8 characters (or fewer if under 8) from the username and append 5 random digits. The user will need to know his own password in order to log in. This password can then be changed by the user.
  • The User class and list comprehension handle the details of sanitizing the input and formatting usernames into lowercase. For example, Pork Chop becomes porkchop.
  • Originally, userSet was a set, not a list, but a set turned out to be unnecessary since our future Bash script will check for existing usernames before adding another. Duplicate usernames are allowed in the JSON file since they will be ignored when creating the users.

The Python script produces this fictional JSON file:

usernames.json

[{"username": "foxy", "password": "foxy55616", "group": "betatester"},
 {"username": "hotsauce", "password": "hotsauce36786", "group": "gamedev"},
 {"username": "lexi", "password": "lexi59829", "group": "gamedev"},
 {"username": "pasha", "password": "pasha17418", "group": "gamedev"},
 {"username": "joker", "password": "joker52601", "group": "betatester"},
 {"username": "koko", "password": "koko23115", "group": "betatester"},
 {"username": "huey", "password": "huey64049", "group": "gamedev"},
 {"username": "floyd", "password": "floyd33278", "group": "gamedev"},
 {"username": "janitor", "password": "janitor76178", "group": "gamedev"},
 {"username": "june", "password": "june51501", "group": "gamedev"},
 {"username": "volvo", "password": "volvo13714", "group": "betatester"},
 {"username": "miasy", "password": "miasy33869", "group": "gamedev"},
 {"username": "dobie", "password": "dobie27558", "group": "gamedev"},
 {"username": "buddie", "password": "buddie90644", "group": "gamedev"},
 {"username": "zeke", "password": "zeke18775", "group": "betatester"},
 {"username": "koko", "password": "koko80567", "group": "gamedev"},
 {"username": "hawk", "password": "hawk54463", "group": "betatester"},
 {"username": "digger", "password": "digger70353", "group": "betatester"},
 {"username": "cuddles", "password": "cuddles78675", "group": "betatester"},
 {"username": "champ", "password": "champ45375", "group": "gamedev"},
 {"username": "boomhauer", "password": "boomhaue99872", "group": "betatester"},
 {"username": "edsel", "password": "edsel55063", "group": "betatester"},
 {"username": "freddy", "password": "freddy94153", "group": "gamedev"},
 {"username": "bo", "password": "bo86538", "group": "betatester"},
 {"username": "precious", "password": "precious76591", "group": "gamedev"}]

Bash Script

addusersjson.sh

#!/bin/bash

# Add new users specified in JSON file
# Requires jq
#     sudo apt install jq

for user in $(jq -c '.[]' usernames.json)
do
    username=$(echo "$user" | jq -r '.username')
    password=$(echo "$user" | jq -r '.password')
    group=$(echo "$user" | jq -r '.group')

    # Check if user already exists
    if [ $(grep -c "^$username:" /etc/passwd) -eq 0 ]
    then
        echo "Adding: ${username} ${password} ${group}"
        sudo useradd -mG "${group}" "${username}"
        echo "${username}:${password}" | sudo chpasswd
    fi
done

The code is similar to adding new users from a text file, but the difference is that we are parsing a JSON file where each user contains more associated data than just a username.

for user in $(jq -c '.[]' usernames.json)

This is how we loop through an array inside a JSON file. Our JSON file is organized as a list of dictionaries (User objects with the properties username, password, group), so we need to loop through the list one user at a time to create that user.

jq -c '.[]' usernames.json

This is how we loop through a list within a JSON file using jq. The -c option returns the entire user object on a single line of text., and the ‘.[]’ is jq syntax that returns all objects in the JSON list. We could use indexing, such as .[3] to grab a specific user object, but we need to iterate through all of them in order to create all users.

username=$(echo "$user" | jq -r '.username')
password=$(echo "$user" | jq -r '.password')
group=$(echo "$user" | jq -r '.group')

Once we have a specific user object, represented by the variable $user, we can get the value of a property by name.

  • .username returns the username property
  • .password returns the password property
  • .group returns the group property

The -r option removes the double quotes around keys and string values in a JSON file. Omitting -r causes “porkchop” to appear instead of porkchop. We need to remove those double quotes from all three strings. Once we have the values, they are assigned to separate variables.

if [ $(grep -c "^$username:" /etc/passwd) -eq 0 ]

This counts the number of times the prospective username appears in the /etc/passwd file. If it does not exist, then the count will be 0. If so, then create the user with useradd.

sudo useradd -mG "${group}" "${username}"

This creates the user and assigns him to the specified group. Make sure that the groups exist on the Linux system before running this script. Also, only an administrator can add users to the system, so you will be prompted for an admin password once. Otherwise, consider it a duplicate username that already exists on the system and skip it.

echo "${username}:${password}" | sudo chpasswd

So far, we have added a new user, but he cannot log in since there is no password assigned yet. This sets the associated password for the user. Be sure to share this with the user so he can log in.

Twenty-five new users added from a JSON file, each with his own password.

Each user is assigned to one of two groups: gamedev or betatester. Users still belong to their own primary group (shown here), so the user will need to log in and run groups to see which groups he belongs to.

Take note that there is no username length check with this code, so the two-character username bo (highlighted) is allowed. If you wish to have usernames within a certain length, then the script will need to be modified.

“Why not just use Python?”

That is certainly possible, but the goal here is to use Bash to add users. How can Bash parse a JSON file? That is the question I wanted to answer in order to add 100+ users to a Linux system based upon user information from a JSON file.

Conclusion

Built-in JSON parsing is not a feature of Bash, but jq provides this functionality and makes it easy to use. There is much more that is possible with jq, including pretty printing with color, so definitely explore its man page for a taste of additional ideas.

Have fun!

, , ,

  1. Leave a comment

Leave a comment