Finally, to put everything in perspective, here’s a summary of roughly the procedure by which the C shell reads, parses and evaluates your commands:
The command is read. If stdin appears to be a keyboard, the command line editing routines are used to read a keystroke at a time, entering them into the command buffer and doing whatever editing is indicated. Otherwise, the shell simply uses the kernel’s ReadFile (Win32) functions to read small chunks until the end of the statement has been found.
History substitution is done. The “!” and “%”-style history references are expanded.
The text is broken up into separate words. Unless it’s part of a quoted string, white space (tabs and spaces) separates words. Also, these special strings are interpreted as separate words even if they’re run together with other text:
& | ; > < ( ) && || >> << >& >! >&!The command is added to the history list. The fact that this is done after the text has been broken up into separate words explains why the commands in the history list will look a bit different than the way you typed them. It’s done this way on purpose so that you can refer to individual words in previous commands, e.g., with “!$” to get just the last word of the last command.
The command is compiled into an internal form using a recursive descent parser, recognizing the language constructs and whether a given portion of a command is really an expression or just a series of words.
Compilation at this stage is at the level of a whole construct, e.g., a whole foreach statement or proc definition and everything inside it. That’s so that every time through a loop or every time a procedure is run, the shell won’t waste time recompiling statements that could have been compiled the first time. Also, aliases are expanded at this stage and some minor optimizations are done, e.g., pre-compiling static patterns appearing in pattern-matching expressions, etc.
The internal form is executed. The various quoting and other substitution activities are done, in effect, in this order:
Threads are spawned for separate stages of a pipeline or for background execution. That’s to avoid serializing any blocking events as, for example, the shell hits the disk, looking through the search path for executable files, etc. By spawning separate threads, those blocking events can be overlapped.
I/O redirection is performed. If the filename being redirected to/from is actually a wildcard or a command or some kind of substitution, that word will be expanded.
Single and double quoted strings are processed. If the quoted string contains any wildcard characters, they’re escaped so that they’ll appear as literal characters when wildcarding is done but still be unescaped right after that.
In the compiled internal form, double-quoted strings containing variable or command substitutions are already specially broken up to look, at this stage, like a series double-quoted strings and substitutions concatenated together.
Variable and command substitutions are done.
Wildcarding is done.
Escape characters are processed.
The series of words is passed to the command as arguments. (It’s at this point, if it’s an eval command, that the argument text is passed back through the parser and then to the evaluation logic)
Commands are searched for in this order:
- User-defined procedures.
Built-in procedures and commands.
External commands, searched for in the PATH directories in this order within each directory:
.csh .exe .com .cmd .batIf no executable file with one of these extensions is found, all files which match the command will be examined. If the file appears to be a script, the C shell will look for a “#!” directive in the first line, e.g.,
#!/usr/bin/perland, if it finds one, try to find the specified language processor, looking first in the location specified and, if not found there, in all the various path directories.
The internal form of each compiled statement is discarded once it’s no longer needed, i.e., if there’s no way you might invoke that code from a later statement.
For example, once you define a procedure, it’s always accessible; you can call it at any time, so that compiled code is never discarded unless you redefine the procedure or explicitly unproc it. But an ordinary statement typed at the command line could be re-run (without re-entering it using history or by retyping it) only if it was part of a larger control structure or if there was a way to goto it, meaning there would have to have been a label preceding it.
Previous Topic |
Table of Contents
| Next Topic
Copyright © 1988-2003 by Hamilton Laboratories. All rights reserved.