Procedures
Arguments to a procedure
Return values
Recursion
Calling a procedure
See also
Procedures, as in any high-level language, are a convenient way to package together a series of statements as a more convenient operation. Once you’ve defined a procedure, you can invoke it simply as if it were a new command.
The proc statement can also be used to ask what procedures are already defined or what arguments a particular procedure takes:
You can explicitly discard a definition with unproc; otherwise the shell remembers any procedure you tell it until you exit the shell or give it a new definition.
When you give the shell a procedure definition, the shell compiles it into an internal form so that the next time you refer to it, it’ll save the reparsing time and run much faster. As an example, unproc the whereis procedure to make the shell reload the definition from the .csh file and see what that does to the execution time:
The namespace for procedures is shared among all the threads: if one thread creates a new procedure, it becomes usable immediately by all the other threads.
You can write a procedure so it expects arguments, just as you would in any other high level language. Argument names are somewhat like local variables: their initial values are set at entry to a procedure, hiding any previous definition; they go away as soon you exit the procedure code. Here’s a simple example which compares the timestamps on two files.
When you pass arguments to a procedure on the command line, the individual argument words are paired up, one-by-one, with the argument names you gave. If the shell runs out of names before it runs out of words, the last named argument gets all the remaining words:
If you pass arguments to a procedure that doesn’t take any, they’re evaluated but quietly ignored.
If a procedure does take an argument, it always get some value, even if it’s zero words long. So if you want to know if you got passed a value, just count the number of words:
In a more serious vein, here’s a simple procedure definition I use all the time (I have it in my startup.csh file) to implement a real quick and dirty (but very easy to use!) personal phone index:
As you add lines to your \phone file, you merely add any interesting search phrases or other tidbits onto the same line with the person’s name. Totally free format. Add anything you like and search on anything you like and it’s fast.
Procedures are also important in expressions, where it’s generally useful to think of the procedure as returning a value, just as it might in any other language. The type and value of what you choose to return is arbitrary. Here’s a purely mathematical example from finance.csh in the samples directory:
If you call a procedure that returns a value as if it were a command, whatever it returns is printed:
A procedure can call other procedures or even itself. When a procedure calls itself, it’s called recursion. Typical uses of recursion are in cases where the problem itself is recursive, or self-replicating. For example, here’s a procedure to walk down two directory trees A and B that are thought to be related and list any non-hidden files in A that are not in B. (If you set nonohidden = 1, it’ll compare hidden files also.)
Notice that i and f were declared as local variables. If the variables were simply set variables, one instance of them would be shared by all the levels of recursion. In this particular example, that would still have worked, but only because each level calls the next only after anything involving f or i has been evaluated; it wouldn’t matter if f or i was trampled by the next call. Here’s an example where obviously that would not be true: a clumsy attempt at a “post-order” traversal of a directory tree:
If you carefully examine the output of this traverse, you’ll see that subdirectories don’t get listed properly: instead of being listed by themselves, the name of their last child is listed twice. For a correct result, try it again with i defined as a local variable. (Use the Page Up key to help you quickly re-enter the lines that stay the same.)
As you may have spotted, there are two ways to invoke a procedure. Sometimes, the arguments are inside parentheses, separated by commas, and sometimes they’re not. What’s the difference?
The difference is whether the context is an expression or a command. As discussed when we first introduced expressions, the shell always begins to parse statements by first breaking them up into words. That’s fine for normal commands, e.g., running an external utility. And it works also when you want to use a procedure as if it were a command, just typing the name of the procedure followed by a list of arguments separated by spaces, e.g.,
But this style of parsing wouldn’t be very suitable in those instances where the point is to do some kind of calculation or expression evaluation. So when the shell encounters something that normally takes an expression, e.g., following the calc keyword, or inside the test in an if statement, it shifts to a different style of parsing, further breaking up the words into tokens, so that * isn’t misunderstood as a wildcard, so we don’t need to type spaces around all the operators, so we can type variable names without having to put a $ in front of them and so on. All of this is so that the rules for typing an expression can bear some resemblance to those followed by other programming languages like C, FORTRAN, Pascal, etc.
When we call a procedure from within an expression, all these same arguments still apply. We want it to act pretty much like any other high level languages. We want to be able to pass it arbitrarily complex expressions as arguments. We want to be able to take the value it returns and use that value as a term in still other expressions.
So there’s a real problem: to call a procedure from within an expression and pass other expressions as arguments, we need a way of separating one argument from the next (obviously, it can’t be just a space as it would be when the procedure is used as if it were a command) and for separating the whole procedure call and its arguments from the rest of the expression. That’s why the common high-level language convention of separating arguments by commas and putting parentheses around the whole list is used. Here’s an example of what that looks like:
If you try using a procedure as a command but accidentally type the argument list with parenthesis, it’s an error:
The reason this is an error is because, since this was typed as a command, the shell took the words following the word power as literal arguments. It couldn’t tell you meant this as an expression. Let’s redefine that procedure, putting some echo statements in there so we can see what happened:
As you can see, the expression a**b failed to evaluate properly because a was set to the first argument word, (, and b was set to a list of all the rest of the words. Neither was a number. If you want to call a procedure and substitute the value back onto the command line even when the context is not an expression, it can be done, however. One way is with command substitution:
This is a bit expensive, though, because the shell will have to create a new thread to run the power procedure and set up a pipe to read the result. And as you see, if the procedure also writes to stdout, you’ll pick up that text also, probably unintentionally. Another, better way, is to use a dollar sign to introduce the substitution just as if it was a variable substitution:
Notice that when use the dollar sign-style procedure reference, the rest of the syntax is as if the procedure had been called from within an expression. The arguments do need to be within parenthesis and they do need to be separated by commas. The reason is just the same one as for why a procedure call in an expression has to be done this way: without the parentheses, there’d be no way to tell where the arguments ended. A nice benefit is that in the argument list, we get to use the full expression grammar:
Procedures
Builtin procedures
Variable substitution
Substitution modifiers
Quoting
Expression operators
Order of evaluation
Tutorial: Variables
Tutorial: Quoting
Tutorial: Expressions