#Bash? #Metaprogramming? WTF? Turns out, bash's everything-is-a-string philosophy makes it pretty awesome for metaprogramming.

I've been messing with Conky and writing a script for getting the currently playing music with it. The first version of the script looked like this.

what_is_running() {
  if [ -n "$(pgrep pianobar)" ]; then
    echo pianobar
  elif [ -n "$(pgrep mpd)" ] && [ -n "$(mpc current)" ]; then
    echo mpd
  else
    echo none
  fi
}

trim() {
  cat /dev/stdin | sed 's/^ *//g' | sed 's/ *$//g'
}

running=$(what_is_running)

if [ "$running" = "none" ]; then
  echo "Nothing playing."
elif [ "$running" = "pianobar" ]; then
  title=$(cat ~/.config/pianobar/nowplaying | cut -f1 -d:)
  artist=$(cat ~/.config/pianobar/nowplaying | cut -f2 -d:)
  echo "Now Playing: $title by $artist"
elif [ "$running" = "mpd" ]; then
  artist=$(mpc current | cut -f1 -d- | trim)
  title=$(mpc current | cut -f2 -d- | trim)
  echo "Now Playing: $title by $artist"
fi

It's pretty ugly. Very repetitive. First in the what_is_running function. All the process searching is hardcoded in. Next we have all those if...elif clauses. Those are awful and very hard to read.

Now, when we introduce metaprogramming into the mix.

services=(pianobar mpd)

what_is_running() {
  for service in ${services[@]}; do
    if [ -n "$(pgrep $service)" ]; then
      echo $service
      return
    fi
  done
  echo none
}

trim() {
  cat /dev/stdin | sed 's/^ *//g' | sed 's/ *$//g'
}

none_fn() {
  echo "Nothing playing."
}

pianobar_fn() {
  title=$(cat ~/.config/pianobar/nowplaying | cut -f1 -d:)
  artist=$(cat ~/.config/pianobar/nowplaying | cut -f2 -d:)
  echo "Now Playing: $title by $artist"
}

mpd_fn() {
  if [ -n "$(mpc current)" ]; then
    artist=$(mpc current | cut -f1 -d- | trim)
    title=$(mpc current | cut -f2 -d- | trim)
    echo "Now Playing: $title by $artist"
  else
    none_fn
  fi
}

running=$(what_is_running)
${running}_fn

Much, much, much nicer, isn't it? The first difference is that our hardcoded process searching is replaced with a services array and a for loop. Then we have all of our music outputting routines done in functions. But here is where it gets a little confusing.

running=$(what_is_running)
${running}_fn

WTF? You just have a variable on its own? How the hell is that supposed to work? Well, it does! This is where bash's magic comes into play. With bash, variables are directly expanded into strings. And since ${running} is directly expanded, that line becomes mpd_fn or pianboar_fn or none_fn at runtime, and so instead of throwing an error like any other language would do, bash just calls the function!

Neat, huh? And you thought Ruby and Lisp were the only languages with metaprogramming!