h1

Just for fun: map as ‘higher-order function’ in bash

March 8, 2008

Defined recursively, of course:

map () { 
  if [ $# -le 1 ]; then 
    return 
  else 
    local f=$1 
    local x=$2 
    shift 2 
    local xs=$@ 

    $f $x 

    map "$f" $xs 
  fi 
}

I’m not going to explain everything, but note the ‘local’ commands to keep everything in function scope (to prevent strange bugs) and the quotes around $f in the recursive call to prevent a ‘function call’ of multiple words from being split.

Now you can do some completely nonsensical things, for which you really don’t need map, such as:

$ map touch aap noot mies

But also slightly more useful things such as

$ map "echo foo" aap noot mies 
foo aap 
foo noot 
foo mies 

$ map "echo file:" `ls` 
file: aap 
file: noot 
file: mies

To open up some more possibilities, I also defined a ‘rota’ function, with rotates the arguments of a command such that the last comes first.

rota () { 
  local f=$1 
  shift 
  local args=($@) 
  local idx=$(($#-1)) 
  local last=${args[$idx]} 
  args[$idx]= 

  $f $last ${args[@]} 
}

I’m using a array (yes, bash has arrays) to easily get the last element, as you can see 😉

So, how about…

$ map "rota mv /tmp" aap noot mies

(Move the aap, noot and mies file to /tmp)

There are probably better (and more useful) examples of it’s usage, but you hopefully get the gist of it.

This code is just for fun (I don’t expect I’m ever going to use it, even), so it’s nowhere near being robust. There are a lot of (possible) bugs concerning spaces and undetected grouping of words (with double quotes). Use at your own risk 😉

Advertisements

3 comments

  1. You could avoid whitespace issues in arguments if you got rid of $xs, which isn’t really needed, and just pass “$@” (with the double quotes) instead within the recursion. “$@” is special in sh in that it preserves whitespace. For example, try this with your implementation:

    map echo 1 2 ‘3 4’

    you get the numbers 1-4 each printed on a separate line, which means the whitespace within the argument ‘3 4’ is lost. If however you make the change I suggested, you get:

    1
    2
    3 4

    which is correct.

    Also, see the BSD apply(1) command:

    NAME
    apply — apply a command to a set of arguments

    SYNOPSIS
    apply [-a c] [-d] [-#] command argument …

    DESCRIPTION
    The apply utility runs the named command on each argument argument in turn.

    It’s available on Mac OS X, if you have that.


  2. Hey, that’s cool. Thanks 🙂


  3. Hey, this is a great tip. I definitely prefer using a “functorial” style of programming. It’s nice to see the functorial primitive operator is definable in with Bash. I will be using this a lot!



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: