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