Bash Lambda Expressions

While strictly not a true lambda expression, the Bash function:

1
2
3
4
5
6
7
# lambda 'anonymous function' arg1 ... argn
function lambda() {
  _f=${1}; shift;
  function _l {
      eval "${_f}";
  }; _l ${*} ; unset _l;
}

lets you use something that works similar to lambda expressions in your Bash scripts.

As you can see, it is a bit like an eval on steroids, because of the function wrapper, it lets you use positional parameters which aren’t available in a plain eval and all the Bash power that stems from those.

Consider the following awk construct that I tend to use quite often to print the last field of some input file: awk '{print $(NR)}' < input.txt. Say you need this in a while loop, like so:

1
2
3
4
while read line; do
  bar=$(awk '{ print $(NR) }' <<< ${line})
  # ...
done

Using lambda() you can avoid calling cut or awk. For instance:

1
2
3
while read line; do 
  bar=$(lambda 'eval echo \$${#}' ${line})
done

does the same thing, but now in pure Bash and saves you from fork()ing awk in a loop..

How does this work? Let’s take the line lambda 'eval echo \$${#}' foo bar baz, this creates a pseudo-anonymous Bash function with the body eval echo \$${#} and the arguments foo, bar and baz. ${#} Evaluates to the number of arguments this function was called with, so 3 in this case. \$ Protects the first $ from the shell so eval gets to evaluate it. Recall that the shell already evaluated ${#} to 3, therefore eval will evaulate echo $3, which prints the last argument, which for the sake of this example is the same as the last field a line.

So with the same ease you can for instance print the but-last field like lambda 'eval echo \${$((${#}-1))}' foo bar baz boz:

turing:~ hessch$ lambda 'eval echo \${$((${#}-1))}' foo bar baz boz
baz

Last but not least, I thought to test the speed difference between lambda() and doing something similar by spawning a shell, the results speak for themselves:

turing:~ hessch$ time for i in {1..1000}; do sh -c 'eval echo \$${#}' foo bar baz > /dev/null; done

real    0m3.078s
user    0m0.946s
sys 0m2.130s

turing:~ hessch$ time for i in {1..1000}; do lambda  'eval echo \$${#}' foo bar baz > /dev/null; done

real    0m0.172s
user    0m0.153s
sys 0m0.019s

Comments