Work the Shell - Making a <emphasis>Mad Libs</emphasis> Generator
My son is at the age when he's decomposing sentences, diagramming them and learning about the parts of speech. Me? I couldn't differentiate between an adverb and an adjective if a wet, smelly red ball smacked me in the head. That's why I have an editor!
There are games for everything, however, and one of the best games for learning the parts of speech is a simple one that's been around since I was a kid: Mad Libs. You know what I'm talking about, it takes simple sentences like: “When my dog is happy, he jumps and barks, his tail wagging a mile a minute.” and transforms them into: “When my [ noun ] is [ adjective ], he [ verb ] and [ verb ], his [ noun ] wagging a mile a [ noun ].”
The question is, can we write a shell script that can perform this sort of transformation? The answer, of course, is yes.
There are two challenges with this project: figuring out which words to replace with their parts of speech and figuring out the part of speech of a given word. Let's tackle these in reverse order.
It turns out that a number of different Web sites let you look up a word and offer its definition and part of speech. The one I use for this exercise is from Princeton, because it's fast, easy to parse and easy to submit queries.
To look up the part of speech of, say, “dog”, the URL to invoke is simply wordnetweb.princeton.edu/perl/webwn?s=dog.
The result highlights the part of speech as an h3 line, so isolating that element is a breeze:
curl --silent "lookup$word" | grep '<h3>'
This particular word demonstrates one of the nuances of the problem: many words have more than one part of speech, demonstrated by the difference between a pet dog and someone who is dogging your every footstep. Sure enough, the result:
<h3>Noun</h3> </ul><h3>Verb</h3>
For simplicity's sake, let's just take the first match, easily done by adding | head -1 to the pipe. Next, let's drop it all into lowercase and strip out the HTML:
| tr '[:upper:]' '[:lower:]' | sed 's/<h3>//;s/<\/h3>//'
Both of these are worth a bit of explanation. You might well have seen tr '[A-Z]' '[a-z]' as the more common way to transliterate uppercase to lowercase, and that works just fine, if you're working in English. Using the character sets “:upper:” and “:lower:” is a more portable alternative that's preferred.
The sed command also lets you specify more than one command argument to apply by simply separating them with a semicolon. What we have here is a substitution of <h3> to a null string (for example, removing it), followed by the same thing for </h3>.
That's all we need to get the part of speech. For example:
$ lookup="http://wordnetweb.princeton.edu/perl/webwn?s=" $ word="happy" $ curl --silent "$lookup$word" | grep '<h3>' | tr '[:upper:]' '[:lower:]' | sed 's/<h3>//;s/<\/h3>//' adjective
And, the hard part's done!
For this article, let's use a replacement density constant to figure out whether any given word should be replaced. The higher the density, the more likely a given word in the input stream will be replaced by its part of speech.
This is lazy and not a great solution, because it can match “is” or “the” just as easily as “dog” or “tail”, but let's go with it for now to get a sense of how it'll all fit together. We'll come back to it and improve the sophistication of the selection criteria later. With me? Good!
For a given word, deciding whether to substitute its part of speech can be calculated as follows, assuming we have a variable called density that has a nonzero integer value:
if [ $(( $RANDOM % $density )) = 1 ] ; then
$RANDOM is one of those cool magic variables in the Bourne shell that has a different value each time you reference it—handy!
Let's put these together and see what we get. We'll use an initial density of 5, which theoretically should mean that if we have a properly random $RANDOM, each word should have a 1:5 chance of being replaced.
The script needs to read the input word by word, testing each word as it goes. This can be done easily with the following loop structure, assuming that the text input comes from stdin:
while read sentence ; do for word in $sentence ; do
Now, we add the random conditional and have a skeleton ready to test:
while read sentence ; do
for word in $sentence ; do
if [ $(( $RANDOM % $density )) -eq 1 ] ; then
echo "(($word))"
else
echo $word
fi
done
done
You can see that at this stage we're going to output the words we're planning on replacing with “(())”. Here's a quick test:
echo this is a test mad-lib input | sh make-madlib.sh this is ((a)) test ((mad-lib)) input
One tiny tweak before I wrap it up for the month—how do we get the words to appear on the same line? It's easy. Remember that each of the code loops is essentially a little script of its own, so this task can be accomplished by adding four characters to the very end of the outermost loop:
done done | fmt
That's all you have to do—add the |fmt after the second done statement. Now when it's run:
echo this is a test mad-lib input | sh make-madlib.sh this is a ((test)) ((mad-lib)) input
Next month, we'll add the part of speech lookup code into the conditional and then spend some time exploring a more sophisticated word choice algorithm. Clearly, random isn't as beneficial.
Dave Taylor has been hacking shell scripts for a really long time, 30 years. He's the author of the popular Wicked Cool Shell Scripts and can be found on Twitter as @DaveTaylor and more generally at www.DaveTaylorOnline.com.
Dave Taylor has been hacking shell scripts for over thirty years. Really. He's the author of the popular "Wicked Cool Shell Scripts" and can be found on Twitter as @DaveTaylor and more generally at www.DaveTaylorOnline.com.
Today’s modular x86 servers are compute-centric, designed as a least common denominator to support a wide range of IT workloads. Those generic, virtualized IT workloads have much different resource optimization requirements than hyperscale and cloud applications. They have resulted in a “one size fits all” enterprise IT architecture that is not optimized for a specific set of IT workloads, and especially not emerging hyperscale workloads, such as web applications, big data, and object storage. In this report, you will learn how shifting the focus from traditional compute-centric IT architectures to an innovative disaggregated fabric-based architecture can optimize and scale your data center.
Sponsored by AMD
Built-in forensics, incident response, and security with Red Hat Enterprise Linux 6
Every security policy provides guidance and requirements for ensuring adequate protection of information and data, as well as high-level technical and administrative security requirements for a system in a given environment. Traditionally, providing security for a system focuses on the confidentiality of the information on it. However, protecting the data integrity and system and data availability is just as important. For example, when processing United States intelligence information, there are three attributes that require protection: confidentiality, integrity, and availability.
Learn more about catching the bad guy in this free white paper.
Sponsored by DLT Solutions
| Making Linux and Android Get Along (It's Not as Hard as It Sounds) | May 16, 2013 |
| Drupal Is a Framework: Why Everyone Needs to Understand This | May 15, 2013 |
| Home, My Backup Data Center | May 13, 2013 |
| Non-Linux FOSS: Seashore | May 10, 2013 |
| Trying to Tame the Tablet | May 08, 2013 |
| Dart: a New Web Programming Experience | May 07, 2013 |
- RSS Feeds
- New Products
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- Drupal Is a Framework: Why Everyone Needs to Understand This
- Home, My Backup Data Center
- A Topic for Discussion - Open Source Feature-Richness?
- What's the tweeting protocol?
- Dart: a New Web Programming Experience
- Developer Poll
- Trying to Tame the Tablet
Enter to Win an Adafruit Prototyping Pi Plate Kit for Raspberry Pi

It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Prototyping Pi Plate Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- Next winner announced on 5-21-13!
Free Webinar: Linux Backup and Recovery
Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.
In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.




2 hours 41 min ago
5 hours 14 min ago
6 hours 31 min ago
7 hours 6 min ago
7 hours 28 min ago
12 hours 17 min ago
13 hours 4 min ago
14 hours 38 min ago
16 hours 14 min ago
18 hours 12 min ago