Bash Completion (Part 1?) and Internet Radio

Recently, I've become a fan of MPD - "Music Player Daemon," which I have running on a Pi on my local network. I use mpc to control it from my desktop. Starting a radio station requires several steps, so I wrapped them in a script. This script is a bit brute force, in that it wipes the MPD queue, but mostly this is about Bash completion and for now this method works for me:

#!/usr/bin/env bash
# filename: ~/bin/radio
radioHost="pib.local"
export MPD_HOST="${radioHost}"

declare -A stations=(
    ["anima"]="http://yp.shoutcast.com/sbin/tunein-station.xspf?id=1679935"
    ["cbc-1to"]="https://cbcliveradio-lh.akamaihd.net/i/CBCR1_TOR@118420/master.m3u8"
    ["cbc-musiceast"]="https://cbcliveradio2-lh.akamaihd.net/i/CBCR2_TOR@382863/master.m3u8"
    ["jazz24"]="https://live.wostreaming.net/playlist/ppm-jazz24mp3-ibc1.m3u"
    ["soma-defcon"]="https://somafm.com/defcon.pls" # DEF CON year round, music for hacking
    ["soma-ds1"]="https://somafm.com/deepspaceone.pls"
    ["soma-dubstep"]="https://somafm.com/dubstep.pls"
    ["soma-dz"]="https://somafm.com/dronezone.pls"
    ["soma-goa"]="https://somafm.com/suburbsofgoa.pls"
    ["soma-groove"]="https://somafm.com/groovesalad.pls"
    ["soma-grooveclassic"]="https://somafm.com/gsclassic.pls"
    ["soma-n5md"]="https://somafm.com/n5md.pls"
    ["soma-poptron"]="https://somafm.com/poptron.pls"
    ["soma-secretagent"]="https://somafm.com/secretagent.pls"
    ["soma-thistle"]="https://somafm.com/thistle.pls" # Celtic
    ["soma-u80s"]="https://somafm.com/u80s.pls" # Underground 80s
)

help() {
    cat << EOF
$(basename "${0}") [-h] [-s] <station-name>

Use 'mpc' to clear the MPD playlist and start playing the named station.

-h            show this help and exit
-s            show current station and track (display varies by station)

MPD host is set (in this script!) to "${MPD_HOST}"

Available stations:

EOF

    # send them through 'sort' as this doesn't list them in the order above?
    for stationName in "${!stations[@]}"
    do
        echo "    ${stationName}"
    done
}

if [ $# -eq 0 ]
then
    help
    exit 0
fi

while getopts :hs opt ; do
    case $opt in
        h)  help
            exit 0
            ;;
        s)  mpc current
            exit 0
            ;;
        *)  echo "unknown option ... "
            help
            exit 1
            ;;
    esac
done

mpc clear
mpc load "${stations["${1}"]}"
mpc play

A big thank you to Soma FM who have been providing commercial-free (listener-supported) music for ... a couple decades now. And doing it well.

But let's get on to the Bash completion portion of this discussion. I have to admit I find Bash completion somewhat daunting: with scripts that take multiple options, it can be very complicated to set up and I haven't got my head around it yet. But for simple commands, like this one, it can be fairly easy.

If you run radio -h you get the help output:

radio [-h] [-s] <station-name>

Use 'mpc' to clear the MPD playlist and start playing the named station.

-h            show this help and exit
-s            show current station and track (display varies by station)   |2015

MPD host is set (in this script!) to "pib.local"

Available stations:

    soma-groove
    soma-goa
    soma-dubstep
    soma-dz
    soma-secretagent
    soma-poptron
    jazz24
    soma-ds1
    cbc-musiceast
    anima
    soma-thistle
    soma-n5md
    soma-grooveclassic
    cbc-1to
    soma-defcon
    soma-u80s

To start a station, you would run radio soma-groove. For Bash completion to work, we would want radio <TAB><TAB> to show us potential radio station matches, like the list of "Available stations." Bash completion has an option for word lists: this works great for me for a couple reasons, the first being that it's easy to understand, the second being that after decades of using Linux, if there's one thing I'm good at, it's producing lists at the command line. And this is an easy one.

# ~/.bashcompletion.d/radio
complete -W "$(radio -h | sed -e '1,/Available stations:/d')" radio

That's it: one line, to get this to work exactly as expected. Your distro may or may not auto-load stuff in the ~/.bashcompletion.d/ folder: if it doesn't, the single command complete -W "$(radio -h | sed -e '1,/Available stations:/d')" radio is a standard Bash command that you can run at the command line to test and/or add to your ~/.bashrc to make it permanent.

Let's look at what that's doing: radio -h | sed -e '1,/Available stations:/d' tells sed to search the output of radio -h for "Available stations:" and then delete line 1 through the matching line. What that leaves us with is a list of station names: this is exactly what we need for our completions.

Looking up help for the complete command is a bit odd: one way is help complete which is the official Bash way, but the help available is short and incomplete. This led me to find an interesting inconsistency between Debian and Fedora: if you try man complete on Debian, you get "No manual entry for complete" - which, strictly speaking, is true. But Fedora handles this better. Try man complete on Fedora, and it opens the BASH_BUILTINS man page. You'll have to search a bit to find the section on "complete", but it's worth it: there's a lot more information. On Debian, you can type man builtins and do the same search.

What we're using here is complete's -W option, which takes as input a "wordlist." This is by far the easiest way to load up Bash completion, and probably the commonest. I hope to get into the other methods soon.