Hamilton LaboratoriesHamilton C shell 2012User guideTutorials

Procedures

Oregon Coast

Procedures
Previous | Next

Topics

Procedures
Arguments to a procedure
Return values
Recursion
Calling a procedure
See also

Procedures

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.

432 D% proc hello() 433 D? echo hello world 434 D? end 435 D% hello hello world

The proc statement can also be used to ask what procedures are already defined or what arguments a particular procedure takes:

436 D% proc hello hello ( ) 437 D% proc | mi abs ( x ) acos ( x ) asin ( x ) : : samepath ( a, b ) sin ( x ) sinh ( x ) --- more --- (Press H for Help)

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.

438 D% unproc hello 439 D% hello csh: Couldn't find an executable file named 'hello'.

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:

440 D% unproc whereis 441 D% time whereis frizzle d:\Nicki\bin\Frizzle.exe 0:00:02.15 442 D% !! time whereis frizzle d:\Nicki\bin\Frizzle.exe 0:00:01.28

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.

Arguments to a procedure

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.

443 D% proc comparedates(a, b) 444 D? if (`newer $a $b`) then 445 D? echo $a is newer than $b 446 D? else 447 D? if (samepath(a, b)) then 448 D? echo $a and $b are the same file! 449 D? else 450 D? echo $a is older than $b 451 D? end 452 D? end 453 D? end 454 D% comparedates `whereis Frizzle` d:\Nicki\bin\Frizzle.exe is newer than d:\Nicki\LastRTM\Frizzle.com 455 D% _

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:

455 D% proc xx(a, b) 456 D? echo $#a $a 457 D? echo $#b $b 458 D? end 459 D% xx now is the time 1 now 3 is the time

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:

460 D% proc xx(a) 461 D? echo $#a ">>$a<<" 462 D? if (a == "") echo null argument! 463 D? end 464 D% xx 0 >><< null argument!

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:

465 D% proc ppi(name) 466 D? grep -i "$name" h:\phone 467 D? end 468 D% ppi hamilton Hamilton Laboratories 425-497-0102

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.

Return values

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:

469 D% proc FV_PresentAmount(i, n) 470 D? # Calculate the multiplier to convert $1 now to a 471 D? # future value, given interest rate i 472 D? return 1/(1 + i/100)**n 473 D? end 474 D% # Calculate the future value of $500 invested 475 D% # for 10 years at 8% interest. 476 D% calc 500*FV_PresentAmount(8, 10) 1079.462499

If you call a procedure that returns a value as if it were a command, whatever it returns is printed:

477 D% FV_PresentAmount 8 10 2.158925

Recursion

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.)

478 D% proc comparetrees(a, b) 479 D? local i, f 480 D? foreach i ($a\*) 481 D? @ f = $i:t 482 D? if (! -e $b\$f) then 483 D? echo $b\$f is missing 484 D? else 485 D? if (-d $i) comparetrees $i $b\$f 486 D? end 487 D? end 488 D? end 489 D% comparetrees c:\src\projectx a:\src

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:

490 D% proc traverse(a) # Don't do it this way 491 D? foreach i ($a\*) 492 D? if (-d $i) traverse $i 493 D? echo $i 494 D? end 495 D? end 496 D% traverse . | more

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.)

Calling a procedure

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.,

497 D% proc power(a, b) 498 D? return a**b 499 D? end 500 D% power 2 3 8 501 D% _

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:

501 D% calc 5.5 + power(2, 3)*9 77.500000

If you try using a procedure as a command but accidentally type the argument list with parenthesis, it’s an error:

502 D% power(2, 3) csh(line 490): Couldn't evaluate expression operands as numeric as required by the expression operator. > in power( "(", "2,", "3", ")" ) defined at line 597 < called from line 502

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:

503 D% proc power(a, b) 504 D? echo a is $a 505 D? echo b is $b 506 D? return a**b 507 D? end 508 D% power(2, 3) a is ( b is 2, 3 ) csh(line 506): Couldn't evaluate expression operands as numeric as required by the expression operator. > in power( "(", "2,", "3", ")" ) defined at line 503 < called from line 508

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:

509 D% echo `power 2 3` a is 2 b is 3 8

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:

510 D% echo $power(2, 3) a is 2 b is 3 8

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:

511 D% echo $power(2, 3*sin(1/2)) a is 2 b is 1.438277 2.709970

See also

Procedures
Builtin procedures
Variable substitution
Substitution modifiers
Quoting
Expression operators
Order of evaluation
Tutorial: Variables
Tutorial: Quoting
Tutorial: Expressions

Previous | Next

Getting started with Hamilton C shell

Hamilton C shell, as it first wakes up.

Getting started with Hamilton C shell

A first few commands.

You can set the screen colors to your taste.

You can set the screen colors to your taste.