4

I'm trying to create a bash script to display a word of the day (random for each day). I have a dictionary file that on each line has a word and its definition.

I'd like to use date to get a unique value for each day. Like so

today=$(date '+%Y%m%d') # will return 20160616 (for today)

Now I'd like to use this value to generate a line number for me to grab from the dictionary file.

My dictionary is 86036 lines long so I need to convert $today to a value between 1 and 86036.

What is the best way to do this?

2
  • It should generate the same random number (between 1..86036) for each 24-hour period? Also -- I just did the math and you won't run out of words for ~235 years.
    – Jeff Schaller
    Jun 17, 2016 at 0:46
  • 1
    Does there need to be randomness in the selection? If the end-users don't know the contents of the file, would a sequential walk through the file be acceptable? (given that humans don't yet live for 235 years, it's unlikely anyone would be offended by a repeat)
    – Jeff Schaller
    Jun 17, 2016 at 0:49

4 Answers 4

2

A somewhat different solution: Scramble the lines in your text files with a cron job every day. Your script then picks the first line.

Cron job (requires a sort that can "sort" data randomly with -R):

0 0 * * * sort -R -o wotd_data.txt wotd_data.txt

Or, if your cron understands @daily (see man 5 crontab):

@daily sort -R -o wotd_data.txt wotd_data.txt

Script:

head -n 1 wotd_data.txt

Full paths to wotd_data.txt are obviously required.

2

Modulus math!

$ today=`date +%Y%m%d`
$ echo $(( today % 86036 + 1 ))
28193

... with possibly the 86036 instead being wc -l that file in the event the length of that file changes ...

5
  • That's a decent solution however it would mean that I'd be starting at line 28193 then I would consecutively go through each entry in the dictionary until I hit the end. This isn't really what I want as it isn't really random in any way
    – CS Student
    Jun 16, 2016 at 18:38
  • If you wanted random, then why not just use $RANDOM?
    – Kusalananda
    Jun 16, 2016 at 18:42
  • @Kusalananda because I want to use today's date as a unique seed so every time I run the script today I will get the same index/line number
    – CS Student
    Jun 16, 2016 at 18:49
  • Setting RANDOM to a number will seed it...
    – Kusalananda
    Jun 16, 2016 at 19:02
  • ... but obviously you can't seed RANDOM every time you run your script.... hmm. Let me think.
    – Kusalananda
    Jun 16, 2016 at 19:05
2

Use the RANDOM variable of your shell to get a random number, but seed the random number generator with today's date first (if the script hasn't been used since midnight). Then pick that line out of the file.

In other words (Bash below)...

wotd_data="wotd_data.txt"

stamp="$HOME/.wotd-stamp"
stamp_random="$HOME/.wotd-random"

date_now=$( date +"%Y%m%d" )

if [ -f "$stamp" ]; then
    date_last=$( <"$stamp" )
else
    date_last=0
fi

if [ "$date_last" != "$date_now" ]; then
    RANDOM="$date_now"
    echo "$date_now" >"$stamp"
else
    RANDOM=$( <"$stamp_random" )
fi

number=$RANDOM
echo $number >"$stamp_random"

number=$RANDOM$RANDOM  # See the "Edit #2" note below

data_length=$( wc -l <"$wotd_data" )

line=$(( 1 + ( number % data_length ) ))

sed -n "${line}p" "$wotd_data"

This uses a time-stamp file in the user's $HOME to keep track of when they last ran the command. If it wasn't today, then re-seed $RANDOM with today's date and write today's date into the file.

EDIT #1: I had to also store the last used random number since $RANDOM is local to the current shell. The seeding doesn't otherwise carry over to the next invocation of the script. I store that in a separate "random stamp" file. You might want to change this to use only one file for both the date of the last invocation and the last used random number.

EDIT #2: Can anyone spot the problem with my original code? Well, $RANDOM will never be more than 32767 (16 bits), and the file was said to have more lines than this. This means that maybe using $RANDOM alone may not be a good idea. This is why I resort to simply concatenating $RANDOM with itself, generating a longer random number. The seeding and the "random stamp" file is not affected by this.

EDIT #3: Just noticed that the OP requested "the same line every time the script is run on a particular day" (in a comment that my brain didn't grok late last night), which is exactly what my script does not do (it gives the same sequence of lines every day). I'm leaving my solution in here anyway in case it helps someone else with similar issues of preserving the state of $RANDOM between script invocations.

1

If a sequential walk through the file is not acceptable, then here's my suggestion; it will take more than 230 years to cover every line in the file, and will repeat words back-to-back at some yet-undetermined point in the future. I made it a bit more flexible by computing the number of lines (word definitions) in the 'words' file at run-time, so if you ever add or remove lines from the file, the script will adjust accordingly.

#!/usr/bin/env bash

# number of days since 1970-01-01 00:00:00
seed=$(( ($(date +%s) / 86400) ))
# initialize RNG to this seed
RANDOM=$seed
nwords=$(wc -l < words)
# generate two random numbers (0 .. 32767), multiply them,
# modulo nwords, plus 1 -> range 1..86036
r=$(( ((RANDOM * RANDOM) % nwords) + 1 ))
# print that line from the 'words' file
sed -n "${r}p" words
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .