Cover V01, I04
Article
Listing 1
Listing 2

nov92.tar


In-Line Input or Bust

Leor Zolman

All standard UNIX shells support a nifty method for embedding standard input data within the very shell script that reads that data. The syntax for using such in-line input is as follows:

command-name parameters <<-END
input line 1
input line 2
.
.
.
input line n
END

With this form of the construct, all text between the END markers (any text pattern may be used in place of END) is fed to the standard input of the command-name command. The hyphen before the first END says to strip all whitespace from the beginning of each line of the input data; this allows the source text to be formatted with arbitrary indentation for clarity, even when such indentation might confuse the command that will actually read the input.

To see the technique in action, submit the following command to your shell:

wc <<-STOP
this is
a
test
STOP

The system should respond with:

3  4  15

So far, this is all basic shell stuff. Any command that accepts data on its standard input stream can be supplied with in-line data using this technique. However, what do you do with a program that does not accept any input from the standard input stream, but only knows how to take its input from a physical file named on the program's command line? The only way to feed literal input to such a program is to manually create a temporary file containing the required text, run the program with the name of that file as a parameter, and then remove the temporary file after the program has finished running.

Even when you can be certain that only one instance of the script that drives such an application is ever running at one time, the temporary file method is bad enough . . . but when multiple instances of such a script must be able to run simultaneously, then you also have to guard against temporary file name collisions. To borrow a line from The Color of Money: "It just keeps getting worse and worse, doesn't it?"

I ran into this dilemma while working with the Informix database system. R&D had just upgraded from the old Informix 3.3 to the new SQL-based version 4 for our Xenix system. While the old Informix informer command supported in-line queries on the standard input stream, the new isql command does not. In the old shell scripts, I had gotten very used to doing things like unloading database tables with one compact command, e.g.:

informer accounts <<END
unload ascii customers to "customers.txt"
bye
END

To do this with isql, write the command sequence

database accounts;
unload to "customers.txt"
select * from customers;

into a file, and then run isql, specifying the name of that file on the command line. Considering how often we perform operations such as this in our shell scripts, a way to specify the SQL commands in-line was sorely needed.

Listing 1 shows the runsql/runsql2 script I wrote to support such in-line SQL commands. While this particular script is specific to the Informix isql command, the technique may easily be adapted to any other application. This script has two names, runsql and runsql2, to support two distinct modes of operation. When invoked as runsql, the stdout and stderr streams are discarded (directed to /dev/null, the appropriate place for most of the useless status information generated by the isql command). When invoked as runsql2, stdout and stderr are passed along so they may be recorded, if desired. Any references to runsql below apply to runsql2 as well.

Both forms of the command take one mandatory command-line parameter, the name of the database to be operated on. If a second parameter is specified, it is the name of a text file from which to read the SQL commands. When the second parameter is omitted, the text is read from the standard input. Most of the time, we use the standard input method. The filename method, being essentially the same as invoking isql directly, is there mainly so the user need not memorize the precise argument flags needed on the isql command line.

runsql works by creating a uniquely named temporary file (using the tmpname program shown in Listing 2), copying the input text into that file, and submitting it to isql. If you supply a filename to runsql instead of in-line text, then that file is passed along to isql only if it ends with the .sql extension; without such an extension, isql would refuse to process it. Thus, if you supply a filename and it does not end in .sql, then runsql copies your file to a temporary file whose name does end in .sql and submits the temporary file to isql for processing.

Since all SQL scripts processed by isql must begin with a database command, runsql automatically inserts such a command (using the database name supplied on the command line) at the beginning of the temporary SQL script file it creates. If you use the filename version of runsql, then your file is passed along to isql without modification and you must therefore include an explicit database command.

Using runsql is just like using the old Informix informer command, except of course for isql's up-to-date SQL query syntax. Thus, to run the example SQL command shown earlier, you'd simply write:

runsql accounts <<END
unload to "customers.txt"
select * from customers;
END

I've found using runsql invaluable for simplifying the source code for applications intermixing small, on-the-fly SQL queries with various file-manipulating operations. One e-mail correspondent of mine has already indicated that the technique could be adapted to streamline ESQL operations; there are probably other applications out there that would benefit from such an in-line input utility as well.

About the Author

Leor Zolman wrote BDS C, the first C compiler targeted exclusively for personal computers. He is currently a system administrator and software developer for R&D Publications, Inc., and columnist for both The C Users Journal and Windows/DOS Developer's Journal. Leor's first book, Illustrated C, has just been published by R&D. He may be reached in care of R&D Publications, Inc., or via net E-mail as leor@rdpub.com ("...!uunet!rdpub!leor").