Maintaining users in a Dovecut user database I learn BASH scripting occasionally from time to time, now I am working on a simple bash script that serves to maintain Dovecot user database. The general purpose of the script is able to list, add or delete user entries in the specified file. Please add some comments about the code - common mistake and etc. I want to learn more about code simplicity and avoiding bad habits.

#!/bin/bash

USERDB="/etc/dovecot/users"
MAILBOXMAP="/etc/postfix/virtual/virtual-mailbox-maps.cf"

## Add user function

add_user () {

# Reading username

while [[ -z "$USERNAME" ]]; do
    echo -n "Please enter username: "
    read "USERNAME"
done

# Reading domain name

while [[ -z "$DOMAIN" ]]; do
    echo -n "Please enter domain name: "
    read DOMAIN
done

# Reading password and generating hash with doveadm pw

while [[ -z "$PASSWORD" ]]; do
    echo -n "Please enter user password: "
    read "PASSWORD"
done

if [[ -n "$PASSWORD" ]]; then
    USERPASS="$(doveadm pw -p $PASSWORD -s SHA512-CRYPT)"
fi

# Adding provided credentials to Dovecot userdb

echo "Adding user credentials to userdb..."

echo "$USERNAME@$DOMAIN:$USERPASS:::" &gt;&gt; "$USERDB"

# Adding provided user@domain to postfix mailbox map

echo "Adding user credentials to postfix map..."

echo "$USERNAME@$DOMAIN $DOMAIN/$USERNAME" &gt;&gt; "$MAILBOXMAP"

# Hashing the map

if [[ -f "$MAILBOXMAP" ]]; then
     postmap hash://"$MAILBOXMAP"
     echo "Hashing map is done!"
else
     echo "Postfix map not exist or have been moved to another directory!"
     exit 1
fi
exit 0
}

## List all users function

list_users () {

if [[ -f "$USERDB" ]]; then
    awk -F: '/./'' {print $1} ' "$USERDB"
else
    echo "User database not exist or have been moved to another directory!"
    exit 1
fi
}

## Delete user function

delete_user () {
if [[ -n "$DELUSER" ]]; then
    sed -i "/^$DELUSER$/d" $USERDB $MAILBOXMAP &amp;&amp; echo "User account have been  deleted!"
else
    echo "User not found!"
    exit 1
fi
}

case "$1" in
 add|-a)
    add_user
    ;;
 list|-l)
    list_users
    ;;
 delete|-d)
    shift
    deluser="$1"
    delete_user
    ;;
 *)
    echo "Unknown command"
    ;;
esac
In this code:


while [[ -z "$USERNAME" ]]; do
    echo -n "Please enter username: "
    read "USERNAME"
done



What if the variable USERNAME is not empty when this code is reached?
For example, the environment might be dirty by misbehaving scripts,
and USERNAME might already be set to garbage, and the script will not prompt the user.

echo -n is not recommended because it's not portable. Use printf instead.

There's no need to quote USERNAME when reading into it.

The equivalent code, safer and better:

USERNAME=
while [[ -z "$USERNAME" ]]; do
    printf "Please enter username: "
    read USERNAME
done


Do similarly for the other loops too.



This will fail if PASSWORD contains spaces:


USERPASS="$(doveadm pw -p $PASSWORD -s SHA512-CRYPT)"



To make it safe, quote it:

USERPASS="$(doveadm pw -p "$PASSWORD" -s SHA512-CRYPT)"




It would be better to indent the function bodies. For example:

list_users () {
    if [[ -f "$USERDB" ]]; then
        awk -F: '/./'' {print $1} ' "$USERDB"
    else
        echo "User database not exist or have been moved to another directory!"
        exit 1
    fi
}


This is especially helpful in the case of longer functions.



The quotes are strange and confusing in this awk:


        awk -F: '/./'' {print $1} ' "$USERDB"



It would be better simply this way:

        awk -F: '/./ {print $1}' "$USERDB"