Exercise: The Playlist Shuffler

Finish the Playlist Shuffler, an application in two files: write the functions in its library that load the playlist into arrays, count it, add it up, answer whether a mood is there, pick a song at random and total each mood's minutes, then let a check script run it for real.

Journeyman · Unix · Linux · Updated

Your road-trip playlist deserves better than the same order every time. In this exercise you finish the Playlist Shuffler, a small application in two files, the way real tools are built: shuffle.sh is the app you run, and lib/playlist.sh is its library of functions. The shuffle itself is written for you. You write the functions around it: one that loads the playlist into arrays, one that counts it, one that adds it up, one that answers whether a mood is there, one that picks a song at random and one that totals each mood's minutes. A check script runs your shuffler for real, with a playlist of its own, and tells you which jobs are done.

Before you start

You need a terminal: the Terminal app on a Mac, or Ubuntu on Linux or in WSL on Windows. The check runs with bats, a small program that runs checks written for bash. If bats --version doesn't print a version number yet, install it:

sudo apt install bats       # Ubuntu, and Ubuntu in WSL
brew install bats-core      # a Mac, with Homebrew

sudo asks for your password, the one you use to sign in; nothing shows on the screen while you type it. On a Mac, check that bash --version says version 5, since the shuffler uses an associative array (Associative Arrays: Lists with Names).

Get the exercise code

Open this lesson's GitHub repository, github.com/ask-codey/exercise-bash-the-playlist-shuffler, also linked from the Exercise box beside the lesson. Click Code, then Download ZIP, and unzip it into your terminal-practice folder. Or clone it with Git. On Windows, put it in your Ubuntu home folder, not under /mnt/c, so chmod works on it.

The folder holds:

  • shuffle.sh: the app. It loads the library, reads the playlist and decides what to play. Job 1 is in it, and the shuffle is done for you.
  • lib/playlist.sh: the library, functions only, with an example done and jobs 2 to 7 to finish.
  • playlist.txt: the songs, one a line: the mood, the minutes, then the title.
  • check.bats: the check. You don't need to change it.

What to do

Open a terminal in the exercise folder, make the app runnable with chmod +x shuffle.sh, and open its two files with nano shuffle.sh and nano lib/playlist.sh. Each song lives in three arrays, moods, minutes and titles, at the same slot number, so ${titles[2]} and ${minutes[2]} are both about the third song. Each job is a comment above a function, and each function has a : line (a command that does nothing) for you to replace:

# 3. song_count: print how many songs there are, and nothing else.
song_count() {
  : # Your lines go here, in place of this one. (: does nothing.)
}
  1. Find home (in shuffle.sh): keep the folder the script is saved in, in here, with dirname and ${BASH_SOURCE[0]} (One Script, Several Files: source).
  2. load_songs: read the playlist line by line, add each line's three parts to the arrays with +=, and skip blank lines and lines that start with # (Arrays; Stopping Early: break, continue and Counters).
  3. song_count: print how many songs there are.
  4. total_minutes: print the minutes added up, with every variable local (Local Variables and Getting a Value Back).
  5. has_mood: answer yes or no with return, so ./shuffle.sh party can say there's nothing to play.
  6. pick_song: print one song, picked at random with $RANDOM.
  7. mood_minutes: total each mood's minutes in an associative array and print them, sorted by mood (Associative Arrays: Lists with Names).

Save, then try the shuffler after each job:

./shuffle.sh
./shuffle.sh chill
./shuffle.sh --pick
./shuffle.sh --moods

Check your work

From the exercise folder:

bats check.bats

At first most checks fail. That's expected: each job you finish turns its check into a ✓. A failing check names the job and gives a hint, for example After total_minutes ran, the script had a new variable it didn't have before: m sum. Job 4: make it local, as in local sum=0 m, so the function can't change the script's variables. The check copies your shuffler into a throwaway folder with a test playlist of its own, so your playlist.txt is never read or changed.

Hints

  • No such file or directory and command not found on every run mean job 1 isn't done: without here, the script looks for /lib/playlist.sh.
  • Read each line with while read -r mood length title. The last name gets the rest of the line, spaces and all. Don't read into minutes: that's the array's name, and a variable with the same name inside the function would hide it.
  • A function that answers with echo prints nothing else. A friendly message inside song_count would end up in the shuffler's last line.
  • has_mood loops over "${moods[@]}", returns 0 as soon as one matches, and returns 1 after the loop.
  • Moods in no particular order? Bash keeps an associative array's keys in its own order: pipe the loop that prints them to sort.

When you're done

Every check passes:

check.bats
 ✓ shuffle.sh and lib/playlist.sh are here, and bash can read them
 ✓ 1) shuffle.sh finds its library and playlist from any folder
 ✓ 2) load_songs fills the three arrays, skipping the note and the blank line
 ✓ 3) song_count prints how many songs there are
 ✓ 4) total_minutes adds up every song's minutes, with its variables kept local
 ✓ 5) has_mood answers yes for a mood in the playlist, and no for one that isn't
 ✓ 6) pick_song picks one song at random
 ✓ 7) mood_minutes adds up each mood's minutes, sorted by mood
 ✓ the whole shuffler plays every song once, numbered, in a new order each time
 ✓ the whole shuffler plays only one mood's songs when given a mood

10 tests, 0 failures

And your shuffler works like this, in its own random order:

1. Rainy Tuesday (5 min, singalong)
2. Gas Station Sunrise (4 min, upbeat)
3. Last Exit Waltz (6 min, chill)
4. Home by Dark (3 min, upbeat)
5. Highway Tea (4 min, chill)
6. Window Down (5 min, chill)
7. All the Way to the Coast (4 min, singalong)
8. Paper Lanterns (3 min, upbeat)
8 songs, 34 minutes

./shuffle.sh --moods prints chill: 15 minutes, singalong: 9 minutes and upbeat: 10 minutes. Now make it yours: put your own songs in playlist.txt, link shuffle.sh into ~/bin as shuffle (with the readlink -f line from One Script, Several Files: source), and it plays from any folder. What would you add next: --long, for songs over five minutes, or a total for a single mood?

Submit your exercise

The exercise is how you finish this lesson. When your work is done, put it on GitHub in a repo of your own, then submit that repo's address here.

  1. On GitHub, click +, then New repository. Name it (for example exercise-bash-the-playlist-shuffler), choose Public, and click Create repository. Leave out the README: your folder already has one.

  2. In a terminal in your exercise folder (Ubuntu in WSL on Windows), send your work to your new repo. If you downloaded the ZIP:

    git init -b main
    git add .
    git commit -m "My solution"
    git remote add origin https://github.com/your-name/exercise-bash-the-playlist-shuffler.git
    git push -u origin main
    

    If you cloned the starter instead, point your copy at your own repo, then commit and push:

    git remote set-url origin https://github.com/your-name/exercise-bash-the-playlist-shuffler.git
    git add .
    git commit -m "My solution"
    git push -u origin main
    
  3. Paste your repo's address, such as https://github.com/your-name/exercise-bash-the-playlist-shuffler, into Submit your exercise on this page. The site checks that the repo is public and holds your commits, and then the lesson is complete.

Changed something after you submitted? Push it, and submit again: your latest submission is the one that counts.

So what

The Playlist Shuffler is a real application in two files: a library of small, named jobs that each do one thing, keep their variables to themselves and hand back an answer, with arrays holding the data, which is the shape every bigger bash tool, spend included, is built in.

Key terms

Related