Last Updated: August 25, 2016
·
1.362K
· robjens

ZSH family of named and anon shell functions

One of the better kept secrets of the z-shell script language is a concept known as 'family of functions' and what they can do. Another suitable description that fits the bill might be "bracket expansion on named function"-definitions.

I use this often in conjunction with a simple naming convention to scaffold out groups of functions, sibling fn's, with minimum of effort. I'll devote another separate topic to another z-shell packed powerhouse: zparseopts - which is the zsh counterpart of getopt(s).

Let's start simple:

function {moe,larry,curly} { print $0 }

Notice that zsh doesn't mind the missing ; semi-colon for functional one-liners. The zsh function name expansion only works when the function keyword is used so do not omit it (I sometimes forget this since bash nor zsh requires it for regular fns).

Let's try and make a bit more useful functions this time:

real_script=$(readlink -f $0)

function {die,reload,rnd}
{    
    case "$0" in

        die) kill -INT $$ ;;
        rnd) coll=("$@"); print $coll[$RANDOM%$#coll+1] ;;
        reload) source $real_script ;;

    esac
} 

This makes for a simple but effective way to easily create more compact functions stacking your common one-liners together. As you grow more comfortable using the zsh archaic notation and have referenced the manual a 100th time you should be able to surpass it with more eloquent structures.

Until now, I've limited myself to using single word function names but there is no need to limit ourselves to that. Next I'll show you a simple, yet superfluous function family in which we employ a naming convention or schema/pattern of namespace-module-function.

function namespace-module{-functionA,-functionB,-functionC,-functionD}
{
    case "$(print $0 | cut -d '-' -f3)" in

        functionA) touch a_file_{1,2,3,4,5,10,20,50,99}.txt ;;
        functionB) namespace-module-functionA ;;
        functionC) files=(*.txt); print $files[$RANDOM%$#files+1] ;;
        functionD) rm a_file_<0-100>.txt ;;

    esac
 }

These sibling functions all have names which begin with namespace-module- as literal parts provided. As a separator we've used the hyphen - but we might have also chosen for the underscore _ although these are additional keys (and they add up) of pressing the shift button first to get the underscore.

Earlier, I mentioned they were a bit superfluous, what did I mean by that? Well, take a look at the function name bracket expansion: ..ule{-functionA,-functionB...}. Notice the pattern? -function is repeated every time. So in this case, we might had just as well moved those outside the curly braces to not have repeated them: namespace-module-function{a,b,c,d} would have been sufficiently and most succinct form.

A nice bonus is that siblings may call each other regardless of order of appearance. So the functionB) is where you notice it actually serves as a alias of functionA called with no arguments passed.

The final .sh-owdown will demonstrate the bracket expansion is not limited to single occurrences only and we can create further internal complexities by adding nested functions too, both anonymous and named sub functions.

The first word of the outer-most function name returns the script file base name with path prefix and extension removed. If the file is named e.g. /some/path/to/myfile.zsh the prefix for the functions would become: myfile-bar-a, myfile-bar-b, ...myfile-foo-a and so on.

function "$0:r:t"-{bar,foo}-{a,b,c}
{
    function { print "We are " $0 }

    {
        function $0_{create,read,update,delete}
        {
            print "I am " $0
        }

        $0_create
        print "Yet we are " $0

     } always {

        unfunction -m "$0_*"
    }
}

You'd probably want to know, if you think them out a bit, to avoid ambiguity on first characters. Then if you are on the zle (the prompt) and you've got these in memory either autoloaded empty bodies or full functions, to type e.g. m-b-b to get myfile-bar-b expanded on <Tab>

Finally, they're also nestable!

function "$0:r:t"-{design-{frontend,admin},code-{local,community,core},etc,skin}
{

}

Creates names like myfile-design-frontend, myfile-design-admin, myfile-code-local and so on.

Feedback is always welcome. Enjoy :)