#!/bin/bash
#
# SecretSanta:
#
# A program to simulate drawing names from a hat for a gift exchange
#
# This program will ask for a list of names. It will then randomly
# draw a name for each person--checking that the person did not
# draw his or her own name or the name of a person to be disallowed.
# When the program draws a legal name for a person, a text file
# with that person's name on it will be created to identify who they
# have picked. By default the person running the program does not
# see what names are picked for each person. He or she can then send
# the output files to the people on the list, without knowing who
# they picked.
# The program includes an option to check that all the output files
# have legal picks and that all names on the list have been picked.
#
# By: Geoffrey Warne
# Email:
[email protected]# Last Edit: January 25, 2010
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# getoptions: The first function to run, 'get_options' looks for
# valid options given by the operator on the command line. A 'help'
# option is included to list the available options.
function get_options
{
# Check positional parameters for
# options
[email protected] # Set default option values
secret="yes"
disallow="no"
verbose="no"
remove="no"
pickcheck="no"
help="no"
keep="no"
useold="no"
# Check for each option and set values
for opt in $optlist; do
option=$opt
if [ $option = "-ns" ]; then secret="no"; fi
if [ $option = "-x" ]; then disallow="yes"; fi
if [ $option = "-v" ]; then verbose="yes"; fi
if [ $option = "-rm" ]; then remove="yes"; fi
if [ $option = "-chk" ]; then pickcheck="yes"; fi
if [ $option = "-h" ]; then help="yes"; fi
if [ $option = "-k" ]; then keep="yes"; fi
if [ $option = "-o" ]; then useold="yes"; fi
done
# Print 'help' menu if requested
if [ $help = "yes" ]; then
echo "Usage: secretsanta [Option] [Option] [...]"
echo "Options: -ns : not secret (view picks)"
echo " -x : disallow picks"
echo " -v : verbose output"
echo " -rm : remove existing files"
echo " -chk: include pickcheck"
echo " -h : help"
echo " -k : keep list after completion"
echo " -o : use old (existing) list"
echo
echo "Note: To have output printed to a file,"
echo "please try: secretsanta [Option] [Option] [...] | tee [Filename]"
echo
exit 0
fi
# If 'verbose' option is given,
# Print out list of options and values
if [ $verbose = "yes" ]; then
echo "Options ($#): $optlist"
echo
echo "Secret? $secret"
echo "Disallow? $disallow"
echo "Verbose? $verbose"
echo "Remove? $remove"
echo "Pickcheck? $pickcheck"
echo "Keep? $keep"
echo "Useold? $useold"
echo
fi
# If 'useold' option is given,
# Check for existing list to use;
# Otherwise get a new list
if [ $useold = "yes" ] && [ ! -s SantaList.txt ]; then
echo "There is no existing list. Getting new list..."
echo
useold="no"
fi
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# setup: This function removes existing files from previous runs
# and prepares fresh files. Removal and output will depend on options
# given (especially -rm, -o, and -v) and which files currently exist.
function setup
{
echo "Setup"
echo "~~~~~"
# Starting with SantaList,
# If file does not exist,
# Then create it
if [ ! -e ./SantaList.txt ]; then
if [ $verbose = "yes" ]; then echo "Creating new file, SantaList.txt..."; echo; fi
touch ./SantaList.txt
# If 'useold' option is on,
# Use existing list
else
if [ $useold = "yes" ]; then
if [ $verbose = "yes" ]; then echo "Use old option (-o) set... Using existing list..."; echo; fi
# Otherwise, delete existing list,
# and create a new one
else
if [ $verbose = "yes" ]; then echo "Removing previous list..."; fi
rm ./SantaList.txt
if [ $verbose = "yes" ]; then echo "Creating new list..."; echo; fi
touch ./SantaList.txt
fi
fi
# Next, set output file directory...
# If directory does not yet exist,
# Create it;
if [ ! -e ./SecretSanta ]; then
if [ $verbose = "yes" ]; then echo "Making new directory, SecretSanta, in current directory..."; fi
mkdir ./SecretSanta
# Otherwise, delete existing files
else
if [ $verbose = "yes" ]; then echo "Directory, SecretSanta, already exists."; fi
if [ ! -z "$(ls ./SecretSanta)" ]; then
# If "remove" option is active,
# Automatically delete output files
if [ $verbose = "yes" ]; then echo "Directory not empty."; fi
if [ $remove = "yes" ]; then
if [ $verbose = "yes" ]; then echo "Remove option? Yes... Removing files..."; fi
rm -R ./SecretSanta/*
# Otherwise, give operator a chance to
# to review files and keep if desired
else
if [ $verbose = "yes" ]; then echo "Remove option? No"; fi
echo "Remove existing Secret Santa files?"
echo "(New files with same names will be overwritten)"
rm -R -i ./SecretSanta/*
fi
# If directory is empty, do nothing
else
if [ $verbose = "yes" ]; then echo "Directory is empty."; fi
fi
fi
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# get_list: This function gets a list of names for the secret santa
# pool. Once it knows how many people are in the pool, it calls on the
# function, get_name, to get the names. If the 'disallow' option is
# active, this function will call on the function, disallow, to get
# disallowed names. If the 'useold' option is active, secretsanta will
# will use an existing list.
function get_list
{
# First time around $1 should be zero
LOOP=$1
# Check if 'useold' option is on
if [ $useold = "no" ]; then
# Only print header first time around
if [ $LOOP = 0 ]; then echo; echo "Get List"; echo "~~~~~~~~"; fi
# Prompt operator for size of pool
# Returning an empty value will yield
# a pool size of zero
echo -n "How many people in list? "; read NUMBER
NUMBER=${NUMBER:=0}
# If operator enters a non-number,
# Or a number less than 3,
# Loop back to top of function,
# Setting $1 to 1 (indicating a loop)
if !(echo $NUMBER | grep -q "^[0-9]*$"); then get_list 1; return; fi
if [ $NUMBER -lt 3 ]; then echo "Number must be greater than two."; get_list 1; return; fi
echo
# For number of people in the pool,
# Run function, get_name, and
# Append name to list of people
for (( ORDER=1; ORDER <= $NUMBER; ORDER++)); do
get_name $ORDER
echo -n "$NAME:" >> ./SantaList.txt
# If 'disallow' option is on,
# Run function, disallow, to get
# the name of one disallowed draw
if [ $disallow = "yes" ]; then disallow $NAME; fi
echo $DISALLOWED >> ./SantaList.txt
done
else # If "useold" option is on,
# Do not touch existing SantaList,
# And count existing number of lines
let -i NUMBER=$(cat ./SantaList.txt | wc -l)
fi
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# get_name: This is part of the parent function, get_list. It prompts
# the operator for each name in the pool list and checks that it is
# valid.
function get_name
{
# Parent function, get_list, sends the
# Line number of the name to get, here
ORDER=$1
# Prompt operator for a name
echo -n "Please enter name #$ORDER: "
read NAME
# Check validity of name entered:
# Not empty, "NONE", or a name already
# given
if [ "$NAME" = "" ]; then echo "Name must have at least one character..."; get_name $ORDER; return; fi
if [ "$NAME" = "NONE" ]; then echo "NONE is a reserved word..."; get_name $ORDER; return; fi
# Check for two or more words
if (echo "$NAME" | grep -q " "); then echo "Only one name (no spaces)..."; get_name $ORDER; return; fi
if (cat ./SantaList.txt | cut -d: -f1 | grep -q -x "$NAME"); then echo "Name, $NAME, is already listed"; get_name $ORDER; return; fi
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# disallow: This is part of the function, get_list. When 'disallow'
# option is active, this function asks the operator for a name that
# current person in list is not allowed to draw.
function disallow
{
# Prompt user for disallowed name
echo -n "Enter disallowed name for $1: "
read DISALLOWED
if (echo "$DISALLOWED" | grep -q " "); then echo "Only one name (no spaces)..."; disallow $NAME; return; fi
# If no disallowed name is given,
# Set exclusion to default, "NONE"
DISALLOWED=${DISALLOWED:="NONE"}
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# take_turns: This function is called in the body of 'secretsanta'.
# Here, a name is drawn ($PICK) for each of the people ($SANTA) in the
# pool. If a person draws their own name or a disallowed name, the
# child function, draw, will loop until a valid pick is made. When
# all Santas have drawn names, the names with be written to output
# files ($SANTA.txt).
# If the draw cannot complete (because either the last person's own
# name or a disallowed name are the only names left at the end of the
# round), this function will loop as a new round. If five rounds are
# completed unsuccessfully, the program will exit.
function take_turns
{
# Parent function, secretsanta, and
# loop send number of people and round
# number, here
NUMBER=$1
ROUND=$2
# At the start of the first round,
# Print the function heading
if [ $ROUND -eq 1 ]; then
echo
echo "Drawing"
echo "~~~~~~~"
fi
# If five rounds have completed
# unsuccessfully, print message and
# exit.
if [ $ROUND -eq 6 ]; then echo "Too many rounds... Cannot complete... Please redo list."; exit 1; fi
# Print the round number
echo "Round: $ROUND"
# Clean up any temporary files left
# from previous runs
if [ -e /var/tmp/Draws.txt ]; then rm /var/tmp/Draws.txt; fi
touch /var/tmp/Draws.txt
if [ -e /var/tmp/DrawsRandom.txt ]; then rm /var/tmp/DrawsRandom.txt; fi
touch /var/tmp/DrawsRandom.txt
# Cut out names from the pool list and
# place them in a "hat"
cat ./SantaList.txt | cut -d: -f1 >> /var/tmp/SantaHat
# Start TURN count at zero
let -i TURN=0
# Read pool list, 'SantaList.txt'
# for Santa and disallowed names
# Then send names, line-by-line to
# child function, draw
for LINE in $(cat ./SantaList.txt); do
SANTA=$(echo $LINE | cut -d: -f1)
if [ $disallow = "yes" ]; then DISALLOWED=$(echo $LINE | cut -d: -f2); fi
DISALLOWED=${DISALLOWED:="NONE"}
# Each person, one-by-one, takes a turn
let TURN=TURN+1
echo -n "$TURN) $SANTA --> "
draw $TURN $NUMBER $SANTA $DISALLOWED
# Check if round ended successfully,
# If not, then go on to next round
if [ $? = "2" ]; then let ROUND=ROUND+1; take_turns $NUMBER $ROUND; return; fi
# If 'secret' option is turned off,
# print out the name that was drawn
if [ $secret = "no" ]; then echo -n "$PICK --> "; else echo -n "--> "; fi
# Print name of output file
echo "$SANTA.txt"
# Record name picked in temporary file
echo $PICK >> /var/tmp/Draws.txt
# Create output file with the name of
# the person who is drawing on it
echo "Your name: $SANTA" > ./SecretSanta/"$SANTA.txt"
echo "Your pick: $PICK" >> ./SecretSanta/"$SANTA.txt"
# Remove the name that was drawn from
# the hat
sed '1d' /var/tmp/SantaHatRandom > /var/tmp/SantaHat
done
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# draw: Child to function, take_turns, this function random sorts
# the (remaining) names in the hat (SantaHat) and draws the first
# name off the top of the list. It then checks is the draw is valid
# and loops back if the draw is not valid.
function draw
{
# Parent function and loop send
# variables here
TURN=$1
NUMBER=$2
SANTA=$3
DISALLOWED=$4
DISALLOWED=${DISALLOWED:="NONE"}
# Random sort the names in the hat file
# and pick the first one off the top
sort -R /var/tmp/SantaHat > /var/tmp/SantaHatRandom
read PICK < /var/tmp/SantaHatRandom
# If there are only two names left, the
# name of the person who is drawing and
# his or her disallowed name, then end
# round
if [ $(cat /var/tmp/SantaHat | wc -l) = 2 ] && (cat /var/tmp/SantaHat | grep -q $SANTA) && (cat /var/tmp/SantaHat | grep -q $DISALLOWED); then echo "Only Two Names Left (Santa and Disallowed)... Start Over"; echo; rm /var/tmp/SantaHat; return 2; fi
# If the last person draws their own
# name or their disallowed name, then
# end round
if [ $TURN = $NUMBER ] && [ $PICK = $SANTA ]; then echo "Santa left with own name... Start Over"; echo; rm /var/tmp/SantaHat; return 2; fi
if [ $TURN = $NUMBER ] && [ $PICK = $DISALLOWED ]; then echo "Santa left with disallowed name... Start Over"; echo; rm /var/tmp/SantaHat; return 2; fi
# If 'verbose', echo picked name
if [ $PICK = $SANTA ] && [ $verbose = "yes" ]; then echo -n "$PICK, "; fi
if [ $PICK = $DISALLOWED ] && [ $verbose = "yes" ]; then echo -n "$PICK, "; fi
# If person draws own name or
# disallowed name, then draw again
if [ $PICK = $SANTA ]; then draw $TURN $NUMBER $SANTA $DISALLOWED; fi
if [ $PICK = $DISALLOWED ]; then draw $TURN $NUMBER $SANTA $DISALLOWED; fi
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# pickcheck: If the 'pickcheck' option (-chk) is on, check if the
# output files are correct. Check that everyone on the list got a file
# with their name on it. Check that they got a pick and that it is not
# themselves or a disallowed name. Finally, check that all the names
# on the list were picked.
function pickcheck
{
let -i ERRORS=0
# Print formatted chart headings
echo "Pick Check"
echo "~~~~~~~~~~"
printf "%-12s" "Santa"
printf "%-12s" "Disallow"
printf "%-12s" "Card File"
printf "%-12s" "Card Name"
printf "%-12s" "Pick"
printf "%-12s" "Self"
printf "%-12s" "Disallowed"
printf "%-12s\n" "Picked Once"
printf "%-12s" "~~~~~"
printf "%-12s" "~~~~~~~~"
printf "%-12s" "~~~~~~~~~"
printf "%-12s" "~~~~~~~~~"
printf "%-12s" "~~~~"
printf "%-12s" "~~~~"
printf "%-12s" "~~~~~~~~~~"
printf "%-12s\n" "~~~~~~~~~~~"
# Read the pool list (SantaList.txt) line-by-line,
# And get the name of person to check (SANTA) and
# a disallowed name (DISALLOWED). If there is no disallowed name,
# set it to "NONE", as the default.
# Also, get all other relevent values for validating draws, and
# set appropriate defaults.
for LINE in $(cat ./SantaList.txt); do
SANTA=$(echo $LINE | cut -d: -f1)
if (ls ./SecretSanta/ | grep -q -x "$SANTA.txt"); then
SANTACARD="$SANTA.txt"
else SANTACARD="NONE"
fi
#Get all relevent values from output files
#(./SecretSanta/$SANTA.txt)
if [ ! $SANTACARD = "NONE" ]; then
PICKLINE=$(cat ./SecretSanta/$SANTA.txt | grep "Your pick: ")
PICK=${PICKLINE#Your pick: *}
PICK=${PICK:="NONE"}
NAMELINE=$(cat ./SecretSanta/$SANTA.txt | grep "Your name: ")
YOUR_NAME=${NAMELINE#Your name: *}
YOUR_NAME=${YOUR_NAME:="NONE"}
else
PICK="---"
YOUR_NAME="---"
fi
if [ $disallow = "yes" ]; then
DISALLOWED=$(echo $LINE | cut -d: -f2)
DISALLOWED=${DISALLOWED:="NONE"}
else
DISALLOWED="NONE"
fi
#Count and list SecretSanta(s) for each name in list
let -i SS=0
for FILE in $(ls ./SecretSanta/); do
if (cat ./SecretSanta/$FILE | grep -q -x "Your pick: $SANTA"); then
let SS=SS+1
SECRETSANTA[$SS]=$FILE
fi
done
#Print out verbose header
if [ $verbose = "yes" ]; then
echo
printf "%-24.23s" "$LINE"
printf "%-12.11s" "$SANTACARD"
printf "%-12.11s" "$YOUR_NAME"
if [ $secret = "no" ] || [ $PICK = "NONE" ] || [ $PICK = "---" ]; then printf "%-12.11s" "$PICK"; else printf "%-12s" "Secret"; fi
printf "%-12.11s" "($SANTA)"
printf "%-12.11s" "($DISALLOWED)"
if [ $secret = "no" ]; then
for (( i=1 ; i <= $SS ; i++ )); do
echo -n "${SECRETSANTA[$i]},"
done
if [ $SS -eq 0 ]; then echo -n "NONE"; fi
echo
else
echo "SS=$SS"
fi
fi
printf "%-12.11s" $SANTA
printf "%-12.11s" $DISALLOWED
#Check for file with each person's name on it ($SANTA.txt).
if [ $SANTACARD = "NONE" ]; then
let ERRORS=ERRORS+1
printf "%-12s" "NO [$ERRORS]"
no_file="yes"
else
printf "%-12s" "yes"
no_file="no"
fi
#If there is a file with the person's name on it,
#Check to see if the name inside is the same
if [ $no_file = "no" ]; then
if [ $SANTA = $YOUR_NAME ]; then
printf "%-12s" "yes"
else
let ERRORS=ERRORS+1
printf "%-12s" "NO [$ERRORS]"
fi
else
printf "%-12s" "---"
fi
#If there is a file with the person's name on it,
#Check if their pick was good
if [ $no_file = "no" ]; then
#Check that each person has a name that is in the list
if !((cat SantaList.txt | cut -d: -f1) | grep -q -x $PICK); then
let ERRORS=ERRORS+1
printf "%-12s" "NO [$ERRORS]"
else
printf "%-12s" "yes"
fi
#If there is a pick to check, check whether it is legal
if [ $PICK = "NONE" ]; then
printf "%-12s" "---"
printf "%-12s" "---"
else
#Check if the person picked their own name
if [ $PICK = $SANTA ]; then
let ERRORS=ERRORS+1
printf "%-12s" "YES [$ERRORS]"
else
printf "%-12s" "no"
fi
#Check if the person picked their disallowed name
if [ $PICK = $DISALLOWED ]; then
let ERRORS=ERRORS+1
printf "%-12s" "YES [$ERRORS]"
else
printf "%-12s" "no"
fi
fi
#If there is no file with the person's name on it,
#Cannot check the pick, so print "---" and move on
else
printf "%-12s" "---"
printf "%-12s" "---"
printf "%-12s" "---"
fi
#Check all output files ($CHECK.txt),
#To make sure that all names have been picked.
if [ $SS -eq 1 ]; then
printf "%-12s\n" "yes"
else
let ERRORS=ERRORS+1
printf "%-12s\n" "NO [$ERRORS]"
fi
done
echo
#Print error count
echo "[Errors: $ERRORS]"; echo
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# BODY:
# This is the main body of the program. It runs the child functions,
# get_options, cleanup, get_list, take_turns, and (if the 'pickcheck'
# option is set) pickcheck. Here the title, the pool list, a list of
# disallowed names, a list of picked names (random), and the list of
# output files are displayed. Temporary files are removed. If the
# 'keep' function is on, the pool list is not deleted.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Print title
echo " _______ "
echo " __/ \ "
echo " / \ "
echo " __/ \_ "
echo " / \_ \_*** "
echo " / \___/ * "
echo " ~~~~~~~~~~~~~~~~~~ * * "
echo "{ Secret Santa } *** "
echo " ~~~~~~~~~~~~~~~~~~ "
echo
# Run function to get a list of options
get_options
[email protected] # Run function to remove old files and
# create new files
setup
# Run function to get a list of people
# in the Secret Santa pool
# (Set $1 to 0 to indicate that this
# is the first time around)
get_list 0
# Read pool list and list people in the
# pool
echo
echo "Santa List"
echo "~~~~~~~~~~~"
for LINE in $(cat ./SantaList.txt); do
let l=l+1; echo -n "$l) "
echo $(echo $LINE | cut -d: -f1)
done
# If exclusions are active,
# Read list and print exclusions
if [ $disallow = "yes" ]; then echo; fi
if [ $disallow = "yes" ]; then echo "Disallowed Picks"; fi
if [ $disallow = "yes" ]; then echo "~~~~~~~~~~~~~~~~"; fi
if [ $disallow = "yes" ]; then let -i r=0; fi
if [ $disallow = "yes" ]; then
for LINE in $(cat ./SantaList.txt); do
let r=r+1; echo -n "$r) "
echo -n $(echo $LINE | cut -d: -f1)
echo -n " cannot pick: "
echo -n $(echo $LINE | cut -d: -f2)
if [ -z $(echo $LINE | cut -d: -f2) ]; then
echo -n "NONE"
fi
echo
done
fi
# Run function to draw names
# (Send number of people and round=1)
take_turns $NUMBER 1
# Cleanup temporary files
rm /var/tmp/SantaHatRandom
rm /var/tmp/SantaHat
# Random sort names drawn
# Remove ordered list of names drawn
sort -R /var/tmp/Draws.txt > /var/tmp/DrawsRandom.txt
rm /var/tmp/Draws.txt
# Print out list of names drawn
# in random order,
# Then remove list
echo
echo "Names Drawn (Random Order)"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~"
cat /var/tmp/DrawsRandom.txt
rm /var/tmp/DrawsRandom.txt
echo
# Print out list of files in output
# directory
echo "Output Card Files (Directory: ./SecretSanta)"
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
ls -a ./SecretSanta
echo
# If "pickcheck" option was selected,
# Run function to check picks
if [ $pickcheck = "yes" ]; then pickcheck; fi
# Unless "keep" option was specified,
# Remove the pool list
if [ $keep = "no" ]; then rm SantaList.txt; fi
# Report number of rounds taken
# Report program file and arguments
if [ $verbose = "yes" ]; then echo "[Rounds: $ROUND]"; echo; fi
if [ $verbose = "yes" ]; then echo "$0
[email protected]"; fi
# Signal end of program
echo "done"; echo