11. Extended Examples

You'll see two extended examples here, which use almost all of the techniques described in the previous sections of this documentation.  Both examples rely on the same application definition, as well as input and output data files.

I also provide sample data files.

You'll get a lot more out of the examples in this section if you've read all previous sections of the documentation.  You may also wish to scan the section on Awk's [s]printf syntax.

Feel free to mail your comments to me at: rog@NOSPAM_rs-freeware.org.

Return to the global table of contents
Frames mode, or No frames


1:  How to run the example

2:  Techniques Illustrated by the Examples

3:  Input files

4:  Output files for a "good" run

5:  Output files for a "bad" run

6:  The ADBAWKLIB file for example #1

7:  The global definitions file

8:  The "heart" of example #1 (ADB_Ex1.Bat)

9:  Start the processing with a lot of "paranoia"

10:  Incoporate the service charge multiplier

11:  Coding your Awk script "in line" (for example #1 only)

12:  Running the main ADB step for example #1

13:  Clean up, phase 1: split main output file apart and "un-join" (for example #1 only)

14:  Remove temporary files, and check for data errors.

15:  Off-site debugging interface

16:  Introducing example #2 (neater)

17:  Example #2: using ADB_J.Bat, the "helper" DOS batch file for joins

18:  Example #2: using ADB_1.Bat, the "helper" DOS batch file for single-file operations

19:  Finishing example #2

20:  Some other comments on the "helper" DOS batch files


1:  How to run the example

Make sure you've followed the instructions in the install.

It's particularly important that your DOS environment size has been changed, by adding the line given there to your C:\Config.Sys file.  You must reboot after doing so.

The example files are contained in ADB_Ex.Zip (this is a zip file that's contained within the main distribution file, ADB_Main.Zip).

If you wish, you can unpack it on a separate folder, but make sure that all required executables are available (see the message at the top).

The batch files which run the examples are: ADB_Ex1.Bat and ADB_Ex2.Bat

I suggest you read through the discussion of the examples before you run it.

Note that the output files from my own execution of the example are included in the zip file.  This includes the log files: ADBLog1.xx is the log file from example 1, and ADBLog2.xx is the log file from example 2.

If you run the example batch files, your log file will always be called ADBLog.xx.  Each time you run an example batch file, it will be "refreshed."

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

2:  Techniques Illustrated by the Examples

In this section, I'll go through the steps required to set ADB up for a complex application.

It's worth emphasizing that the equivalent standalone compiled executable would probably be at least 3-500 lines in any standard programming language.  Although our DOS Batch file will be close to that size, more than half the lines will be remarks.

It's also possible that you could implement this application in some traditional DBMSs, but it would be "messy."

The examples are a bit contrived: the whole point is to illustrate all of the techniques that I've outlined in the rest of this documentation.

You can skip ahead if you want to start looking at the example right now.

  • Importing Global Data

    We'll import the "service charge percent" field from a one-line record written from a standard database.  This value will be used by an Awk script to calculate the new customer balances.  The "service charge percent" will actually be converted to a DOS environmental variable.

  • Exporting Global Data

    We'll export the total accounts receivable value to a file.  This file will satisfy our (hypothetical) DBMS's requirements for importing currency fields in commas-n'-quotes format.

  • Stopping the job *immediately* if certain error conditions occur.

    We'll verify that the DOS environmental variable which contains the service charge percent actually exists

    If it doesn't we'll call ADB_ErrorFnc to stop the job "dead in its tracks" (i.e. immediately).

    This condition won't actually occur in the example data.

  • Using small tables and "phantom" fields

    We'll augment the input customer records with the sales tax percentage from each state.  The sales tax percentage will come from a table contained in an Awk script.

  • Record selection ("filtering") with an input Awk script

    The input invoices will be of two types: "open item" invoices and "on account" invoices.  Only the "open item" invoices will actually be processed.

  • Validating file data and reporting errors

    The example files will contain two kinds of errors.

    First, there will be an invoice with an invalid customer number.  We'll write that out to a file of "unmatched" invoices, and alert the user if that file isn't empty.  

    Second, we'll save a customer record which has an "invalid" state code (more precisely: a state for which there is no known sales tax percentage in the table of state sales tax percentages).  All invoices for such customers will be written out to a special file, and we'll include the code needed to alert the user if the file isn't empty.

    We'll also be writing out customers with invalid state codes to yet another file, so that they can be individually examined.

  • Multiple output files

    Our output Awk script will write multiple output files.

    One file will consist of updated customer master records (i.e. with their accounts recievable totals updated for all "open item" invoices.)

    Another file will consist of the ("open item") invoices for each customer who has a "bad" state code (i.e. one that's not in the table of known state sales tax percentages.

    Each file format will be prefixed with a single-digit "multiplexor" field.  We'll use the utility FileMumx to split the records in that file up, and send each to their proper destination files, while stripping off the "multiplexor" field.

    Our input Awk script for the customer records will be responsible for writing out customer records to a separate file, if they have invalid (unrecognized) state codes.

  • Commas-n'-quotes import and export

    We'll assume that the data in this example is coming from a standard DMBS, and also that it will be heading back into that DBMS after the appropriate processing has been completed.

  • "Paranoid" programming and archivers for off-site debugging.

    We'll assume that "Murphy's Law" applies to this case ("If anything can go wrong, it will").

    Specifically, we'll check for missing executable and input files, as well as provide all the error processing needed to handle just about every other conceivable type of failure.

    The one assumption that we'll make is that the hypothetical DBMS (the "source" of our input files) is correctly outputting numeric data in proper numeric format.

    If any detectable error occurs, we'll use zip up all the relevant files and instruct the user to send the zip as an e-mail attachment.  This eliminates the need to have a $100 PC-Anywhere license on every computer on which the system is run.

    Of course, all this extra "paranoia" will increase the amount of code that we'll have to write.  However, once you get used to thinking in terms of adding "paranoid processing," you'll discover that it becomes "second nature."

    And . . . sooner or later . . . you'll find yourself in a situation in which this level of "paranoia" actually saves you (or someone else) quite a bit of debugging time.

  • Handling "special" fields with Awk functions.

    When hypothetical DBMS outputs currency fields in commas-n'-quotes format, it formats them like this:

    
    $-##########.##
    

    In other words: it puts a dollar sign on the left, a minus sign or a space follows that, and then there's room for 10 digits, followed by a decimal point and 2 more digits.

    Because this field value isn't quoted when presented in commas-n'-quotes format, and because it can't be converted to a number with the leading dollar sign around it, we'll have to define this as a "special" field (field name ends in three consecutive underscore).  Since special fields are treated as strings, we'll need to write two Awk functions that convert it back-and-forth between the DB's "native" value and Awk's floating point format.  

    Note that our very helpful (hypothetical) DBMS designers have--in their infinite wisdom--decided to pad spaces (instead of zeros) after any minus sign that follows the dollar sign.  So most interfaces that accept numeric data won't be able to "understand" this format, even after the dollar sign is pulled off.

    Despite the apparent inelegance of this badly-designed format, the Awk functions needed to convert it to floating point (and back) will be trivial.

  • Using the ADBAWKLIB variable

    The state sales tax rate table is probably going to be useful for other processes within this family of applications.

    The same goes for the functions that convert the currency format (see previous bullet).

    We'll put both of those functions in a global file of Awk functions that gets referenced by the ADBAWKLIB DOS environmental variable.

  • Using global and step definitions files

    Because the invoice and customer files are undoubtedly used for other applications, we're going to put their layouts in a global definitions file.  Each step's input and output file definitions will be defined in a step definitions file. &nbps;This is how you would use ADB in a typical application.

  • File format "transparency"

    Like a typical DBMS (but not a compiled language), our example will not require us to keep track of every field in the input and output files.

    So the example will work fine if new fields are added to the customer file, or to the invoice file.  None of the Awk scripts will reference the precise field layouts of each file, nor the field lengths and offsets of any field (other than the size and format of the special "currency" field format, which we presume to be fixed.)

    That makes it easy to maintain the system without worrying about "unpleasant surprises," or having to recompile executables.

  • Minimizing the number of files with "code"

    There will only be three files in this example that actually contain "code."  Because the ADBAWKLIB global Awk functions are useful for other processes, we'll put those in a separate file.  And because the customer and invoice files are probably used elsewhere, we'll put those in a global definitions file.

    However, all the other Awk files and all the ADB step definitions files will be defined within the main DOS batch file that runs the job.

    Essentially, what this means is that your DOS batch file is really the equivalent of a large, compiled executable . . . except for the fact that it's both smaller and easier to debug (because you can "freeze" the processing after every step and verify that the right things have been done, without using a debugger).

  • Minimizing the amount of processing and number of steps with the "helper" DOS batch files

    Example #2 shows how to use the "helper" DOS batch files.  These will let you put the Awk functions in a separate file, even if they handle the preprocessing of input files, and the postprocessing of output files.  This approach contains all the advantages I've previously described, plus, it makes the code even "neater."

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

3:  Input files

As always, our project file suffix will be "xx".

I'll show all these examples in commas-n'-quotes format, since it's easier to read than the corresponding "fixed length" format.

This will also serve to emphasize the fact that we're drawing this data from a standard DBMS.


The customer file contains just 3 fields: the customer number, the customer's state, and the customer's balance.

Here's what the input customer file ("Cust.xx"), looks like:


"000","CA",$        100.00
"111","CA",$        200.00
"222","IN",$        300.00
"333","NY",$        400.00

As you can see, there are only 3 fields.  The customer number is the first one, the customer's state is the second, and the last is a currency field.  This represents the customer's current balance, i.e. how much the customer owes.

Note that the currency field is formatted in the DBMS's special format, with a dollar sign, followed by either a space or a minus sign, and then followed by space for 10 digits plus a decimal point and by 2 decimal places.  This particular format is especially hard to parse with traditional programming languages, because there are spaces between any minus sign and the rest of the digits on the left.  (However, some DBMS designers don't concern themselves with issues of that sort, because they assume that all pertinent operations will be done from a GUI interface.)


The invoice file is almost as simple.

Here's what "Inv.xx" looks like:


"000","100","A",$         10.00
"111","200","O",$         20.00
"111","300","O",$         30.00
"222","400","O",$         40.00
"333","500","O",$         50.00
"333","600","O",$         60.00
"444","700","O",$         70.00

The first field on the left is the customer number.  Note that customer "444" is not in the customer file!

The second field is the invoice number.

This is followed by a "code" field, which is either "A" or "O".

"A" invoices are "on account" invoices, i.e. they're from customers who are allowed to carry (positive) balances.  "O" invoices are "open item" invoices.  Customers who have open item invoices are expected to pay them ASAP.

If a customer has open item invoices, we're going to automatically apply a "service charge" of 1% to their account, based on their balance in the customer file.  This service charge won't apply to new invoices (until, perhaps, next month).

Customers with open item invoices also have to pay state sales tax on the invoice amounts.  We'll draw the amount of the sales tax from a table.

What if the customer's state isn't in the state sales tax table?

In that case, we'll have to separate that customer from the rest of the customers--probably for an error report.  We'll also have to separate their open item invoices from the rest of the invoices.  These will have to be handled in some other process.

(You might wonder about the customers who have "on account" invoices.  They're handled in another part of the system: this portion of the system is soley concerned with the customers who are invoiced on an "open item" basis.)


The users insist on the ability to modify the service charge percentage.  So we really don't want this to appear as a constant in the code.

Now let's assume that the users don't know how to use text editors . . . or perhaps that they would be shocked, appalled, and dismayed if we didn't provide them with a beautifully-formatted entry screen from their DBMS's forms interface.

So we'll draw this information from a special DBMS table which consists only of application constants.

This field is a fixed-decimal value, and it will be written out in commas-n'-quotes format, to the file SvcMult.xx.  Here's what it looks like:


1.01

For simplicity, I've defined this as a "multiplier," rather than as a percentage.  The value 1.01 simply indicates that one percent of the balance is added.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

4:  Output files for a "good" run

There are three output files that we expect to see from a run that encounters no errors.

CustNone.xx is the file for customers with no "open item" invoices.

Since this part of the system isn't concerned with them, we simply want them to go back "as is."

(Yes, some of these customers could've had open item invoics in the past, but this isn't our concern.  They've already been assessed state sales taxes and/or service charges on their balances.)


CustNew.xx is the file for customers who have had their balances modified.

These customers had open item invoices during the previous accounting period.

We're going to make two changes to them: first, we'll assess the service charge of 1% on their current balance.

We'll also assess the appropriate state sales tax on all their invoices.

So CustNew.xx, in tandem with CustNone.xx will reflect the new customer master.

Assuming, of course, that state sales tax percentages are available for all customers with open item invoices.

In our example, customer "222" is from Indiana ("IN").  We don't have a state sales tax percentage available from that state.

This customer (and all their invoices) are going to be written to one or more "bad" files (i.e. files with erroneous data).  They'll be processed in some other part of the system.


We want to put the total amount owed by all open item invoice customers back into the database.

ARTot.xx will consist of that total, in commas-n'-quotes format.

This is a currency value, so we'll have to make sure that we satisfy the DBMS's standards for writing out currencies.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

5:  Output files for a "bad" run

We've already identified two sources of problems in the data.

First, some invoices may not have valid customer numbers.  Invoice number "700" has a customer number of "444", and there's no such customer in the customer file.

Second, it's going to turn out that we have no state sales tax percentage for customers from the state of "IN" (Indiana).  Perhaps a user accidentally allowed a customer into the system from Indiana without realizing that state sales tax percentages exist only for customers from NY (New York) and CA (California).  Poor Indiana.


InvBadC.xx will contain open item invoices that have "bad" customer numbers.

InvBad.xx will contain open item invoices from customers with an invalid state code (i.e. no available state sales tax percentage).

CustBad.xx will contain customers who don't have valid state codes (again: codes for which there is no available state sales tax percentage).

If there are any records present in any of these files, we're going to alert the user at the end of the processing.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

6:  The ADBAWKLIB file for example #1

Consider what we'll need in our ADBAWKLIB file.

As you recall, ADBAWKLIB is one of ADB's environmental variables for DOS.

This variable specifies the file name of an Awk file which is always present when you invoke Awk scripts.  (Note that you need not define this variable.)

Normally, this file contains the functions that you'll need for typical "application specific" operations.

You can also use it to accumulate a set of "utility" functions that you might wish to use in a variety of applications.  For example, Awk has no built-in mechanism for comparing strings case-insensitively.


Our ADBAWKLIB file is going to contain the function that fills in the state sales tax percentage.  This is essentially similar to what I described in the Awk tutorial: it's a typical sort of function that will help you with "small" tables (tables with fewer than 1,000 values or so).

It'll also contain two functions that assist us with our hypothetical DBMS's currency format.  As you'll recall: our fictional DBMS's designers chose to output currency fields with a dollar sign on the left, followed by an optional minus sign (or a blank), and then ten digits, a period and two more digits.

So we need to write a function that will take the currency value and convert it to a number, as well as a function that will do the reverse operations.

If you want to run the example, you'll need to save this code into the file: AwkLib.Awk:


function Define_Sales_Tax_Rate_Table ()
{
         Sales_Tax_By_State["CA"]  = 1.08;
         Sales_Tax_By_State["NY"]  = 1.09;

         Sales_Tax_By_State_Init   = "y";
         Sales_Tax_By_State_BadVal = -0.01;}

function Get_Sales_Tax(StCode)
{
         if (Sales_Tax_By_State_Init == "")
            {Define_Sales_Tax_Rate_Table();}
         if (StCode in Sales_Tax_By_State)
            {return(Sales_Tax_By_State[StCode]);}
         return(Sales_Tax_By_State_BadVal);}

function Get_Curr(Amt,  Amt2)
{
         Amt2 = substr(Amt, 3, length(Amt)-1) + 0;
         if (substr(Amt, 2, 1) == "-") Amt2 = 0 - Amt2;
         return(Amt2);}

function Put_Curr(Amt,  NegFlag)
{
         NegFlag = 0;
         if (Amt < 0) {NegFlag = 1; Amt = 0 - Amt;}
         Amt = sprintf("%13.2f", Amt);
         if (NegFlag == 1) Amt = "$-" Amt;
         else Amt = "$ " Amt;
         return(Amt);}


Note that the Define_Sales_Tax_Rate_Table function is "self initializing."

Specifically: whenever Get_Sales_Tax is called, it checks to see whether the variable Sales_Tax_By_State_Init is initialized.

If Sales_Tax_By_State_Init is equal to the empty string, then Get_Sales_Tax will call Define_Sales_Tax_Rate_Table to initialize the state sales tax table.

What if the customer's state code isn't found?  In that case, we'll return Sales_Tax_By_State_BadVal, which is a negative value.

Instead of defining sales tax percentages, I've simplified the computation by specifying "multipliers".

Get_Curr takes a string argument, which is presumed to be a currency field (in the hypothetical DBMS format).  It returns a number.  Put_Curr does the reverse.  Note how careful I had to be with any minus sign.  Awk, like most pieces of software, won't ignore excess spaces between a minus sign on the left, and a series of digits.  So if I don't strip it off, I'll end up asking Awk to view something like "-   1.0" as a number.  Awk won't complain: instead it will cheerfully report that the number is zero.

You might think this is a far-fetched example . . . but in fact, many DBMS designers are relatively unconcerned with how easy it might be to process their "special field" formats.  That's because they expect you to do everything via a GUI interface that they've designed.

Local variables are listed in the parameter list for each function, but separated by excess spaces.  This is merely a convention that most Awk script writers practice.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

7:  The global definitions file

Here's ADB.xx, our global definitions file for both examples:


# Invoice File:
# -------------
FLD  Inv_Num       3     ! Invoice No.
FLD  Inv_CustNum   3     ! Cust No.
FLD  Inv_Type      1     ! 'O' or 'A'
FLD  Inv_Amt___   15     ! Currency field

FSET Inv_Srt Inv_CustNum, Inv_Num END
FSET Inv_Fil @Inv_Srt; Inv_Type;
             Inv_Amt___ END

# Customer File:
# --------------
FLD  Cust_Num        3   ! Cust No.
FLD  Cust_St         2   ! Cust State
FLD  Cust_Bal___    15   ! Currency
FLD  Cust_TxMul__    9 4 ! Sales Tax Multiplier

# Ordinary Customer File
FSET Cust_Srt   Cust_Num END
FSET Cust_Fil   @Cust_Srt; Cust_St;
                Cust_Bal___ END

# Customer File with State Sales Tax Multiplier
FSET Cust_FilTx @Cust_Fil; Cust_TxMul__ END

# Invoice+Cust (with tax)
FSET Cust_Inv_Fil @Cust_FilTx; @Inv_Fil END

There shouldn't be too many surprises in this series of definitions, but let me just mention two points.

First, there's a special layout called Cust_FilTx_, which contains the customer fields, plus the sales tax multiplier.

The multiplier is what's going to come from our state sales tax table, and it's a "phantom" field in the input customer file.

Specifically: it's not actually present there, but we're going to "pretend" that it is, by telling ADB that it does indeed exist, and asking ADB to pad extra spaces on the input record.

We'll write an input Awk script for the customer file which will actually put this field in.

Second, note the layout called Cust_Inv_Fil.

I've used the "@" construct to indicate that it's built from combining the fields in Cust_FilTx (the customer file with the "phantom" sales tax multiplier) . . . and Inv_Fil (the invoice file).

Of course, this is the "join" file which is produced when we match customer records with invoice records (on customer number).


What if additional fields were added to the customer or invoice files?

No problem: because we used "@", we won't have to make any additional changes.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

8:  The "heart" of example #1 (ADB_Ex1.Bat)

When all the "smoke and mirrors" are cleared away, the main part of this example will be done in just one ADB processing step.

This step will consist of a "join" between the customer and invoice files.


First, we have to define an Awk script for the invoice file.

As you recall, invoices are of two types: "open item" invoices (the invoice type is "O"), and "on account" ' invoices (invoice type is "A").  We're only interested in the "O" (open item) invoices.

So what we want to do is to filter out the "A" (on account) invoices.

This is a fairly typical case in which you can use an "input" Awk script to "select" records:


function ADB_Main()
{        if (Inv_Type == "O") ADB_WriteFnc();
}


Next, we'll define an input Awk script for the customer file.

This script will perform two important operations.

First, it will fill in the state sales tax multiplier.

For customers who don't have a valid state code, the tax multiplier field will be equal to Sales_Tax_By_State_BadVal.

We'll write those customers out to the file CustBad.xx, using ADB's system command.  This command lets us do anything that we could do from the DOS command line.

What we do, in particular, is to write the customer record to that file.

Now how ADB_BldOutLineFnc is called, to build the proper (fixed-length) format for customer records.

Since we know for sure that the DOS environmental variable ADBPROJ is available, we retrieve it's value.  That in turn becomes the file extension.


function ADB_Main( Cmd)
{        Cust_TxMul__ = Get_Sales_Tax(Cust_St);
         if (Cust_TxMul__ == Sales_Tax_By_State_BadVal)
            {Cmd = sprintf("Echo %s>>CustBad.%s",
               ADB_BldOutLineFnc(), ENVIRON["ADBPROJ"]);
             system(Cmd);}
         ADB_WriteFnc();
}

There's a fair amount going on here--within just one line of code--so let's examine it more closely.

ADB_BldOutLineFnc is going to return the fixed-length format line for a given customer.  In this example, the customer with an unrecognized state code is "222", and the customer record looks like this (in fixed-length format):


222IN$        300.00  -0.0100

Note that the sales tax multiplier is precisely the negative value assigned to customers with unrecognized state codes, in the function Get_Sales_Tax.

The array access ENVIRON["ADBPROJ"] is just going to return the value of the DOS environmental variable called ADBPROJ.  Of course, this is the project file suffix, xx.

The sprintf statement will return the following Echo command (assigned to the local variable Cmd):


@Echo 222IN$        300.00  -0.0100>> CustBad.xx

The system function will execute this as if it was run from the DOS command line.  The result is that customers will be writen to CustBad.xx, if they have bad state codes.

Let's not forget that we still need to call ADB_WriteFnc, in order to make sure that all customers are brought into the "join" (AKA the "matching") between customers and invoices.

This is because we still need to identify invoices for customers who have bad state codes.  These invoices will have to be written out to the file InvBad.xx.


We're finally ready for the "whole enchilada."

This is the massive Awk script that we're going to use for the M&T output file, i.e. the file that's outputted from the matching of customer and invoice records.  It's all of 35 lines long (not counting blank lines):


function ADB_BOJ()
{        G_Tot_AR      = 0;
         G_LastCust_No = "NONE";
         if ("SVC_MULT" in ENVIRON)
            SVC_MULT = ENVIRON["SVC_MULT"];
         else ADB_ErrorFnc("DOS Env. Var: \"SVC_MULT\" undefined!");}

function ADB_EOJ( Cmd)
{        Wrt_Cust_If_OK();
         Cmd = sprintf("Echo %s>TotAR.%s\n",
                       Put_Curr(G_Tot_AR), ENVIRON["ADBPROJ"]);
         system(Cmd);}

function ADB_Main()
{
         if (G_LastCust_No == Cust_Num)
            {if (G_LastCust_OKFlag == 0) Wrt_InvBad();
             else Add_Inv();
             return;}

         Wrt_Cust_If_OK();
         G_LastCust_No = Cust_Num;

         if (Cust_TxMul__ > 0)        # Valid cust sales tax mult?
            {G_LastCust_OKFlag =  1;
             G_LastCust_Bal    =  Get_Curr(Cust_Bal___) * SVC_MULT;
             Add_Inv();}
         else {G_LastCust_OKFlag = 0; Wrt_InvBad();}}

function Wrt_InvBad() {printf("0%s\n", ADB_BldOutLineFnc());}

function Wrt_Cust_If_OK()
{        if ((G_LastCust_OKFlag == 1) && (NR != 1))
            {G_Tot_AR += G_LastCust_Bal;
             printf("1%s\n", LastCust_OutLine);}}

function Add_Inv( Amt)
{        Amt             = Get_Curr(Inv_Amt___);
         G_LastCust_Bal += Amt*Cust_TxMul__;
         Cust_Bal___     = Put_Curr(G_LastCust_Bal);
         LastCust_OutLine= ADB_BldOutLineFnc();}

ADB_BOJ initializes the G_Tot_AR global, which will eventually be written out (the total accounts recievable from open customers with open item invoices).  Note also that it checks for the DOS environmental variable called SVC_MULT.  If that variable isn't present, it will call ADB_ErrorFnc, to stop the processing.  ADB will then report an error (signalled by the Awk script).  This variable, SVC_MULT, is our service charge multiplier.  As you may recall, it comes from a global table in the DBMS that the users can modifuy.  Later, we'll see how it "travels" from the file SvcMult.xx into the DOS variable SVC_MULT.

ADB_EOJ actually writes the total accounts receivable variable G_Tot_AR to the file TotAR.xx (and it does so in with DBMS's own currency format).

The variable LastCust_OutLine serves as a kind of "record structure holder" the previous customer.  As you can see, ADB_BldOutLineFnc is used in Add_Inv to store the entire customer/invoice join record.  This is an excellent example of a technique that you can use to augment a record with accumulated totals in a one-step process.

There are two other output files (besides the totals file TotAR.xx.)

Wrt_BadInv outputs records for customers who lack a valid state code.  These records are in the Cust_Inv_Fil ("joined") format, but they're prefixed with a "0".

Wrt_Cust_If_OK writes out join records as soon as there are no more invoices for a customer, but only for customers with valid state codes.  These records are also in Cust_Inv_Fil ("joined") format, but they're prefixed with a "1"."

The utilitity FileMux will be used to split the output file into its two parts. &nbps;We'll need one step of ADB to "unjoin" each of the parts.  Although this seems like a minor annoyance, the required code consists of just 2 ADB definitions and one execution of ADB.s

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

9:  Start the processing with a lot of "paranoia"

The first few steps of this batch file are essentially independent of this example.  So you can easily copy them for your own ADB processes (with minor modifications):


@Rem ================================
@Rem 1. Set Variables; clear log file
@Rem ================================
@Rem
@Rem These are pretty standard
@Rem
@Set ADBPROJ=xx
@Set S=%ADBPROJ%
@Set SA=%ADBPROJ%A
@Set L=ADBLog.%S%
@Set EMAIL=your.name@yourdomain.com
@Rem
@Rem Special for this application
@Rem
@Set ADBAWKLIB=AwkLib1.Awk
@Set ADBJOBNAME=%0
@Set ADBPROJNAME=Accounts Receivable Open Item Invoice Update Example #1
@Rem
@Rem Announce our presence, delete log file and write project name into it
@Rem
@If Exist %L% Del %L%
@ErrMsg -E %0 PROJECT: %ADBPROJNAME%>> %L%
@ErrMsg -E %0 ###STEP### 1 in progress . . .>> %L%

Here, we simply "short form" variables for things like the project file suffix (%S%), the log file (%L%), the suffix for Awk files (%SA%), and so forth.  The %EMAIL% variable is used to tell the user where to write, if there are errors.



@Rem
@Rem =========================
@Rem 2. Paranoid check: part I
@Rem =========================
@ErrMsg -E %0 ###STEP### 2 in progress . . .>> %L%
@Rem
@If Not Exist FileStat.Exe Echo FileStat.Exe not on current folder>> %L%
@If Not Exist FileStat.Exe Goto EXE_ERR
@Rem
@Rem ==========================
@Rem 3. Paranoid check: part II
@Rem ==========================
@ErrMsg -E %0 ###STEP### 3 in progress . . .>> %L%
@Rem
@FileStat PkZip.Exe Repl.Exe StrRepl.Exe >> %L% < NUL
@If ErrorLevel 1 Goto EXE_ERR
@FileStat ADB.Exe ErrMsg.Exe FileMux.Exe >> %L% < NUL
@If ErrorLevel 1 Goto EXE_ERR
@Goto FILE_CHK
:EXE_ERR
@Echo Please e-mail %L% to %EMAIL% as an attachment
@Pause
@Goto DONE

Step 2 does nothing more than to ensure that FileStat.Exe is on the current folder.  Why?  The answer is that FileStat is critical for determining whether or not other executables (and files) exist.  If it's not present, any number of things can go wrong, and we'll have no way of knowing it.

Note how ErrMsg is being used, to "broadcast" messages to the user, while simultaneously sending them to the log file.

Step 3 ensures that all additional executables are present.

We direct the output messages of FileStat to the log file.  If problems result, we don't bother explaining them to the user, but instead simply take the user to the label EXE_ERR, where the user will be asked to send the log file to the programmer.  It's not necessary in this case to zip up any data files, since any errors encountered up to this point will have to do with the availability of executables.

In step 3, ErrMsg gets run with the -E parm, which sends the output both the standard output (and thence to the log file) as well as to standard error (which always goes to the terminal).  Even if ErrMsg.Exe isn't available on the DOS PATH, that won't be a problem (that's precisely the sort of error that we'll "trap" in step 3.)



@Rem
@Rem ===================================
@Rem 4. Check for input and output files
@Rem ===================================
@ErrMsg -E %0 ###STEP### 4 in progress . . .>> %L%
@Rem
:FILE_CHK
@FileStat !FileStat.%S% >> %L% < NUL
@If ErrorLevel 1 Goto FAIL
@Rem  ------------- Input files for this run -------------------------
@Echo ADB.%S%       ADB definitions file                 >> FileStat.%S%
@Echo AwkLib1.Awk   Awk library file                     >> FileStat.%S%
@Echo Inv.%S%       Input inv file                       >> FileStat.%S%
@Echo Cust.%S%      Input cust file                      >> FileStat.%S%
@Echo SvcMult.%S%   Input svc. charge multiplier         >> FileStat.%S%
@Rem  ------------- Output files for this run ------------------------
@Echo !CustBad.%S%  Custs w/ bad st. codes (cust format) >> FileStat.%S%
@Echo !CustNew.%S%  Updated custs (in cust format, CQ)   >> FileStat.%S%
@Echo !CustNone.%S% Custs w/ no inv.s, OK st. codes (CQ) >> FileStat.%S%
@Echo !InvBad.%S%   Invs: bad st. code custs (CQ)        >> FileStat.%S%
@Echo !InvBadC.%S%  Invs w/ no matching custs (CQ)       >> FileStat.%S%
@Echo !TotAR.%S%    A/R total in DB's format             >> FileStat.%S%
@Rem  ------------- Temporary files for this run -----------------------
@Echo !CustInv.%S%  Joined custs & inv.s                 >> FileStat.%S%
@Echo !CustNew0.%S% Updated cust recs, joined w/ inv.s   >> FileStat.%S%
@Echo !InvBad0.%S%  Invs for bad st. code custs (joined) >> FileStat.%S%
@Echo !SvcMult.Bat  Batch file assigns svc. chg. var.    >> FileStat.%S%
@Rem  ------------- Awk-related stuff ----------------------------------
@Echo !ReplAwk.%S%  String repl file for awk scripts     >> FileStat.%S%
@Echo !Inv.%SA%     Inv input awk script                 >> FileStat.%S%
@Echo !Cust.%SA%    Cust input awk script                >> FileStat.%S%
@Echo !CustInv.%SA% Output awk script                    >> FileStat.%S%
@Rem
@FileStat < FileStat.%S% -Q >> %L%
@If ErrorLevel 1 Goto FAIL

Since we're giving FileStat a file of file names, we can also document these files at the very same time.

Remember, FileStat will stop looking at a file name whenever it encounters the first space (because it doesn't take Windows 9x file names).

So we're killing two birds with one stone: we're explaining the nature and purpose of each file involved in this run . . . at the same time as we check for its (non)existence (and/or erase it).

If you wanted to be really careful about this process, you could assign DOS variable names to all such files; however I haven't bothered in this example.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

10:  Incoporate the service charge multiplier

Here's a very simple step: this one just takes the service charge multiplier value in SvcMult.xx and turns it into a DOS variable assignement.

After Repl runs, the batch file SvcMult.Bat looks like this: (Pre0) Set SVC_MULT= 1.01 (Pre9)

Then we use the DOS Call command to run that bath file.  This will assign the variable value, which is the same as putting it into the DOS environment.


@Rem
@Rem ===============================================================
@Rem 5. Build and run batch file to assign Service Charge Multiplier
@Rem ===============================================================
@ErrMsg -E %0 ###STEP### 5 in progress . . .>> %L%
@Repl -Q "$@Set SVC_MULT=" @SvcMult.%S%> SvcMult.Bat
@If ErrorLevel 1 Echo Repl failed in step 5>> %L%
@If ErrorLevel 1 Goto FAIL
@Call SvcMult.Bat

The function ADB_BOJ in the last Awk script shown above is going picks out the DOS variable SVC_MULT from environment (and stops the processing by calling ADB_ErrorFnc if it can't find it).

You can use this technique to take values from a table of constants in a standard DBMS and make them available to your Awk scripts.

Of course if they're string values, then you'll have to do a little work with Awk's built-in substr and length functions, in order to extract the inner value.

If necessary, you can also call ADB's built-in function called ADB_StripRtSp to strip off any trailing spaces on the right.


This method won't work if the values you wish to "import" contain less-than signs, greater-than signs, or percent signs.

In the unlikely event that you need that kind of power, you can use StrRepl to map these characters to replacement strings, and then convert them back, within your Awk script.  (See the very next section.)

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

11:  Coding your Awk script "in line" (for example #1 only)

The next two steps demonstrate how to code Awk scripts inside your DOS batch files.

This makes it easy for the reader of your DOS batch file to see "everything at once" without having to open multiple windows.

(On the other hand, it's awkward: in example #2, I demonstrate how to use the "helper" batch files for a more elegant result.)

In step #6 (immediately below), I use Repl to build a file of "change list requests" for StrRepl.  These will map "_lt_" to the less-than sign, "_gt_" to the great-than sign, and "_pct_" to the percent sign.


@Rem
@Rem =====================================
@Rem 6. Build StrRepl file for Awk Scripts
@Rem =====================================
@ErrMsg -E %0 ###STEP### 6 in progress . . .>>%L%
@Rem
@Repl -Q "$I _lt_">ReplAwk.%S%
@Repl -Q "$N <">>ReplAwk.%S%
@Repl -Q "$I _gt_">>ReplAwk.%S%
@Repl -Q "$N >">>ReplAwk.%S%
@Repl -Q "$I _pct_">>ReplAwk.%S%
@Repl -Q "$N %%">>ReplAwk.%S%

Note that I used two percent signs in a row, in order to produce one (this stops DOS from regarding the "%%" as a variable name).

If you wish to use this technique, you can just keep a file on your system that contains these commands (instead of dynamically building that file every time you create a DOS batch process with ADB):


O _lt_
N <
O _gt_
N >
O _pct_
N %


Finally, I just slap my Awk script into the destination file Cust.xx, and run StrRepl to do the character replacments.


@Rem
@Rem ==============================
@Rem 7. Build input cust Awk Script
@Rem ==============================
@ErrMsg -E %0 ###STEP### 7 in progress . . .>>%L%
@Rem
@Echo function ADB_Main( Cmd)>>Cust.%SA%
@Echo {   Cust_TxMul__ = Get_Sales_Tax(Cust_St);>>Cust.%SA%
@Echo     if (Cust_TxMul__ == Sales_Tax_By_State_BadVal)>>Cust.%SA%
@Echo        {Cmd = sprintf("Echo _pct_s>>CustBad._pct_s", >>Cust.%SA%
@Echo          ADB_BldOutLineFnc(), ENVIRON["ADBPROJ"]);>>Cust.%SA%
@Echo         system(Cmd);}>>Cust.%SA%
@Echo     ADB_WriteFnc();>>Cust.%SA%
@Echo }>>Cust.%SA%
@StrRepl ReplAwk.%S% /Cust.%SA% >NUL
@If ErrorLevel 1 Echo StrReplFailed in step 6>>%L%
@If ErrorLevel 1 Goto FAIL
@Rem

This method is practical for relatively short Awk scripts.  As you can see, it's tricky to read, because the percent sign is replaced (another alternative is just to use double percent signs, however this is also difficult to read).

In the example, I've used this strategy for all three Awk scripts--even the relatively long one.  Only the ADBAWKLIB script is kept as a separate file.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

12:  Running the main ADB step for example #1

So far, we've done all the required "paranoid processing" to ensure that each input file is present and every output file is erased (and writeable).  We've also built our Awk scripts and made sure that the global "service charge multiplier" value is accessible via the DOS SVC_MULT variable.

It's now time to run the main ADB step, which will be quite simple (in fact, all the remaining steps are trivial):


@Rem
@Rem =======================================================
@Rem 10. Actually run ADB (build I/O file definitions first)
@Rem =======================================================
@ErrMsg -E %0 ###STEP### 10 in progress . . .>>%L%
@Create0 CustBad.%S%
@Rem
@FileStat !Step.%S% >>%L% <NUL
@If ErrorLevel 1 Goto FAIL
@Echo MAST  Cust_FilTx Cust_Srt SORT AWK CQ>>Step.%S%
@Echo TRANS Inv_Fil    Inv_Srt SORT AWK CQ>>Step.%S%
@Echo M&T   Cust_Inv_Fil AWK END>>Step.%S%
@Echo M-T   Cust_Fil CQ END>>Step.%S%
@Echo T-M   Inv_Fil CQ END>>Step.%S%
@Rem
@Rem Next line split into two for ease of display:
@Set ADBSTEPNAME=Step10
@ADB -MASTcust -TRANSinv -M&Tcustinv
-M-Tcustnone -T-Minvbadc -LAXPRE -STEPstep -Q
@If ErrorLevel 2 Goto FAIL
@Rem

(Note: The actual ADB execution line has been "wrapped" to prevent the code from forcing this HTML page to be too wide.)

All I really had to do here was to build my step definitions file (for the input and output file definitions).

Note that unmatched customer ("master") records are sent to the CustNone.xx output file: these are the customers with no open item invoices.

Invoices without customer records are sent to InvBadC.xx.

Because the sales tax percentage field is a "phantom" field in the customer file (i.e., it doesn't actually exist in the input to ADB), we had to code -LAXPRE to request that "short" records be padded with spaces.

Since this will generate warning messages, ADB will return 1, and we need to do an If ErrorLevel 2 check (because a return code of 2 means a catastrophic error).

We coded the ADBSTEPNAME variable, in order to make the log file easy to read.

And finally, we used the "-Q" operand on ADB to keep the user from being overwhelmed by all ADB's log file diagnostic messages.

In fact, throughout the example, I'm very careful to ensure that the user sees nothing but the "broadcast" messages at the beginning of each step.

This lets the user know that the processing is running, but it also doesn't distract the user, so that s/he will ignore genuinely important error messages (if and when errors occur.)

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

13:  Clean up, phase 1: split main output file apart and "un-join" (for example #1 only)

Remember that the output CustInv file consists of joined customer and invoice records.  Records that begin with a "0" are invoice records for customers who have bad state codes (no sales tax multiplier is available to them).  These records were written by the function Wrt_InvBad.

Records that begin with a "1" are updated customer records, for those customers who have open item invoices.

So we need to split that file up into its two record types, and then we need to "unjoin" the joined records.

This is fairly trivial:


@Rem =============================================================
@Rem 11. Run FileMux to split up custinv
@Rem     Records beginning with "0" are invoices for customers who
@Rem      have unrecognized state codes.
@Rem     Records beginning with "1" are updated customer records.
@Rem =============================================================
@ErrMsg -E %0 ###STEP### 11 in progress . . .>>%L%
@Create0 X0.%S%
@Create0 X1.%S%
@FileMux -INcustinv.%S% -EXT%S% -COL1 -LEN1 -MAX2 -NOKEEP >>%L%
@If ErrorLevel 1 Goto FAIL
@Ren X0.%S% InvBad0.%S%
@Ren X1.%S% CustNew0.%S%
(Note that the default for FileMux is to create output file names that have a prefix of "x".  We used that very default option here, but we made sure to create them both as zero-length files, first.  This ensures that both files exist, even if there are no records that start with "0" and/or no records that start with "1").

One of ADB's weaknesses is the need to "un-join" records, although it only requires 3 lines of code (at a bare minimum) for each file that has to be "un-joined".

However, we write a few extra lines, in order to keep the user informed of the job's progress, and to ensure that no time will be wasted if there are problems, and someone has to look at the log file.


@Rem
@Rem =========================================================
@Rem 12. Run ADB to "un-join" updated customers, convert to CQ
@Rem =========================================================
@ErrMsg -E %0 ###STEP### 12 in progress . . .>>%L%
@FileStat !Step.%S% >>%L% <NUL
@If ErrorLevel 1 Goto FAIL
@Echo TRANS Cust_Inv_Fil Cust_Srt NONE>>Step.%S%
@Echo T-M   Cust_Fil CQ END>>Step.%S%
@Set ADBSTEPNAME=Step12
@ADB -TRANScustnew0 -T-Mcustnew -STEPstep -Q
@If ErrorLevel 1 Goto FAIL
@Rem
@Rem ==================================================================
@Rem 13. Run ADB to "un-join" invoices for custs w/ illegal state codes
@Rem     We also convert to CQ
@Rem ==================================================================
@ErrMsg -E %0 ###STEP### 13 in progress . . .>>%L%
@FileStat !Step.%S% >>%L% <NUL
@If ErrorLevel 1 Goto FAIL
@Echo TRANS Cust_Inv_Fil Cust_Srt NONE>>Step.%S%
@Echo T-M   Inv_Fil CQ END>>Step.%S%
@Set ADBSTEPNAME=Step13
@ADB -TRANSinvbad0 -T-Minvbad -STEPstep -Q
@If ErrorLevel 1 Goto FAIL

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

14:  Remove temporary files, and check for data errors.

Note that we continue to document the nature and purpose of our files even as we're checking to make sure that they get cleaned up.


@Rem
@Rem ================================
@Rem 14. Clean up all temporary files
@Rem ================================
@ErrMsg -E %0 ###STEP### 14 in progress . . .>>%L%
@FileStat !FileStat.%S% >>%L% <NUL
@If ErrorLevel 1 Goto FAIL
@Rem  ------------- Temporary files for this run -----------------------
@Echo !CustInv.%S%  Joined custs & inv.s                 >>FileStat.%S%
@Echo !CustNew0.%S% Updated cust recs, joined w/ inv.s   >>FileStat.%S%
@Echo !InvBad0.%S%  Invs for bad st. code custs (joined) >>FileStat.%S%
@Echo !SvcMult.Bat  Batch file assigns svc. chg. var.    >>FileStat.%S%
@Echo !Step.%S%     Step definitions file                >>FileStat.%S%
@Rem  ------------- Awk-related stuff ----------------------------------
@Echo !ReplAwk.%S%  String repl file for awk scripts     >>FileStat.%S%
@Echo !Inv.%SA%     Inv input awk script                 >>FileStat.%S%
@Echo !Cust.%SA%    Cust input awk script                >>FileStat.%S%
@Echo !CustInv.%SA% Output awk script                    >>FileStat.%S%
@FileStat <FileStat.%S% -Q >>%L%
@If ErrorLevel 1 Goto FAIL
@FileStat -Q !FileStat.%S% <NUL
@Rem

Now we need to alert the user, just in case there are customers with bad state codes, and/or invoices with bad customer numbers:


@Rem
@Rem ================================
@Rem 15. Check for errors in the data
@Rem ================================
@ErrMsg -E %0 ###STEP### 15 in progress . . .>>%L%
@FileStat /InvBadC.%S% <NUL >NUL
@If ErrorLevel 1 ErrMsg *ERROR* open item invoices w/ bad cust codes!!
@FileStat /CustBad.%S% <NUL >NUL
@If ErrorLevel 1 ErrMsg *ERROR* customers w/ bad state codes!!
@FileStat /InvBad.%S%  <NUL >NUL
@If ErrorLevel 1 ErrMsg *ERROR* open item invoices for such custs!
@Rem
@ErrMsg -E %0 ###STEP### 15 is done.  No S/W errors.>>%L%
@Goto DONE

Note how FileStat and ErrMsg work together here.  The slash in front of the InvBadC.xx, CustBad.xx and InvBad.xx files is used to tell FileStat that these files should exist, and be of length 0.

If they aren't, FileStat returns 1, and the If ErrorLevel 1 statement will execute ErrMsg.

ErrMsg will then broadcast the error message to the user, and ask the user to hit a space (to acknowledge it).

It's up to the users to actually fix the data.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

15:  Off-site debugging interface

As you may recall, if ADB or any of the utilities finds an error, we're going to branch to the label FAIL:


@Rem
@Rem ================================================================
@Rem FAIL: Ordinary failure: zip files up and send them to programmer
@Rem ================================================================
@Rem
:FAIL
@ErrMsg -E Run Failed . . . please wait while we zip up the files >>%L%
@Pause
@If Exist ERROR-%S%.Zip Del ERROR-%S%.Zip
@PkZip -aex -= ERROR-%S%.Zip *.%S%* $*.%S%* SvcMult.Bat
@ErrMsg **ERROR** Please E-mail ERROR-%S%.Zip to %EMAIL%
@Goto DONE

This is an example of how to use an archiver to handle off-site debugging.

The variable %EMAIL% gives the user the address of the programmer.

Most users should be able to take it from there, although you probably should make sure that they understand how to send e-mail attachments, and how to cut and paste (or type) a file name which is to be sent as an attachment.  You'd be surprised by the fraction of Windows users who don't know how to do these sorts of things.

Beats a $100 PC-Anywhere license for every computer on which you want to run the system . . . doesn't it?

(And no, PkZip is not freeware, it's shareware.  However I do supply a freeware archiver called Zoo with ADB.


Finally, for "good ecology," we're going to clear all the DOS variables that we assigned for this run:


@Rem
@Rem ============================
@Rem DONE: clean up DOS variables
@Rem ============================
@Rem
:DONE
@Set ADBPROJ=
@Set S=
@Set SA=
@Set L=
@Set Z=
@Set D=
@Set SVC_MULT=
@Set EMAIL=
@Set ADBAWKLIB=
@Set ADBJOBNAME=
@Set ADBPROJNAME=
@Set ADBSTEPNAME=

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

16:  Introducing example #2 (neater)

Example #2 (in ADB_Ex2.Bat is a lot "neater" than example #1, and it demonstrates how to use two of the three "helper" DOS batch files.

The main thing that's ugly about example #1 is that we had to code the Awk functions inside the DOS batch file.  It would be a lot easier if we could put them in the Awk library.

It would also be a lot nicer if we could set a series of DOS variables that defined certain generic operations, and let a subsidiary set of DOS batch files do the "ugly" work.

With that in mind, let's take a look at three new variables that are set in step #1 of example 2:


@Rem These three variables are required by the "helper" batch files:
@Rem The TRACE variable will show you exactly what's going on, step-by-step,
@Rem  if it's set to 1.  I've set it to 0, in order to make things less
@Rem  confusing.
@Set TRACE=0
@Rem
@Rem This second variable will initiate an automatic check for executables,
@Rem  as well as the amount of available DOS environment space, if it's
@Rem  set to 0.  Set to 1 if you wish to block these checks.
@Set NOENVCHK=0
@Rem
@Rem This last variable says that "helper" batch files will append to the
@Rem  default ADB log file (instead of deleting it).  If you want to append,
@Rem  you don't even need to set the value: but you do have to set it to
@Rem  0 if you want to delete it.
@Set APPEND_LOG=1

These variables are required by the "helper" DOS batch files.

Note that they aren't cleared automatically at the end of these files, so you'll have to "manually" clear them after a long job.  Example #2 (ADB_Ex2.Bat) does precisely that.

When I'm giving a system to a client, I typically let set NOENVCHK variable to 1, for the first call to one of the "helper" DOS batch files ... and then I reset it to 0, for later steps.

Of course, it won't hurt to leave it at 0 all the time: the processing overhead here is fixed, and won't change, no matter how much data has to be moved around.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

17:  Example #2: using ADB_J.Bat, the "helper" DOS batch file for joins

Example #2 (in ADB_Ex2.Bat is a lot "neater" than example #1, and it demonstrates how to use two of the three "helper" DOS batch files.

The main thing that's ugly about example #1 is that we had to code the ADB_Main, ADB_BOJ, and ADB_EOJ function inside the DOS batch file.  And then we had to copy them to other files.

Wouldn't it be nicer if we could keep them in the Awk library file?

All we have to do, is to find some way to associate those functions with a particular input or output file to ADB.


To see why this is a good strategy, open up AwkLib2.Awk, in one window, and ADB_Ex1.Bat in another.

For example, compare step #7 of ADB_Ex1.Bat with the function CustPrep_Main in AwkLib2.Awk.

They're exactly the same, right?  But how do we tell ADB that this function is the "ADB_Main" function for the input customer master?


@Rem
@Rem ======================================================================
@Rem 6. Run the "helper" file ADB_J.Bat, to join customer and invoice files
@Rem ======================================================================
@ErrMsg -E %0 ###STEP### 6 in progress . . .>gt; %L%
@Set ADBSTEPNAME=Step6
@Create0 CustBad.%S%
@Rem
@Rem Here are the definitions for the input customer master
@Rem
@Set IN_MFNAME=Cust
@Set IN_MLAY=Cust_FilTx
@Set IN_MSRT=Cust_Srt
@Set IN_MSRTFLAG=1
@Set IN_MAWKFNC=CustPrep_Main
@Set IN_MCQFLAG=1

Voila!  We simply set a DOS variable (IN_MAWKFNC, in the above example), to the name of the function within the Awk library file!


What about the other variables that are shown above?

In fact, these ther variables say the same thing about the input customer file (the master file) as this statement to ADB:


MAST Cust_FilTx Cust_Srt SORT AWK CQ

The only difference is that we've also (in effect), defined the Awk file for ADB.  "We" didn't do that ... the "helper" DOS batch file will do that: by creating a temporary Awk file with the appropriate name.  This Awk file will have just one line: function ADB_Main() {CustPrep_Main();}

This is one major advantage of using the "helper" DOS batch files: they'll do the unpleasant chores for you.


Another advantage to using the "helper" DOS batch files is that you can tell ADB what to do, by a series of DOS variable assignments: this makes the processing more readable.

Let's take a look at how the "transaction" file (the invoice file, in this case), and the output files are defined:


@Rem
@Rem Here are the definitions for the invoice trans. file
@Rem
@Set IN_TFNAME=Inv
@Set IN_TLAY=Inv_Fil
@Set IN_TSRT=Inv_Srt
@Set IN_TSRTFLAG=1
@Set IN_TAWKFNC=InvPrep_Main
@Set IN_TCQFLAG=1
@Rem This definition applies to both input files
@Set IN_LAXPREFLAG=1
@Rem
@Rem Here are the output files ... starting with the unmatched masters (custs)
@Rem Note that the output file must have the suffix! (extension)
@Rem
@Set OUT_MFNAME=CustNone.%ADBPROJ%
@Set OUT_MLAY=Cust_Fil
@Rem
@Rem Here are the unmatched invoices
@Rem Note that the output file must have the suffix! (extension)
@Rem
@Set OUT_TFNAME=InvBadC.%ADBPROJ%
@Set OUT_TLAY=Inv_Fil
@Rem
@Rem Here are the output joined cust&invoice records
@Rem Note that the output file must have the suffix! (extension)
@Rem
@Set OUT_JFNAME=CustInv.%ADBPROJ%
@Set OUT_JLAY=Cust_Inv_Fil
@Rem Don't use CQ on these (CQ is the default)
@Set OUT_JCQFLAG=0
@Set OUT_JAWKFNC=CustInv_Main
@Set OUT_JAWKFNCBOJ=CustInv_BOJ
@Set OUT_JAWKFNCEOJ=CustInv_EOJ

One thing to notice here is that the "joined" output file has 3 different Awk function defintions: one corresponds to the ADB_BOJ function (that's OUT_JAWKFNCBOJ, a similar one corresponds to the EOJ function, and of course OUT_JAWKFNC is the ADB_Main function.

Note also that the output joined file name is defined ... and this file name has an extension (i.e., a part after the dot).  (See the variable OUT_JFNAME).  This is the case for all the "helper" batch files.  The reason for this is that you may want another "extension."


There's not much more work to do, before we call the "helper" batch file:


@Rem
@Rem Now we call the "helper" batch file, ADB_J.Bat
@Rem
@Call ADB_J.Bat
@If Exist ADB_J.Err Echo ADB_J.Bat failed in step %ADBSTEPNAME% >> %L%
@If Exist ADB_J.Err Goto FAIL

Note that the "helper" batch files create an error file.  This will tell you exactly what went wrong.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

18:  Example #2: using ADB_1.Bat, the "helper" DOS batch file for single-file operations

ADB_1.Bat is the "helper" DOS batch file for an ADB call that involves a single file.

In example #1, we had two such cases: step 12 "un-joined" updated customers from the combined customer/invoice file, and step 13 "un-joined" invoices for customer with illegal state codes from the combined customer/invoice file.

Let's take a look how example #2 handles the first case:


@Rem =============================================================
@Rem 8. Run the "helper" batch file ADB_1.Bat to "un-join" updated
@Rem     customers, and convert to CQ
@Rem =============================================================
@ErrMsg -E %0 ###STEP### 8 in progress . . .>> %L%
@Set ADBSTEPNAME=Step8
@Rem
@Rem Here's the input file (customer/invoice join)
@Rem
@Set IN_FNAME=CustNew0
@Set IN_LAY=Cust_Inv_Fil
@Set IN_SRT=Cust_Srt
@Set IN_CQFLAG=0
@Rem This says that files will be treated as trans files, i.e. dups OK
@Set UNIQFLAG=0
@Rem
@Rem Here's the output file (customers with updated A/R totals)
@Rem
@Set OUT_FNAME=CustNew.%ADBPROJ%
@Set OUT_LAY=Cust_Fil
@Rem
@Call ADB_1.Bat
@If Exist ADB_1.Err Echo ADB_1.Bat failed in step %ADBSTEPNAME% >> %L%
@If Exist ADB_1.Err Goto FAIL

As you can see, this step contains a lot more lines than the the corresponding step (#12) in ADB_Ex1.Bat.

However, it's a lot easier to read.

The other thing to notice is that the output file name contains an extension.  (i.e., a part after the dot).  So, if you wanted to move it to a different subsystem, you wouldn't have to go to the trouble of copying it over, or renaming it  (and/or checking for errors).

This second "helper" DOS batch file (ADB_1.Bat), allows you to set all the usual variables: I suggest you take a closer look at it.

For example, you can specify an ADB_BOJ and an ADB_EOJ function.  These will be taken from the file name that's specified by the ADBAWKLIB variable.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

19:  Finishing example #2

Finally, for "good ecology," we're going to clear all the DOS variables that we assigned for this run:


@Set ADBPROJ=
@Set S=
@Set L=
@Set SVC_MULT=
@Set EMAIL=
@Set ADBAWKLIB=
@Set ADBJOBNAME=
@Set ADBPROJNAME=
@Set ADBSTEPNAME=
@Rem This is new for example #2: the "helper" files don't remove these var.s
@Set TRACE=
@Set NOENVCHK=
@Set APPEND_LOG=

What's new here is the fact that the last three variables aren't automatically deleted.

The term "ecology" is apt: all variable+value combinations are referred to as the "environment" (in both DOS and Unix).  A responsible programmer keeps the environment "clean" of "trash" (AKA "lint").

There are two reasons why you want to be "responsible": one is that unexpected variable assigments can interfere with other systems that you may be running ... the other is that you may simply run out of space.  This isn't a consideration for Unix, but in DOS, the environmental space is quite limited, and therefore you can't afford to "pollute" it with "trash."

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

20:  Some other comments on the "helper" DOS batch files

There are 3 "helper" DOS batch files.

ADB_1.Bat is for single-file operations.

ADB_J.Bat is for joins between two files.

ADB_U.Bat is for the special case in which you want to join two files, and update one of them.

To learn more about these "helper" DOS batch files, you should look at each one, individually.  The defaults are particularly important.

If you wish to build a large, complex, application with these files, I recommend that you test your batch file, after each step.

Return to local table of contents
Return to global table of contents
Frames mode, or No frames

Next documentation section