next up previous contents index
Next: Shared Clients Up: Events Previous: Point-to-Point Communication

Creating Clients


  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 gif), 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


  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 gif, and § 7.5, page gif, 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:  

    specifies on which host to run the client, as a string scalar. For example,
    c := client("tester", host="mars")
will run tester on the remote host mars. If missing, the client runs on the local host.

    takes a string value and makes it the client's standard input. The value is split into lines at each occurrence of a newline ('\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.

    takes a boolean value. If true then when the client runs it will first suspend itself, allowing a debugger to attach. Suspending clients generate a message like:
    tester @ myhost, pid 18915: suspending ...
indicating that the client tester running on host myhost with process ID 18915 has suspended. See § 11.2, page gif, for more information on debugging suspended clients.

    takes a boolean value. If true then whenever an event is sent to the client, the client will be ``pinged" by also sending it a SIGIO signal. Use of ping= is discouraged, since it is error-prone (in particular, receipt of a single SIGIO signal may indicate more than one new event has arrived for the client). I'm interested in hearing from users who find ping= indispensable.

    takes a boolean value. A true value specifies an asynchronous client. In this case, Glish does not execute the client process. Instead it is assumed that the user has arranged a separate mechanism for invoking the process, perhaps because the mechanism Glish uses to invoke remote clients (Chapter 14, page gif) is unavailable for this particular client.

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.

For example, executing:  

    a := client("special", async=T)
    print "executing special using:", a.activate
will 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 gif, below). Even before this happens, though, you can send events to a and execute whenever statements for responding to a's events.

The shell Function


    You can use the shell function to incorporate unmodified Unix programs into a Glish program. As noted in § 4.9, page gif, 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::status
to 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

whenever hex->stdout do
    print count, "=", $value
    if ( count < 32 )
        count +:= 1

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 gif, 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 $value
will print out several filenames at a time, because ls writes its output in columns when writing to a terminal.        

next up previous contents index
Next: Shared Clients Up: Events Previous: Point-to-Point Communication

Thu Nov 13 16:44:05 EST 1997