Clients form the heart of the Glish system. They can be created in two
ways. You can use either the predefined client function to execute
a new instance of a client program (i.e., a program linked with the Glish
Client Library; see Chapter 13, page ), or the predefined shell
function to run an unmodified Unix program as a simple type of Glish
client. We discuss these two alternatives in turn.
The client function takes the name of a program and an optional
set of arguments, invokes the program with the arguments, and returns
a Glish agent value that you can then use to manipulate the
program via events, as discussed in § 7.4, page , and § 7.5, page
,
above.
A call to client requires at least one argument, a string
giving the name of the program to execute. If the name begins with
a slash (/) or a period (.) then it is interpreted as
giving the complete pathname to the program; otherwise
system.path.bin.hostname is used to locate clients.
hostname in this case is the host on
which the client is being started. If no field matching the hostname
is found in system.path.bin, system.path.bin.default
is
used (if it exists). A field for the local host is created in
system.path.bin
at startup time using the $PATH environment
variable.
Additional arguments to client are converted to string values and passed to the program. For example,
c := client("tester", 1:3, "hi")invokes the program tester and passes it four string arguments, ``1", ``2", ``3", and ``hi".
Presently the client arguments are first evaluated as string values and then split into separate run-time arguments at each instance of whitespace. This means that the following:
c := client("tester", 'hello there')invokes tester with two arguments, ``hello" and ``there". This behavior means that it's impossible to pass an argument to a client that includes embedded whitespace. This behavior will be fixed in the future.
Also note that the evaluation behavior applies to the first client argument as well. The last example could also have been written:
c := client("tester hello there")This still invokes the program tester, not tester hello there.
The client function also takes a number of optional, named parameters:
c := client("tester", host="mars")will run tester on the remote host mars. If missing, the client runs on the local host.
\n
'):
c := client("tester", input="hello there")results in tester seeing a single line on its standard input, namely the string ``hello there'', while
c := client("tester", input='how\nare\nyou?')results in three lines appearing on tester's standard input.
Note that presently non-string values are converted to strings as though they were being printed, so
c := client("tester", input=1:3)results in the single line ``[1 2 3]'' appearing on the standard input. This may change in the future.
If no input= argument appears then the client inherits the Glish interpreter's standard input.
tester @ myhost, pid 18915: suspending ...indicating that the client tester running on host myhost with process ID 18915 has suspended. See § 11.2, page
For asynchronous clients, the client function still returns an agent value. The record associated with this value has its activate field set to a string giving special command-line arguments that need to be passed to the client. Once the client is executed by some means, these arguments will then be interpreted on the client's behalf by the Glish Client Library so that the client knows how to connect to the Glish interpreter.
a := client("special", async=T) print "executing special using:", a.activatewill assign to a an agent value and then print out the arguments that need to be used to invoke special and associate it with a's agent. Note that a host= option should not be used even if special is running on a remote host.
When the asynchronous client runs and ``joins" the current Glish
script it generates an established event, just as do regular
clients (see § 7.11, page , below). Even before this
happens, though, you can send events to a and execute
whenever statements for responding to a's events.
You can use the shell function to incorporate unmodified Unix
programs into a Glish program. As noted in § 4.9, page , above,
a standard use of shell is to run a Unix program, wait for
it to terminate, and collect its output as a string vector. For
example:
sources := shell("ls *.c")returns in sources a list of all of the files in the current directory that end in ``.c". The return status of the command which the shell executes is returned as an attribute:
print sources::statusto allow the user to check on the exit status of the Unix command.
Similar to client (see preceding section), shell takes a number of optional arguments. Of these, host=, input=, and ping= behave identically (suspend= is useful only for debugging Glish itself).
The async= option behaves differently, though. It runs the shell command asynchronously; that is, it instructs Glish not to wait for the command to complete but instead to use an event-oriented interface for the shell command. We call asynchronous shell commands ``shell clients".
The asynchronous interface works as follows. First, when async=T is used, shell returns not a string value but instead an agent value, as client does. You can then use the agent value to send stdin events to make text appear on the shell client's standard input, EOF events to close the standard input, and terminate events to terminate the client. Furthermore, each line of text the shell client writes to its standard output becomes a stdout event.
To illustrate, here's a Glish script that uses awk to print the numbers from 1 to 32 in hexadecimal, each appearing as a separate event:
cvt := "awk '{ printf(\"%x\\n\", $1) }'" hex := shell(cvt, async=T) count := 1 hex->stdin(count) whenever hex->stdout do { print count, "=", $value if ( count < 32 ) { count +:= 1 hex->stdin(count) } else hex->EOF() }
The first two statements associate an asynchronous shell client with the variable hex. The next line initializes the global count to 1 and sends that value to hex, making it appear on awk's standard input.
The whenever body prints out the current count and its hexadecimal equivalent, and then either increments the count and sends awk a new input line or closes its standard input.
One might think that a race exists between sending the first stdin
event to hex's client and setting up the whenever to deal
with the client's response. This problem does not arise, however,
because the Glish interpreter does not read events generated by
clients until it is done executing all of the statements in a script;
see § 11.1.2, page , below.
One final note regarding asynchronous shell commands. Glish uses a technique called ``pseudo-ttys" for communicating with shell clients. This makes the shell clients' standard output be line-buffered (instead of block-buffered, the default). Without this technique, in our example awk would buffer up its output until either it had filled an entire block (a lot of text) or its standard input was closed and it exited. In this case we would not immediately get a new stdout event for each stdin event, and the program would not work correctly. One drawback of using ``pseudo-ttys", though, is that the shell command truly believes that it is writing to a terminal. Programs that alter their behavior depending on whether they're writing to a terminal will engage the altered behavior. For example, on BSD systems the following Glish program:
a := shell("ls", async=T) whenever a->stdout do print $valuewill print out several filenames at a time, because ls writes its output in columns when writing to a terminal.