[comp.unix.questions] exported functions

forsyth@minster.UUCP (03/21/87)

I was most disappointed when I discovered that the system V shell could
not export functions.  That was the second thing I tried!  The code
to print functions out for the `type' built-in command is pretty
much what one requires to do exported functions.  It can be changed to
take the parse tree for a function and accumulate the function text as
a string on the heap instead of printing it (the Bourne shell has a
special storage manager that makes this trivial).

I chose to represent each exported function as a separate string
in the environment, with the form

		f(){...body...}

Of course, getenv was changed to watch for ( as well as =, so that in my current
implementation there are three possibilities for the parameter to getenv:

		name	fetches the value of either a variable or function
		name=v	fetches the value of a variable only
		name()	fetches the value of a function only

The ``value'' of a function includes the ``()''.  Putenv accepts
``name=value'' or ``name()...''.  There might be better conventions,
but these work well enough.  I think it is important for programs to be
able to access or change the definition of functions individually, just
as for variables like PATH; this makes exporting all functions in one
environment variable unattractive.

I also thought it made sense for execlp(name, arg...) to look for functions.
At the ``as-is'' part of the PATH variable, if it finds that name is a simple name
(no /'s) and there is a function with that name in the environment, it executes:

	sh -c 'name ${@+"$@"}' name arg ...

This allows commands such as time, nohup, find, and overwrite, all of
which execute other commands, to execute functions as well.  If
functions were exported only from shell to shell, using files, it would
be much more expensive to do this.

It is quite useful to allow these and other programs using
execlp/execvp to execute functions.  A function can contain arbitrary
Shell commands, including loops and pipelines, and the quoting and ${}
expansion is applied when the function is called, not while it is being
defined.  By naming uninterpreted Shell text in this way and passing it
to other commands to execute, one can defer the interpretation until an
``ordinary'' command has prepared the desired environment.  I can do
this using sh -c directly, but it can be hard to get the quoting right
first time in difficult cases, which is just when I need most help.

For instance, I can type

	blotto() sed "s/^[^ ]*	/`uuname -l`	/"
	export blotto
	overwrite file blotto <file

However, it is tedious to have to invent a function name.  What I want to do is this:

	overwrite file { sed "s/^[^ ]*	/`uuname -l`	/" } <file
or
	overwrite file { sed ... | sort | more-stuff } <file

and have the Shell take { ... } appearing as an argument to be the definition of
a temporary function.  It would generate a name, export it, and substitute the name
for the {...}.  Ie, something like

	_1() { sed ... | sort | more-stuff }
	export _1
	overwrite file _1 <file
	unset _1

for the second example. This would allow

	time { something | else }

in a convenient syntax without building time (and nohup, and ...) into the Shell.
Unfortunately, I have not (yet) implemented all this (but perhaps someone else has!).

Such a change could enhance and simplify the interface to some
commands.  The -exec operator of the find command replaces each
instance of ``{}'' (when it appears by itself) by the current
pathname.  Unfortunately this does not apply to arguments such as
``{}/name''.  It would be easy enough to extend find's treatment of {},
but the syntax still remains unique to find. Suppose instead
that the Shell has been extended as above.  Find might then use putenv
to export the current pathname as the value of FILE, allowing:

	find . -exec { mv $FILE b/$FILE } \;

A user must know that find gives a value to FILE, but the rules for its use,
and for the construction of the -exec argument, are just the rules of the Shell
and found elsewhere on the system.

This is intentionally similar to the treatment by some shells of (...)
appearing as an argument to another command.  In that case, the shell makes
a new process to execute the command inside (), and replaces the (...) by
the name of one end of a pipe to that process.  In a sense, for (...) the
shell substitutes a reference to the command's value (its output),
and for {...} it would substitute a reference to the command's text.

Some care is required to prevent introducing security problems with
set-uid shell files and restricted shells.

Oh yes.  System V functions share the caller's argument list, so that if ``rr'' is
the file

	f(){
		shift
		echo $*
	}
	f $*
	echo $*

then ``rr a b c'' gives

	b c
	b c

I thought this was odd (it messes up nested function calls). On minster, it gives

	b c
	a b c