There are many ways to generate passwords, and to avoid passwords in the first place (which you really should do), but I wanted a little practice in writing interactive bash scripts. So I chose to create a password generator based on the Diceware algorithm, even though I don’t carry any dice with me.

Find of the day is the excellent gum tool that makes it easy to create pretty user interaction. Thanks Ari!

#!/bin/bash
set -eo pipefail

# Ref: https://theworld.com/~reinhold/diceware.html

if [[ ! -x "$(command -v curl)" ]]; then
  echo "Please install curl"
  exit 1
fi

if [[ ! -x "$(command -v shuf)" ]]; then
  echo "Please install coreutils"
  exit 1
fi

if [[ ! -x "$(command -v gum)" ]]; then
  echo "Please install https://github.com/charmbracelet/gum"
  exit 1
fi

echo 'Which worldlist to use?'
WORDLIST=$(gum choose 'diceware (original)' 'beale (alternative)')

echo 'How many words to pick?'
COUNT=$(gum input --value='6')

echo 'What is your smallest word length?'
MINLEN=$(gum input --value='3')

echo 'Which word separator to use?'
SEPARATOR=$(gum choose 'space' 'dash' 'underscore')

WORDLIST=$(echo "$WORDLIST" | awk '{print $1}')
echo "Using $WORDLIST wordlist to create ${COUNT} words of ${MINLEN} or more characters each, separated by ${SEPARATOR} characters."

CACHE_DIR="${HOME}/.diceware"
if [[ ! -d "$CACHE_DIR" ]]; then
  mkdir "$CACHE_DIR"
fi

WORDFILE="${CACHE_DIR}/${WORDLIST}.wordlist.asc"
if [[ ! -f "$WORDFILE" ]]; then
  # vendored from https://theworld.com/%7Ereinhold/
  curl -sSf "https://tomczarniecki.com/assets/${WORDLIST}.wordlist.asc" \
    -o "$WORDFILE"
fi

separator=' '
if [[ "$SEPARATOR" == "dash" ]]; then
  separator='-'
elif [[ "$SEPARATOR" == "underscore" ]]; then
  separator='_'
fi

phrase=''
for i in $(seq "$COUNT"); do
  word=''
  until [[ "${#word}" -ge "${MINLEN}" ]]; do
    group=''
    for j in {1..5}; do
      roll=$(shuf -i 1-6 -n 1 --random-source=/dev/random)
      if [[ -z "$group" ]]; then
        group="$roll"
      else
        group="${group}${roll}"
      fi
    done
    word=$(grep "$group" "$WORDFILE" | awk '{print $2}')
  done
  if [[ -z "$phrase" ]]; then
    phrase="$word"
  else
    phrase="${phrase}${separator}${word}"
  fi
done

gum style \
  --border double --align center \
  --margin '1 2' --padding '2 4' \
  "$phrase"

Sample output:

$> ./passphrase.sh
Which worldlist to use?
> diceware (original)
  beale (alternative)
How many words to pick?
> 6
What is your smallest word length?
> 3
Which word separator to use?
> space
Using diceware wordlist to create 6 words of 3 or more characters each, separated by space characters.

  ╔══════════════════════════════════════════╗
  ║                                          ║
  ║                                          ║
  ║    neuron pansy cicada mood young ani    ║
  ║                                          ║
  ║                                          ║
  ╚══════════════════════════════════════════╝

Hope you find this useful.