summaryrefslogtreecommitdiffstats
path: root/examples/functions/autoload.v4
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:38:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:38:56 +0000
commit6c20c8ed2cb9ab69a1a57ccb2b9b79969a808321 (patch)
treef63ce19d57fad3ac4a15bc26dbfbfa2b834111b5 /examples/functions/autoload.v4
parentInitial commit. (diff)
downloadbash-6c20c8ed2cb9ab69a1a57ccb2b9b79969a808321.tar.xz
bash-6c20c8ed2cb9ab69a1a57ccb2b9b79969a808321.zip
Adding upstream version 5.2.15.upstream/5.2.15upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--examples/functions/autoload.v4556
-rw-r--r--examples/functions/autoload.v4.t184
2 files changed, 740 insertions, 0 deletions
diff --git a/examples/functions/autoload.v4 b/examples/functions/autoload.v4
new file mode 100644
index 0000000..850c614
--- /dev/null
+++ b/examples/functions/autoload.v4
@@ -0,0 +1,556 @@
+## -*- sh -*-
+
+# The psuedo-ksh autoloader.
+
+# How to use:
+# o One function per file.
+# o File and function name match exactly.
+# o File is located in a directory that is in FPATH.
+# o This script (autoload) must be sourced in as early as possible. This
+# implies that any code in this script should NOT rely on any library of local
+# or self-defined functions having already been loaded.
+# o autoload must be called for each function before the function can be used. If
+# autoloads are in directories where there are nothing but autoloads, then
+# 'autoload /path/to/files/*' suffices (but see options -a and -f).
+# o The call must be made in the current environment, not a subshell.
+# o The command line suffices as "current environment". If you have autoload
+# calls in a script, that script must be dotted into the process.
+
+# The first cut of this was by Bill Trost, trost@reed.bitnet.
+# The second cut came from Chet Ramey, chet@ins.CWRU.Edu
+# The third cut came from Mark Kennedy, mtk@ny.ubs.com. 1998/08/25
+# The fourth cut came from Matthew Persico, matthew.persico@gmail.com 2017/August
+
+autoload_calc_shimsize ()
+{
+ echo $((AUTOLOAD_SHIM_OVERHEAD + 3 * ${#1}))
+}
+
+_autoload_split_fpath ()
+{
+ (IFS=':'; set -- ${FPATH}; echo "$@")
+}
+
+_aload()
+{
+ local opt OPTIND
+ local doexport=0
+ local doreload=0
+ local doverbose=0
+ local doevalshim=0
+ local loadthese
+ local optimize=0
+ local loaded=0
+ local exported=0
+ local optimized=0
+ local summary=0
+ local dofpath=0
+ while getopts xrvla:oyf opt; do
+ case $opt in
+ x) doexport=1;;
+ r) doreload=1;;
+ v) doverbose=1;;
+ l) doevalshim=1;;
+ a) loadthese=$(find $OPTARG -maxdepth 1 -type f -printf '%f ');;
+ o) optimize=1;;
+ y) summary=1;;
+ f) loadthese=$(find $(_autoload_split_fpath) -maxdepth 1 -type f -printf '%f ');;
+ *) echo "_aload: usage: _aload [-xrvlyf] [-a dir] [function ...]" >&2; return;;
+ esac
+ done
+
+ shift $(($OPTIND-1))
+
+ [ -z "$loadthese" ] && loadthese="$@"
+
+ local func
+ for func in $loadthese; do
+ local exists_fn
+ exists_fn=$(declare -F $func)
+ if [ -n "$exists_fn" ] && ((doreload==0)) && ((doevalshim==0))
+ then
+ if ((doverbose))
+ then
+ echo "autoload: function '$func' already exists"
+ fi
+ else
+ local andevaled=''
+ local andexported=''
+ local evalstat=0
+ local doshim=1
+ local funcfile
+ funcfile=$(_autoload_resolve $func)
+ if [[ $funcfile ]] ; then
+ ## The file was found for $func. Process it.
+
+ if ((optimize)); then
+ ## For the first function loaded, we will not know
+ ## AUTOLOAD_SHIM_OVERHEAD. We can only calculate it after
+ ## we have loaded one function.
+ if [[ $AUTOLOAD_SHIM_OVERHEAD ]]; then
+ local size=$(wc -c $funcfile| sed 's/ .*//')
+ local shimsize=$(autoload_calc_shimsize $func)
+ if (( size <= shimsize)); then
+ doshim=0
+ andevaled=', optimized'
+ ((optimized+=1))
+ fi
+ fi
+ fi
+
+ if ((doevalshim)); then
+ doshim=0
+ andevaled=', evaled'
+ fi
+
+ ## 'brand' as in branding a cow with a mark. We add a local
+ ## variable to each function we autoload so that we can tell
+ ## later on it is an autoloaded function without having to
+ ## maintain some bash array or hash that cannot be passed to
+ ## and used by subshells.
+ local brandtext
+ brandtext="eval \"\$(type $func | sed -e 1d -e 4ilocal\\ AUTOLOADED=\'$func\')\""
+ if ((doshim)); then
+ ## Don't bother trying to save space by shoving all the
+ ## eval text below onto one unreadable line; new lines will
+ ## be added at your semicolons and any indentation below
+ ## seems to be ignored anyway if you export the function;
+ ## look at its BASH_FUNCTION representation.
+ eval $func '()
+ {
+ local IS_SHIM="$func"
+ local file=$(_autoload_resolve '$func')
+ if [[ $file ]]
+ then
+ . $file
+ '$brandtext'
+ '$func' "$@"
+ return $?
+ else
+ return 1;
+ fi
+ }'
+ else
+ . $funcfile
+ eval "$brandtext"
+ fi
+ evalstat=$?
+ if((evalstat==0))
+ then
+ ((loaded+=1))
+ ((doexport)) && export -f $func && andexported=', exported' && ((exported+=1))
+ ((doverbose)) && echo "$func autoloaded${andexported}${andevaled}"
+ if [[ ! $AUTOLOAD_SHIM_OVERHEAD ]] && ((doshim)); then
+ ## ...we have just loaded the first function shim into
+ ## memory. Let's calc the AUTOLOAD_SHIM_OVERHEAD size
+ ## to use going forward. In theory, we could check
+ ## again here to see if we should optimize and source
+ ## in this function, now that we now the
+ ## AUTOLOAD_SHIM_OVERHEAD. In practice, it's not worth
+ ## duping that code or creating a function to do so for
+ ## one function.
+ AUTOLOAD_SHIM_OVERHEAD=$(type $func | grep -v -E "^$1 is a function" | sed "s/$func//g"| wc -c)
+ export AUTOLOAD_SHIM_OVERHEAD
+ fi
+ else
+ echo "$func failed to load" >&2
+ fi
+ fi
+ fi
+ done
+ ((summary)) && echo "autoload: loaded:$loaded exported:$exported optimized:$optimized overhead:$AUTOLOAD_SHIM_OVERHEAD bytes"
+}
+
+_autoload_dump()
+{
+ local opt OPTIND
+ local opt_p=''
+ local opt_s=''
+ while getopts ps opt
+ do
+ case $opt in
+ p ) opt_p=1;;
+ s ) opt_s=1;;
+ esac
+ done
+
+ shift $(($OPTIND-1))
+
+ local exported=''
+ local executed=''
+ local func
+ for func in $(declare | grep -E 'local\\{0,1} AUTOLOADED' | sed -e "s/.*AUTOLOADED=//" -e 's/\\//g' -e 's/[");]//g' -e "s/'//g")
+ do
+ if [ -n "$opt_p" ]; then echo -n "autoload "; fi
+ if [ -n "$opt_s" ]
+ then
+ exported=$(declare -F | grep -E "${func}$" | sed 's/declare -f\(x\{0,1\}\).*/\1/')
+ [ "$exported" = 'x' ] && exported=' exported' || exported=' not exported'
+ executed=$(type $func | grep 'local IS_SHIM')
+ [ -z "$executed" ] && executed=' executed' || executed=' not executed'
+ fi
+ echo "${func}${exported}${executed}"
+ done
+}
+
+_autoload_resolve()
+{
+ if [[ ! "$FPATH" ]]; then
+ echo "autoload: FPATH not set or null" >&2
+ return
+ fi
+
+ local p # for 'path'. The $() commands in the for loop split the FPATH
+ # string into its constituents so that each one may be processed.
+
+ for p in $( _autoload_split_fpath ); do
+ p=${p:-.}
+ if [ -f $p/$1 ]; then echo $p/$1; return; fi
+ done
+
+ echo "autoload: $1: function source file not found" >&2
+}
+
+_autoload_edit()
+{
+ [ -z "$EDITOR" ] && echo "Error: no EDITOR defined" && return 1
+ local toedit
+ local func
+ for func in "$@"
+ do
+ local file=$(_autoload_resolve $func)
+ if [[ $file ]]
+ then
+ toedit="$toedit $file"
+ else
+ echo "$funcname not found in FPATH funcfile. Skipping."
+ fi
+ done
+
+ [ -z "$toedit" ] && return 1
+
+ local timemarker=$(mktemp)
+
+ $EDITOR $toedit
+
+ local i
+ for i in $toedit
+ do
+ if [ $i -nt $timemarker ]
+ then
+ local f=$(basename $i)
+ echo Reloading $f
+ autoload -r $f
+ fi
+ done
+}
+
+_autoload_page()
+{
+ [ -z "$PAGER" ] && echo "Error: no PAGER defined" && return 1
+ local topage
+ local func
+ for func in "$@"
+ do
+ local file=$(_autoload_resolve $func)
+ if [[ $file ]]
+ then
+ topage="$topage $file"
+ else
+ echo "$funcname not found in FPATH funcfile. Skipping."
+ fi
+ done
+
+ [ -z "$topage" ] && return 1
+
+ $PAGER $topage
+}
+
+_autoload_remove()
+{
+ unset -f "$@"
+}
+
+_autoload_help()
+{
+ cat <<EOH
+NAME
+ autoload
+
+SYNOPSIS
+ autoload [-ps]
+ autoload [-xuremloyv] [function ...]
+ autoload -a directory [-oyv]
+ autoload -f [-oyv]
+ autoload [-h]
+
+ autoreload [function ...]
+
+DESCRIPTION
+
+ An implementation of the 'autoload' functionality built into other
+ shells, of which 'ksh' is the most prominent. It allows for a keeping
+ the process environment small by loading small 'shim' functions into
+ memory that will, on first call, load the full text of the given
+ function and run it. Subsequent calls to the function just run the
+ function.
+
+ 'autoreload' is a synonym for 'autoload -r'. See below.
+
+USAGE
+
+ o Each function to be autoloaded should be defined in a single file,
+ named exactly the same as the function.
+
+ o In order to avoid side effects, do NOT put code other than the
+ function definition in the file. Unless of course you want to do some
+ one-time initialization. But beware that if you reload the function
+ for any reason, you will rerun the initialization code. Make sure
+ your initialization is re-entrant. Or, better yet,
+
+ *** do NOT put code other than the function definition in the file ***
+
+ o These function definition files should be placed in a directory that
+ is in the FPATH environment variable. Subdirectories are NOT scanned.
+
+ o The autoload script should be sourced into the current process as
+ early as possible in process start up. See NOTES below for
+ suggestions.
+
+ o The calls to the autoload function must be made in the current
+ process. If your calls are in their own script, that script must be
+ sourced in. Command line invocations are also sufficient. (But see
+ '-l' below.)
+
+ o The first time the function is called, the shim function that was
+ created by the 'autoload' call is what is executed. This function
+ then goes and finds the appropriate file in FPATH, sources it in and
+ then calls the actual function with any arguments you just passed in
+ to the shim function. Subsequent calls just run the function.
+
+OPTIONS
+
+ -a Autoload (a)ll the functions found in the given directory.
+
+ -f Autoload all the functions found in all the directories on the
+ FPATH.
+
+ -p Print all the autoloaded functions.
+
+ -s Print all the autoloaded functions and add their export status.
+
+ -x Export the specified functions to the environment for use in
+ subshells.
+
+ -u Unset the function, so it can be reloaded.
+
+ -r Reload the shims of the specified functions, even if the functions
+ have been already been executed. This will allow you to modify the
+ functions' source and have the new version executed next time the
+ function is called.
+
+ It would be very easy to modify a function's script, run the
+ function and scratch your head for a long time trying to figure out
+ why your changes are not being executed. That's why we provide the
+ '-e' flag described below for modifications.
+
+ Reloads, of course, only apply in the context of the current session
+ and any future subshell you start from the current session. Existing
+ sessions will need to have the same 'autoload -r' command run in
+ them.
+
+ -e Find the scripts in which the specified functions are defined and
+ start up \$EDITOR on those scripts. Reload the ones that were
+ modified when you exit \$EDITOR. (Note: If you use 'autoload -e foo'
+ to edit function 'foo', and then in your editor you separately load
+ up function 'bar', 'autoload' has no way of knowing that you edited
+ 'bar' and will NOT reload 'bar' for you.)
+
+ Reloads, of course, only apply in the context of the current session
+ and any future subshell you start from the current session. Existing
+ sessions will need to have the same 'autoload -r' command run in
+ them.
+
+ -m Find the scripts in which the specified functions are defined and
+ run \$PAGER on them ('m' is for 'more', because 'p' (page) and 'l'
+ (load) are already used as options in 'autoload').
+
+ -l When autoloading a function, eval the shim immediately in order to
+ load the true function code. See "Using '-l'" in the NOTES below for
+ details.
+
+ -o Optimize. When autoloading, take the time to execute
+
+ 'theCharCount=\$(wc -c \$theFuncFile)'
+
+ for each function and
+
+ if \$theCharCount < \$AUTOLOAD_SHIM_OVERHEAD
+
+ don't shim it, just eval directly.
+
+ -y Summar(y). Print the number of loaded, exported and optimized
+ functions.
+
+ -v Turns up the chattiness.
+
+NOTES
+
+ o Calling 'autoload' on a function that already exists (either shimmed
+ or expanded) silently ignores the request to load the shim unless it
+ has been previously removed (-u) or you force the reload (-r).
+
+ o Changing and reloading a function that has been exported does not
+ require it be re-exported; the modifications will appear in
+ subsequent subshells.
+
+ o Using '-1'
+
+ If you are running under set -x and/or set -v, you may see that the
+ shim does not appear to "work"; instead of seeing the shim first and
+ the real code subsequently, you may see the shim evaluated multiple
+ times.
+
+ This may not be an error; review your code. What is most likely
+ happening is that you are calling the function in subshells via
+ backticks or $(), or in a script that is not being sourced into the
+ current environment. If you have not previously called the function
+ in question at your command line or in a script that was sourced into
+ the current environment, then the various subshells are going to
+ encounter the shim and replace with the real code before executing.
+
+ Remember, however, that environment modifications that occur in a
+ subshell are NOT propagated back to the calling shell or over to any
+ sibling shells. So, if you call an autoloaded function in a very
+ tight loop of very many subshells, you may want to make an 'autoload
+ -l' call before you start your loop. '-l' will instruct 'autoload' to
+ bypass the shim creation and just source in the function's file
+ directly. For a few calls, the overhead of repeatedly running the
+ shim is not expensive, but in a tight loop, it might be. Caveat
+ Programmer.
+
+ o Although the number of functions in the environment does not change
+ by using 'autoload', the amount of memory they take up can be greatly
+ reduced, depending on the size of your functions. If you have a lot
+ of small functions, then it is possible that the shim text will be
+ larger than your actual functions, rendering the memory savings moot.
+
+ 'small' in this case can be determined by calling the function
+ 'autoload_calc_shimsize' with the name of the function to determine
+ its shim size.
+
+ o In order to support the -p and -s options, we need a way to determine
+ if a function 'func' has been autoloaded or if it was loaded
+ diredctly. In order to do that, we modify the function's code by
+ adding the text
+
+ local AUTOLOADED='func';
+
+ to the shim and to the actual function text, just after the opening
+ brace. Then supporting -p and -s is just a matter of grepping through
+ all the function text in memory. Even though grepping through the
+ environment may not be the most efficient way to support this, it is
+ the simplest to implement for -p and -s operations that are not
+ heavily used.
+
+ As a consequence of this (and other reasons), the AUTOLOAD* namespace
+ is reserved for autoloading. Make sure you check any functions that
+ you bring under autoload for use of variables or functions that start
+ with AUTOLOAD and change them.
+
+ o The easiest way to load shims for all functions on the FPATH is to run
+
+ autoload -f -x
+
+ in the profile that gets run for login shells.
+
+ When called in the profile of a login shell where no definitions
+ exist, -f will load all functions it can find on FPATH and -x will
+ export all of those functions to be available in subshells when this
+ is called in a login shell. Using this option will relieve you of the
+ need to call 'autoload' after Every Single Function Definition, nor
+ will you need to call it in subshells.
+
+ The only thing left to do is to load up the autoload function itself
+ and its helper functions. That needs to happen in your profile:
+
+ export FPATH=~/functions # or wherever you stash them
+ if [ -z $(declare -F autoload) ]
+ then
+ . ~/bin/autoload # or wherever you've put it
+ fi
+
+ The 'if' statement is used to make sure we don't reload autoload
+ needlessly. Sourcing in the autoload script loads the 'autoload'
+ function and all of its support functions. Additionally, we export
+ all of these functions so that they are available in subshells; you
+ do not have to re-source the autoload file in '.bashrc'.
+
+ o Even with all of these shenanigans, you will find cases where no
+ matter how hard you try, your autoloaded functions will be
+ unavailable to you, even if you run 'autoload -x -f'. The typical
+ condition for this is starting up not a subshell, but a brand new
+ DIFFERENT shell. And the typical example of this is git extensions.
+
+ At the time of this writing, git extensions work by taking a command
+ 'git foo' and looking for a file 'git-foo' on the path. 'git' then
+ executes 'git-foo' in a new shell - it executes your command in
+ /bin/sh. That's not a subshell of your process. It will not get your
+ exported shell functions. Ballgame over.
+
+ If you find that you want your functions to be available in such
+ circumstances, convert them back to plain old scripts, make sure they
+ are 'sh' compliant and take the read/parse hit every time they are
+ run.
+
+EOH
+}
+
+autoload()
+{
+ if (( $# == 0 )) ; then _autoload_dump; return; fi
+
+ local opt OPTIND OPTARG
+ local passthru
+ local dumpopt
+ while getopts psuema:yxrvlohf opt
+ do
+ case $opt in
+ p|s) dumpopt="$dumpopt -${opt}";;
+ u) shift $((OPTIND-1)); _autoload_remove "$@"; return;;
+ e) shift $((OPTIND-1)); _autoload_edit "$@"; return;;
+ m) shift $((OPTIND-1)); _autoload_page "$@"; return;;
+ x|r|v|l|y|f|o) passthru="$passthru -$opt";;
+ a) passthru="$passthru -$opt $OPTARG";;
+ h) _autoload_help; return;;
+ *) echo "autoload: usage: autoload [-puUx] [function ...]" >&2; return;;
+ esac
+ done
+
+ shift $(($OPTIND-1))
+ if [ -n "$dumpopt" ]
+ then
+ _autoload_dump $dumpopt
+ else
+ _aload $passthru "$@"
+ fi
+}
+
+autoreload ()
+{
+ autoload -r "$@"
+}
+
+## When we source in autoload, we export (but NOT autoload) the autoload
+## functions so that they are available in subshells and you don't have to
+## source in the autoload file in subshells.
+export -f _aload \
+ _autoload_dump \
+ _autoload_edit \
+ _autoload_help \
+ _autoload_page \
+ _autoload_resolve \
+ _autoload_split_fpath \
+ autoload \
+ autoload_calc_shimsize \
+ autoreload
diff --git a/examples/functions/autoload.v4.t b/examples/functions/autoload.v4.t
new file mode 100644
index 0000000..6d35d14
--- /dev/null
+++ b/examples/functions/autoload.v4.t
@@ -0,0 +1,184 @@
+#!/bin/bash
+
+workdir=$(mktemp -d)
+
+cp autoload $workdir
+
+cd $workdir
+pwd
+
+. ./autoload
+
+funclist='ALTEST_func1 ALTEST_funcexport ALTEST_funcu'
+for funcname in $funclist; do
+ cat <<EOFFUNC > $funcname
+$funcname ()
+{
+echo this is $funcname
+
+}
+EOFFUNC
+
+done
+
+export FPATH=$workdir
+
+autoload ALTEST_func1 ALTEST_funcu
+autoload -x ALTEST_funcexport
+
+ok=0
+failed=0
+
+for funcname in $funclist; do
+
+ testname="$funcname loaded"
+ got=$(type $funcname 2>&1)
+ if [[ $got =~ "$funcname: not found" ]]; then
+ echo "## Failed $testname"
+ ((failed+=1))
+ else
+ echo "ok - $testname"
+ ((ok+=1))
+
+ testname="$funcname is a shim"
+ if [[ ! $got =~ "IS_SHIM" ]]; then
+ echo "## Failed $testname"
+ ((failed+=1))
+ else
+ echo "ok - $testname"
+ ((ok+=1))
+
+ testname="$funcname shim executed"
+ $funcname > /dev/null
+ got=$(type $funcname 2>&1)
+ if [[ $got =~ "IS_SHIM" ]]; then
+ echo "## Failed $testname"
+ ((failed+=1))
+ else
+ echo "ok - $testname"
+ ((ok+=1))
+ fi
+ fi
+ fi
+done
+
+funcname=ALTEST_func1
+testname="$funcname shim reloaded"
+autoload -r $funcname
+got=$(type $funcname 2>&1)
+if [[ ! $got =~ "IS_SHIM" ]]; then
+ echo "## Failed $testname"
+ ((failed+=1))
+else
+ echo "ok - $testname"
+ ((ok+=1))
+fi
+
+funcname=ALTEST_funcu
+testname="$funcname shim unloaded"
+autoload -u $funcname
+got=$(type $funcname 2>&1)
+if [[ ! $got =~ "$funcname: not found" ]]; then
+ echo "## Failed $testname"
+ ((failed+=1))
+else
+ echo "ok - $testname"
+ ((ok+=1))
+fi
+
+testname="autoload -p"
+got=$(autoload -p | grep ALTEST)
+if [[ ! $got =~ "autoload ALTEST_func1" ]] || \
+ [[ ! $got =~ "autoload ALTEST_funcexport" ]] ; then
+echo "## Failed $testname"
+ ((failed+=1))
+else
+ echo "ok - $testname"
+ ((ok+=1))
+fi
+
+testname="autoload -s"
+echo "Executing $testname, could take a long time..."
+got=$(autoload -s | grep ALTEST)
+if [[ ! $got =~ "ALTEST_func1 not exported not executed" ]] || \
+ [[ ! $got =~ "ALTEST_funcexport exported executed" ]] ; then
+ echo "## Failed $testname"
+ echo "## got: $got"
+ ((failed+=1))
+else
+ echo "ok - $testname"
+ ((ok+=1))
+fi
+
+testname="autoload -r -a $FPATH"
+autoload -r -a $FPATH
+localfailed=0
+localok=0
+for funcname in $funclist; do
+ got=$(type $funcname 2>&1)
+ if [[ $got =~ "$funcname: not found" ]]; then
+ echo "## Failed $testname - $funcname"
+ ((localfailed+=1))
+ else
+ ((localok+=1))
+ if [[ ! $got =~ "IS_SHIM" ]]; then
+ ((localfailed+=1))
+ else
+ ((localok+=1))
+ fi
+ fi
+done
+if ((localfailed==0)); then
+ echo "ok - $testname"
+ ((ok+=1))
+else
+ ((failed+=1))
+fi
+
+testname="autoload -u $funclist"
+autoload -u $funclist
+localfailed=0
+localok=0
+for funcname in $funclist; do
+ got=$(type $funcname 2>&1)
+ if [[ ! $got =~ "$funcname: not found" ]]; then
+ echo "## Failed $testname - $funcname"
+ ((localfailed+=1))
+ else
+ ((localok+=1))
+ fi
+done
+if ((localfailed==0)); then
+ echo "ok - $testname"
+ ((ok+=1))
+else
+ ((failed+=1))
+fi
+
+testname="autoload -r -f"
+autoload -r -f
+localfailed=0
+localok=0
+for funcname in $funclist; do
+ got=$(type $funcname 2>&1)
+ if [[ $got =~ "$funcname: not found" ]]; then
+ echo "## Failed $testname - $funcname"
+ ((localfailed+=1))
+ else
+ ((localok+=1))
+ if [[ ! $got =~ "IS_SHIM" ]]; then
+ ((localfailed+=1))
+ else
+ ((localok+=1))
+ fi
+ fi
+done
+if ((localfailed==0)); then
+ echo "ok - $testname"
+ ((ok+=1))
+else
+ ((failed+=1))
+fi
+
+echo $ok passed, $failed failed
+exit $failed