Redirecting input and output with Clipper
  Erik Wynn, Ottawa Clipper User Group
-----------------------------------------

Redirection of input and output is not a new idea to the computer
industry.  It has been around for many years, and is one of the very
powerful features of UNIX and MS-DOS.

Most DOS commands send their output to the "standard output device",
also known as STDOUT.  By default, STDOUT is the screen, but this can
be changed by the use of the redirection symbol ">" to redirect
the output to an alternative device or file, for example:

	dir > dir.txt	- to send output to file dir.txt
	dir > lpt1	- to send output to the printer at lpt1

DOS commands also receive input from the "standard input device", also
known as STDIN.  By default, STDIN is the keyboard, however this can be
changed by the use of the redirection symbol "<" to redirect input from
an alternative device or file, such as:

	more < readme.txt	- view the readme.txt file with DOS's "more" command

When a command reads from STDIN and writes to STDOUT, it is called a pipe
or a filter.  Generally, filters are used to transform data.  The "|"
character is used to represent a pipe in DOS, as in:

	dir | sort	- produce a sorted directory listing

Of course, the result of this could be redirected to a file, as in:

	dir | sort > sortdir.txt	- send a sorted directory listing to sortdir.txt

Pipes can be combined for even more power, for example:

	dir | sort | more	- view a sorted directory list with DOS's "more" command

Pipes and redirection provide the user with a great amount of power
and flexibility.


Clipper Functions StDin and StdOut
----------------------------------

By design, Clipper programs do not receive their data from STDIN, nor do they
send data to STDOUT.  They can be made to read from STDIN and write to STDOUT
by using the low level file functions fread() and fwrite().  We will create
two Clipper UDFs, StdIn() and StdOut() to perform these tasks.


#define STDIN  0
#define STDOUT 1

FUNCTION StdIn(cIn, nLen)

	/**************************************************
	*
	* Function    : StdIn()
	* Description : Read string from STDIN
	* Details     :
	*   Read string up to length of nLen into buffer cIn from
	*   standard in, STDIN.
	* Parameters  :
	*   cIn  <expC> : buffer into which to read the string
	*   nLen <expN> : length of string to read
	* Usage       : nBytesRead := StdIn(@cBuffer, nBytes)
	* Returns     : <expN> as length of string read
	* Category    : STDIO
	* See Also    : StdOut()
	* Author(s)   : Erik Wynn
	* Date        : 91.05.01
	*
	**************************************************/

	local nLenRead := 0
	nLenRead := fread(STDIN, @cIn, nLen)

RETURN(nLenRead)


FUNCTION StdOut(cOut)

	/**************************************************
	*
	* Function    : StdOut()
	* Description : Write string to STDOUT.
	* Details     :
	*   Write string to standard output, STDOUT.
	* Parameters  : 
	*   cOut <expC> : string to be sent to STDOUT.
	* Usage       : StdOut(cOut)
	* Returns     : NIL
	* Category    : STDIO
	* See Also    : StdIn()
	* Author(s)   : Erik Wynn
	* Date        : 91.05.01
	*
	**************************************************/

	fwrite(STDOUT, cOut, len(cOut))

RETURN(NIL)


An Example of Redirecting Output
--------------------------------

You can redirect the output of a Clipper program by using the StdOut()
function, above.  This allows you to send the results to the
screen (default) or to a file or a printer by redirecting the output from
the DOS command line.  Here is an example of a routine to determine
index expressions which sends its results to STDOUT.

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< BEGIN CODE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


#define CRLF     chr(13) + chr(10)

/* file constants */
/*      F_NAME is found in "directory.ch"                 */
/*      RD_ONLY is read-only open mode used by fopen()    */
/*      FTOP is the top-of-file indicator used by fseek() */

#define F_NAME           1
#define RD_ONLY          0
#define FTOP             0


/* some useful index file constants */
/*      The index expression in Clipper NTX files starts at offset 22 */
/*      The index expression in dBASE   NDX files starts at offset 24 */
/*      The index expression in FoxBase IDX files starts at offset 16 */
/*      The maximum length of an index expression is 250 characters   */

#define NTX_EXP_OFFSET  22
#define NDX_EXP_OFFSET  24
#define IDX_EXP_OFFSET  16
#define NTX_EXP_MAXLEN 250


