MH & nmh: Email for Users & Programmers

May, 2006

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.

    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
    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:

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.