on Little Functions

Table of Contents

1 Introduction

2 Little functions

How about some "lessons learned" for the little functions which pop up in routine use? Today, 4/16/14, one such was efbdy, better called fun_bdy, either of which upgrades the popular fbdy Function BoDY. The problem with fbdy, as it stands, is it only shows the body of enabled functions. An enabled function is one which is available by the usual:

. {shelllibrary}  # or 
source {shelllibrary}

mechanism.

Not every function is available at all times. That's the purpose of shell libraries. In a perfect world, functions are only required in the proper context. In *nix land, context is supplied by the working directory, the time of day, week, month, and a discernable state of files and devices. Until that day, we need to recognize not all functions are enabled at all times, or all places.

What I'd like to see is functions enabled (and disabled) by directory, while a sufficient number are globally available.

2.1 f2file   ref

The f2file function replaces the now-obsolete funFbdy

Rather than get fancy with sub-shells, the idea occurred to use the file system, rather than the process table. Just collect the functions in an appropriately named sub-directory in files sharing the function name.

The two growing problems:

  1. where does a function belong? which library? this leads to a function appearing in multiple libraries. an ancillary questions:
    • "which one is being used?",
    • "are other copies identical, equivalent?"
  2. having just discovered a dependency in this directory, how do I quickly fix it.

So, f2file may be run against the libraries containing an alleged duplicate function, the copies may be compared, the using functions may be identified and compared, and a portable (or not) fix may be quickly prepared. Here's f2file:

function f2file
{ 
    report_nonfile ${1:-MissingFirstArgument} && return 1;  # (report)
    set -- $1 .$(basename $1);      # (set)
    trace_call $*;                  # (trace)
    rm -fr $2; mkdir $2;            # (clean)
    cat $1 | awk -v dir=$2/ '
    BEGIN { funfile = "/dev/null"
            stderr  = ".f2file.err"
            fmta    = "NR, n, f, b, t: "
            fmtb    = "NR, 1, 2, t, funfile: "
            fmtc    = "%12s %4d %s\n"
          }
            { f = $0 ~ /^ *function */     # (ksh)
              n = $2 ~ /^[a-zA-Z0-9_-]*$/  # (bash)
              b = $2 ~ /^\(\)$/
              r =  ((b)? $1: f? $2 : r)    # (name)
              t = dir r
              print fmta, NR, n, f, b, t > stderr 
            }
    b       { fclose funfile; funfile = dir $1 }

    f && n  { 
              fclose funfile               # (close)
              funfile = t
              print fmtb, NR, $1, $2, t, funfile > stderr
            }
            {
              printf fmtc, funfile, NR, $0 > ".f2file.out"
              print > funfile              # (written)
            }
'
}

F2file accounts for both ksh and bash format function bodies.

It also demonstrates a function style I've developed in the last four months (since Jan. 2015):

  1. a series of report functions, which if true, report on a failed assertion, in this case nonfile, and directly return an error
  2. zero or more argument conditioning statements. Here the set idiom is used to set the positional parameters: $1, $2, … in this case the first argument is unchanged, and the second is set to the basename of the file, prefaced with a period, e.g. .libname.
  3. and after the assertion checking and argument conditioning, then a call to trace. This is almost mandatory. Why would you not want to trace function execution.

The .libname directory is cleaned out. The input library is run thru the body of the awk program. At some point in the development it occurred to me: "hey, why fuss with individual functions? you never know which ones in this library might connect with which ones in another library. so, why not collect all of them from this one?" What were learning in our head-long rush to the singularity is "whip that computer into doing your bidding before it takes your whole job away."

So the variables: f is true for ksh functions, n for bash; r holds the function name, and t is the path-name to the file.

When a function is found ( b or f and n ), the existing funfile is closed (awk may exhaust the table if > 20 files are left open). Then some diagnostic information is put on the stderr and the current line is written into the funfile

So, an application would be:

$ set -- thislib thatlib
$ f2file $(which $1)
$ f2file $(which $2)
$ comm -12 <(cd .$1; ls) <(cd .$2; ls)  # (comm)
$ set -- $* $(!!)  # re-use the last command
$ echo $*          # lists the libraries and common functions

You can see from this approach, my use of the set -- idiom. In this instance, the commands may be a throw-away, or easily lead to a new function.

For instance, in this case, the last two lines merely echo the information you already have, and you might like to have the option of identifying the other two sets: functions in either file, not in both. The flags to the comm command (1, 2, 3) exclude lines from the first (1), second(2), or both(3) files. In the example then we exclude lines unique to the first and second files, and only return the lines (function names in this case) which are in common.

So, as an exercise for the student, write the function f2libs, which:

  1. takes an optional third argument, defaulting to 12,
  2. satisfying comm usage, e.g. 1, 2, 3, 12, 13, 23,
  3. which returns the comm results by comparing the function names from the two libraries as arguments.

It should:

  1. verify the first two arguments are libraries,
  2. verify the optional third argument is valid for comm,

It may (or may not):

  1. insist on the user supplying minus sign,
  2. put the optional argument as the first argument – this is challenging,
  3. changing the default flags to blank (i.e. no filtering, to output all three columns)

2.2 fun_fbdy   obsolete