** main

	/**************************************************
	*
	* Program     : NtxExp
	* Description : determine the index expression for a group of NTX file.
	* Details     :
	*   Use the directory() to build an array of index files matching
	*   the file specification cFileSpec.  Use aeval() to determine the
	*   index expression for each file using low level file functions.
	*   Sends output to STDOUT.
	*
	* Parameters  : 
	*   cFileSpec <expC> File Specification
	* Category    : NTX
	* Author(s)   : Erik Wynn
	* Date        : 91.05.01
	*
	**************************************************/

	parameters cFileSpec

	local nExpOff, cNtxExt, aNtxFiles

	/* check for file extension */
	cFileSpec += iif(at(".", cFileSpec) == 0, ".NTX", "")

	/* determine the index extension */
	cNtxExt := upper(substr(cFileSpec, at(".", cFileSpec) + 1, 3))

	/* determine the index expression offset */
	if ((nExpOff := iif(cNtxExt == "NTX", NTX_EXP_OFFSET, ;
		iif(cNtxExt == "NDX", NDX_EXP_OFFSET, ;
		iif(cNtxExt == "IDX", IDX_EXP_OFFSET, -1)))) == -1)
		qout(CRLF + "Invalid index file type " + upper(cNtxExt) + "." + CRLF)
		quit
	endif

	/* fill the array of ntx files using the directory() function */
	if ((len(aNtxFiles := directory(cFileSpec))) == 0)
		qout(CRLF + "No files found matching " + cFileSpec + "." + CRLF)
		quit
	endif

	StdOut(CRLF)
	StdOut("File           Index Expression" + CRLF)
	StdOut("-------------------------------" + CRLF)

	/* evaluate the index expression for each element in the array */
	aeval(aNtxFiles, { | aCurNtx | NtxExpOut(aCurNtx[F_NAME], nExpOff) })

	stdout(CRLF)


FUNCTION NtxExpOut(cNtxFile, nNtxExpOff)

	/**************************************************
	*
	* Function    : NtxExpOut
	* Description : Output the expression of an index file.
	* Details     :
	*   Determine the index expression from the header of an index file
	*   using low level file functions.  Send result to STDOUT
	* Parameters  :
	*   cNtxFile   <expC> : index file to use
	*   nNtxExpOff <expN> : offset of index expression from start of file
	* Returns     : NIL
	* Category    : NTX
	* See Also    : NtxExpEval()
	* Author(s)   : Erik Wynn
	* Date        : 91.05.01
	*
	**************************************************/

	local hNtxFile := 0
	local cExp

	if (empty(cExp := NtxExpEval(cNtxFile, nNtxExpOff)))
		StdOut("Unable to open index file " + cNtxFile + CRLF)
	else
		StdOut(substr(cNtxFile + space(12), 1, 12) + "   " + cExp + CRLF)
	endif

RETURN(NIL)

FUNCTION NtxExpEval(cNtxFile, nNtxExpOff)

	/**************************************************
	*
	* Function    : NtxExpOut
	* Description : Determine the expression of an index file.
	* Details     :
	*   Determine the index expression from the header of an index file
	*   using low level file functions.
	* Parameters  :
	*   cNtxFile   <expC> : index file to use
	*   nNtxExpOff <expN> : offset of index expression from start of file
	* Returns     : <expC> as index expression, blank if unable to open file
	* Category    : NTX
	* See Also    : NtxExpOut()
	* Author(s)   : Erik Wynn
	* Date        : 91.05.01
	*
	**************************************************/

	local hNtxFile := 0
	local cExp

	if ((hNtxFile := fopen(cNtxFile, RD_ONLY)) < 1)
		? "DOS file open error."
		cExp = ""
	else
		fseek(hNtxFile, nNtxExpOff, FTOP)
		cExp := freadstr(hNtxFile, NTX_EXP_MAXLEN)
		fclose(hNtxFile)
	endif

RETURN(cExp)


This routine can be used in a number of ways:

ntxexp cust.ntx			- determine expression of ntx file cust.ntx
				send output to screen

ntxexp a*.ndx > andx.doc	- determine expressions of all ndx files
				beginning with the letter "A" in the
				current directory, send output to file andx.doc

ntxepx *.idx | sort > idx.doc	- determine expressions of all idx index files
				and sort them alphabetically by file name, sending
				results to idx.doc.


An Example of a Pipe
--------------------

A pipe can be created in Clipper by simply using both the StdIn() and StdOut()
functions.  We will create a simple pipe to convert characters into upper case.


#define MAXLNLEN 80

/* declare a buffer into which to read from STDIN */
local cInput := space(MAXLNLEN)

/* read from STDIN */
while StdIn(@cInput, MAXLNLEN) != 0

	/* send result to STDOUT */
	StdOut(upper(cInput))

	/* clear input buffer */
	cInput := space(MAXLNLEN)

end


That's all there is to it.  More complex pipes can be created using this as
an example.  


If you have any questions or comment, please leave a message on NANFORUM
user id [72020, 3152], or call me at 613-563-4407 days.

Erik.
