# Explanation of distprompter

The Section distprompter Edits dist Drafts gives an overview of this script. Here is the distprompter script (you might want to open it in a separate browser window). To install it, see the Section Programs in This Book's Archive.

The main part of distprompter is the while loop. It reads the template draft file (that dist built; the Section Making the Draft from the Template File explains how). The draft filename is in the first command-line parameter, `$1`. The while loop writes the edited header into the `$header` file. The loop is shown below. The line numbers like `31>` aren't part of the file; they're just for reference.

        30> # read COMMAND AT TOP OF LOOP GETS STDIN (FD 0), SO SAVE FD 0 NOW:
        31> exec 4<&0   # SAVE ORIGINAL STDIN (USUALLY TTY) AS FD 4
        32> while read label line
        33> do
        34>     case "$label" in
        35>     [Rr]esent-?*:)
        36>         case "$line" in
        37>         ?*) # SHOW LINE ON SCREEN AND PUT INTO HEADER FILE:
        38>             echo "$label $line"
        39>             echo "$label $line" 1>&3
        40>             ;;
        41>         *)  # FILL IT IN OURSELVES:
        42>             $echo "$label $nnl"
        43>             exec 5<&0   # SAVE DRAFT FILE FD; DO NOT CLOSE!
        44>             exec 0<&4   # RESTORE ORIGINAL STDIN
        45>             read ans
        46>             exec 0<&5   # RECONNECT DRAFT FILE TO STDIN
        47>             case "$ans" in
        48>             "") ;;  # EMPTY; DO NOTHING
        49>             *)  echo "$label $ans" 1>&3 ;;
        50>             esac
        51>             ;;
        52>         esac
        53>         ;;
        54>     ""|---*) # END OF HEADER
        55>         echo "-------" 1>&3
        56>         break   # PROBABLY NOT NEEDED...
        57>         ;;
        58>     *)  echo "$myname: illegal header component
        59>         '$label $line'" 1>&2
        60>         break
        61>         ;;
        62>     esac
        63> done <$1 2>$err 3>$header

For efficiency, the script uses UNIX file descriptors to keep several files open at once. The loop reads and writes those open files, one line at a time. The Bourne shell can manipulate file descriptors with its exec command and operators like `2>&1` and `0<&5`. There isn't room here for a complete explanation; to get that, see an advanced Bourne shell programming book like UNIX Power Tools. The comments in the script and the explanation here will tell you what each file descriptor redirection does.

The Table below lists the file descriptor numbers used in operators like `4<&0`.

Table: UNIX File Descriptor Numbers

        Number   Default Use

        0        Standard input

        1        Standard output

        2        Standard error

        3-9      Not assigned*

`*` If your MH was built with the `[OVERHEAD]` configuration option, some of the file descriptors 3-9 may already have been used. (Section The -help Switches shows how to list your configuration options with -help.) This distprompter script uses file descriptors 3-5. If you need to use those special `[OVERHEAD]` file descriptors inside distprompter (and that's not too likely), see the Table Environment Variables that MH Sets for an explanation of the MHFD and MHCONTEXTFD descriptors.

Outside the loop, the standard input of the commands is (by default) from your keyboard. But the loop is different because of the redirection at the `done` line (line 63 -- see below). Every command in the loop reads its standard input from the template draft file, not from your terminal. It "takes over" standard input during the loop. So, the read command at the top of the loop (line 32) reads the template draft file. But that means the read command inside the loop, on line 45, couldn't read from your terminal.

The answer is to connect any other files you want to read to their own file descriptors. Line 31 does that. Inside the loop, instead of reading its standard input, the read on line 45 reads file descriptor 4.

Before reconnecting to the saved file descriptor 4, there's one more thing the script needs to do. Line 43 saves the open draft file template as file descriptor 5. Because we don't close the file here (with `exec 5<&-`), the next read command at the top of the loop (line 32) will read the next line of the file. If we'd simply closed the template file and reopened it, the next read on line 32 would read the first line of the file instead of the correct line.

Next, line 44 changes file descriptor 0 to point to the original standard input before the loop -- it was saved as file descriptor 4 in line 31. Now, the read ans command on line 45 can read the original standard input and get your answer to the prompt. Line 46 rearranges descriptors for the rest of the loop -- it reconnects the draft template file from file descriptor 5 back to file descriptor 0. So, the next read at the top of the loop will read the template file.

Some shells can solve the problem on line 45 directly. They let you change what file descriptor the read command reads, instead of the default (file descriptor 0, the standard input). For instance, to read the value of the ans variable from file descriptor 4, line 45 could look like one of these:

        read ans 0<&4
        read -u4 ans

But some Bourne shells won't let you redirect the input of read on the same line.

That's why distprompter was written this way: the kind of redirection around line 45 works on all Bourne shells.

Now, about the loop: three file descriptors (fds) are redirected at line 63; these affect the input and output of every command inside the loop:

fd 0
  The standard input; taken from the draft template file. Each read command on line 32 will read the next line from the template file.

  Any other command inside the loop that reads its standard input would read from this template file, too. That would be bad here, of course. See the explanation of lines 43-47, above.

fd 2
  The standard error; goes to an error-collecting file.
fd 3
  Written to the new message header file. Anything that is written to file descriptor 3 during the loop will be added to the new header.

The sidebar entitled The Ins and Outs of Redirected I/O Loops points out some things to keep in mind when you write other loops like this one.

The read in line 32 reads two shell variables. The first variable, label, always contains the first word on the line -- usually a field label like `Resent-Fcc:`. If the draft file has any other text (like the word `outbox`), that goes into the line variable.

The body of the loop is a big case structure. It tests the label and branches:

- If the label starts with `Resent-`, lines 35-53 test to see if the rest of the field had any text.
  - A complete field is echoed to the terminal (via standard output) in line 38 and to the new header file (by file descriptor 3) in line 39.
  - If the draft field was empty, line 42 echos it without a newline. This leaves the cursor to the right of the colon, ready for the user's response. The `$echo` and `$nnl` set at one of the lines 12-14 makes this work portably.

    A simple read command won't read what you type at the prompt because the loop's standard input comes from the draft file. As you read at the start of this section, some shells' built-in read command will not let you redirect their standard input to another file descriptor. A workaround that should take care of all Bourne shells starts at line 43. It's explained in detail near the start of this section.

    The case in lines 47-50 tests the response. The field is only printed to the new header if the user typed an answer.

- The case in line 54 matches an empty line or a line that starts with at least three dashes. This is the end of the header. The script echos its own row of dashes to the draft file. Then it breaks the loop -- that probably isn't necessary because the next read in line 32 should return a nonzero status when it reads the end of the template file.
- Line 58 matches anything else in the header. Because MH is strict about the fields in the dist header, this test does some early enforcement of the rules and avoids those problems when you try to send the message.

After the loop finishes, the script tests the error file's size. If there's something in the file, the script shows the errors collected. Otherwise, the script copies the new header file on top of the original empty template. If that succeeds, a zero exit status tells whatnow (the program that called distprompter) that the edit went okay. Otherwise the exit status remains 1 (the default value); whatnow will remove the draft file.