Enter fun_bdy. It uses a sub-shell to source the necessary library, then call fbdy on the function arguments. The sub-shell guarantees the definitions in the library don't show up in the current shell. The functions in the library are visible to the user just during the sub-shell. Their contents of the function arguments are displayed.

function fun_bdy
{ 
    set -- ${*:-$(myname)};            # (myname)
    report_notfunction $1 && return 1; # (notfunction)
    set -- $(whf $1) $*;               # (whf)
    report_notfile $1 && return 2;     
    trace_call $*;
    ( . $1;                            # (source)
    shift;                             # (remaining)
    fbdy $* )
}

Calling fun_bdy with no arguments:

  • The first call to myname sets the first postional parameter to 'fun_bdy', the value of 'myname'
  • The call to report_ notfunction is defensive, in case the arugment is not a function, and can't find it's source file.
  • The call to whf assigns the library name to $1, $2 holding 'fun_bdy'
  • Then source the file, which loads the official, or currently saved copy of the function,
  • The shift moves out the file name and leaves the remaining arguments, presumably all functions, whose bodies are displayed with the call to fbdy

And here is the stderr from that call.

TRACE whf       @ fun_bdy 1  ( fun_bdy )
TRACE funclocn  @ whf 1  ( ./.funclocn.txt )
TRACE xwh       @ whf 6  ( 105 ... ./cblib ./fixlib ./indexlib ... )
TRACE fun_bdy   @ COMMANDLINE 2  ( /users/applemcg/dropbox/git/backash/bin/funlib fun_bdy )
fun_onetimer fun_firsttime fun_copyright
TRACE fun_type  @ fun_init 3  ( a b c )
USAGE fun_type [[type, file] file]
where type is [function], alias, builtin, file, keyword, loner

2.3 dotty

Dotty? just like the ladies name, pronounced DAH – tee. What does it do? what's long been needed in the world of hierarchical directories: It answers the question: What's the dotty (relative) path to a directory?

Here's the source code. rooted defends against never finding the argument. Ignore quiets the pushing and popping; the final popd on failure is all the warning you'll get.

For fun, I'll make bin the default argument, since I'm often interested in a bitmeld directory.

dotty () 
{ 
    set -- ${1:-bin}
    trace_call $*
    d=.;
    while true; do
        ignore pushd $d;
        rooted && { 
            popd;
            return
        };
        ignore popd;
        [[ -d $d/$1 ]] && { 
            echo $d/$1;
            return
        };
        d=$d/..;
    done
}
rooted	() { [[ $PWD == "/" ]]; }
ignore	() { $@ > /dev/null; }

I've used dotty in lazy moments, when I waht to include or list a file when in know the relative path from some node, but I don't know (or care, for that matter), how deeply i'm burried in the depth of a directory. e.g here are some testcases for mymarkdown

test_0 () { test_x $(dotty lib/mark)           requirements.mkd $*; }
test_1 () { test_x $(dotty commonplace/online) resources.mkd $*; }
test_c () { test_x $(dotty commonplace/book)   -c cook.mkd; }

These examples are obsolescent, since I'm moving the .mkd files into .org files. The dotty function is still useful, but I've seen much in the org documentation on exactly the problem dotty was set to solve: "now, just where is that file I want to link to?"

3 Quick commandline to function

This one's so quick, I don't know why I haven't recorded it before. Use the command history to turn a command into a function.

The simple way to turn a just-executed command into a function:

functionName () { !!; }

where functionName is the name to give the command. In general, it may be a few commands later before you decide on the command you wish to capture as a function. Here's a piece of history.

  681  functions $(which cmdlib)
  682  functions $(which cmdlib) | awk '!/_time_/'
  683  functions $(which cmdlib) | awk '!/_time_/ || !just' 
  684  functions $(which cmdlib) | awk '!/_time_/ || !just; { just = /_time_/ } ' 
  ...
$ justOneTime () { !684; } (justOneTime)
justOneTime () { functions $(which cmdlib) | awk '!/_time_/ || !just; { just = /_time_/ } ' ; }

The functions returns the names of the functions in a file or files, and uniquely in the order encountered. When saving functions to a library:

dbsave somelib funa funb ...

the command creates marker functions with a time-stamp. in this case, some_time_YYYYMMDD_hhmmss, the idea being, at some point I'll have a complete record of the creation and last modification times for every function.

So, when listing the functions in which cmdlib (681) there were any number of consecutive time functions. The function, which I've called justOneTime was created in successive steps.

  1. list all the functions,
  2. then just those without time, testing awk's negation operation on an RE (I'd never tried this explicitly before).
  3. add the just flag. Have I just printed a time function?
  4. finally, assign the just value.

After other testing, to create the function justOneTime:

  • type the name,
  • a pair of parentheses,
  • the left curly brace, and here's the history trick,
  • type an exclamation followed by the number from the history.
  • Don't forget the one-line trailing semicolon, and
  • conclude with the right curly brace, and you have a function.

To make this completely general, we need to replace the specific:

$(which cmdlib) 

with the generic "$*" for a variable number of file arguments. Here's the sequence I use to do that:

fbdy justOneTime | sed 's/.which cmdlib./*/' > .x
. ./.x           # source's the file, updating the function
dbsave cmdlib justOneTime  

And it's safely tucked back in the cmdlib

4 Other papers

Author: Marty

Created: 2016-02-20 Sat 15:50

Emacs 24.4.1 (Org mode 8.2.10)

Validate