The MH 6.8.3 mh-format(5) manual page says: "Format strings are designed to be efficiently parsed by MH which means they are not necessarily simple to write and understand. This means that... users of MH should not have to deal with them."NOTE: for users of the online version of this book: This chapter has a lot of examples followed by long explanations. To avoid jumping between the example and its explanation, it's a good idea to open a new browser window to show an example. (Check your browser's menu for a command like New Web Browser or Open in New Window). Then, use the original browser to read the explanation while you view the example in the second browser window.
The MH 6.6 page said just the opposite: format strings "...represent an integral part of MH. This means that... users of MH should deal with them."
I tend to agree with the MH 6.6 wording. Unless you're doing something very complex, MH format strings really aren't that tough to figure out. And they're very useful. MH format strings let you:
Until recently, the mh-format(5) manual page was fairly brief; it didn't document all of mh-format. The most recent version of the manual page, released with MH 6.7, has quite a bit of information. If your online version isn't up to date, the Section Online Manual Pages explains how to get a newer one.
One term you'll need to know is escape. An escape is a lot like a variable in programming or mathematics: it stands for (and is usually replaced with) something else. There are three kinds of escapes in MH format strings.
The easiest escapes to define are component escapes. These are replaced with the fields' values from your message header. (Remember, MH calls a header field a "component.") Here's an example. To get the subject of a message into your MH format string, you use the subject component escape. Write it this way:
%{subject}There are two other kinds of escapes: function and control. You'll see examples of those below, and the mh-format(5) manual page defines them.
In fact, this is a good time to spend a few minutes with your online manual page. You don't need to read it word for word, but you should see what sections are there and what topics they cover.
The following sections will take you through MH format strings by example, like the mhl sections did. An easy way to get started with MH format strings is the scan command.
It's time for a few examples. I have a folder with two messages in it. In the Example below, I'll use show to display the header of the first message for reference. Then I'll scan both messages with the normal scan command. Because there's no -form or -format string, scan uses its default format.
Example: Sample folder with two messages
% show 1 (Message scantest:1) Forwarded: Fri, 13 Jan 1995 03:41:35 -0500 Forwarded: alicia Replied: Mon, 09 Jan 1995 10:25:45 -0500 Replied: Joe Doe <joe@foobar.ph.com> Date: Thu 14-Dec-89 17:31:21 est Received: by asun.phl.ph.com (5.54/PHL) id AA29237; Thu, 14 Dec 89 17:31:21 EST Message-Id: <8912142231.AA29237@phl.ph.com> From: Al Bok <al@phl.ph.com> Reply-to: Joe Doe <joe@foobar.ph.com> To: hquimby@asun.phl.ph.com cc: ehuser@quack.phl.ph.com, aguru@mt.top.ph.com Subject: Query about "repl -query" I have a question about repl -query... % scan 1+-12/14 Al Bok Query about "repl -query"<<I have 2 01/09 To:Joe Doe Re: Query about "repl -query"<<JoNow let's give scan a format string. Either you can put format strings in a format file and use scan's -form switch or you can type them on the command line with the -format switch. I'll start with -format.
A simple format string that prints a hash mark followed by the message number and a colon, then the subject, works like this:
% scan -format "#%(msg): %{subject}" #1: Query about "repl -query" #2: Re: Query about "repl -query"Here are some points about that last example:
If you don't use the percent sign (%) characters, MH won't treat what comes next as an escape. Look what happens without the % characters:
% scan -format "#(msg): {subject}" #(msg): {subject} #(msg): {subject}You've already seen examples of two of the three types of escapes: component and function escapes. The third type, a control escape, does an if-else_if-else-endif operation. The parts are:
%< = if %? = else_if %| = else %> = endif
NOTE: MH 6.7.2 added the else-if operator, %?, to that list. To keep things simple at the start, I won't cover %? until the Section The Default scan Format File.
Let's add a control escape to this example. It will test to see who each message is from. If a message was sent by me, this control escape will display the words FROM ME. Otherwise, it'll display the sender's address by printing the %{from} component escape. The control escape looks like this:
%<(mymbox{from})FROM ME%|%{from}%>That's not as hard as it might look -- we'll dissect it in a minute. Let's try it first, then explain.
% scan -format "#%(msg): %<(mymbox{from})FROM ME%|%{from}%> %{subject}" #1: Al Bok <al@phl.ph.com> Query about "repl -query" #2: FROM ME Re: Query about "repl -query"The first message is from someone else, so scan prints his address. The second message is from me, so FROM ME is printed instead.
Let's dig into that control escape. Here's a diagram of the if-then-else parts:
%< (mymbox{from}) FROM ME %| %{from} %> if then else this is true do this do thisActually, that's a nested set of all three kinds of escapes -- control, function, and component.
The %< is the start of the control escape. It tests the return value of (in other words, the "answer" from) (mymbox{from}). The (mymbox) function escape tests whether an address belongs to the person who's running the MH command. The {from} component escape is the address to test. Note the following:
An escape returns one of two kinds of values, either numeric (integer) or string. The return values of escapes are put into registers (holding places) named num and str, respectively.
For simple format files, you don't need to know about registers. That's because the return value of an escape is always printed, unless the escape is nested in another escape. The outermost escape should always start with a percent sign (%); inner (nested) escapes shouldn't.
For instance, in the previous format string, the %(msg) and %{subject} escapes are not nested in others -- so their values are just printed. But the nested set of escapes (mymbox{from}) is itself nested in a control escape. There the return value of {from} is passed to (mymbox), and the return value of (mymbox) is passed to the control escape. What's printed is the value of the control escape (which starts with a percent sign (%); that's a clue that it'll be printed).
It's a good idea to test yourself as you look at the other mh-format strings in this section. Experiment to be sure how they work, what will be printed, and so on. The mh-format(5) manual page has more precise information.
NOTE: Most address-parsing function escapes won't work if your MH is configured with [BERK]. scan -help lists your configuration.
The Table below summarizes the four kinds of escapes.
A format file has the same syntax as the format strings we used above, but you type the format string into the file without quotes around it. (Use a text editor like vi or emacs.) You give the filename to scan with its -form switch -- if the file is in your MH directory, you don't need to type a pathname. 'br For example, here's what the above format string would look like in a format file named scan.from in your MH directory (I've left in the backslash at the end of the short first line, so you can see how to continue lines if you need to):
% cat scan.from #%(msg): \ %<(mymbox{from})FROM ME%|%{from}%> %{subject} % scan -form scan.from #1: Al Bok <al@phl.ph.com> Query about "repl -query" #2: FROM ME Re: Query about "repl -query"Another note about these example format files: if you don't want to type them in yourself, you can get them electronically. For instructions, see the Section Obtaining Example Files From This Book.
% scan -form scan.answer 1R Al Bok <al@phl.ph.co Query about "repl -query" 2 ****** FROM ME ***** Re: Query about "repl -query" % cat scan.answer %4(msg)%<{replied}R%| %> \ %<(mymbox{from})****** FROM ME *****%|\ %<{reply-to}%20{reply-to}%|%20{from}%>%> \ %{subject}Okay; let's take this step by step again:
436+-06/28 Al Bok <<I have a very complicated ques 441 06/29 Jerry Peek That complicated message Al sent 443 06/30*To:ehuser,emmab More about lunch<<The meeting is
NOTE: In earlier versions of MH, message 441 showed a problem in the default format. It would scan this way:
441 06/29 To: That complicated message Al sentIf a message was from you and its header didn't have a To: field, scan would show To: followed by an empty field. That happened when a particular message was a reply sent with repl -query, where the reply wasn't sent to to the person who wrote the original message.
The default scan format is not read from a file each time scan runs. It's built into the scan command. The compiled-in definition is in the file h/scansbr.c in the MH source tree. There are two versions. If your MH is configured without the [UK] option (see the Section The -help Switches to find out), look at the first Example below. I've added line numbers (like 8>) for reference; those aren't part of the file. In the [UK] configuration, the day of the month is printed before the month. That file is shown in the second Example below.
Example: Default scan format file
1> %; NOTE: This file is supplied for reference only; it shows the default 2> %; format string (for non-UK sites) which was compiled into "scan". 3> %; See the source file "h/scansbr.h" for details. 4> %4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ 5> %02(mon{date})/%02(mday{date})%<{date} %|*%>\ 6> %<(mymbox{from})%<{to}To:%14(friendly{to})%>%>%<(zero)%17(friendly{from})%> \ 7> %{subject}%<{body}<<%{body}>>%>
Example: Default UK scan format file
1> %4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ 2> %02(mday{date})/%02(mon{date})%<{date} %|*%>\ 3> %<(mymbox{from})%<{to}To:%14(friendly{to})%>%>%<(zero)%17(friendly{from})%> \ 4> %{subject}%<{body}<<%{body}>>%>The non-UK scan format (in the Example Default scan format file) is also available, in MH 6.8 and above, as the file scan.default in the MH library directory. I made the UK version by swapping the day and month entries from scan.default.
Let's take a walk through the non-UK Example, Default scan format file. As we work through this example and the ones after it, keep the mh-format(5) manual page close by and refer to it as we go. To help with the explanation, here are two scan output lines with each character (column position) numbered:
436+-06/28 Al Bok <<I have a very complicated ques 443 06/30*To:ehuser,emmab More about lunch<<The meeting is 0 1 2 3 4 5 6 7 1234567890123456789012345678901234567890123456789012345678901234567890
Before MH 6.8, an ugly way to add a comment looked like this:
%<{-comment-}This is a comment%>If the message header doesn't have a field named -comment-: (with a dash at the start and end of its name), the comment This is a comment won't be printed. I don't recommend making comments that way.
If the message doesn't have a Date: header field, {date} gives the date that the message file itself was last modified. In that case, column 12 will have an asterisk (*) instead of a blank. This is handy for scanning draft folders, where messages usually don't have Date: fields. The note after Table MH-format Special Component and Function Escapes has more information.
Line 6 starts with a nested control escape. It ends with a control escape that tests a register set by the first control escape. Let's take it in steps.
%<(mymbox{from})%<{to}To:%14(friendly{to})%>%>It starts by testing to see whether the From: field contains my address. (As a side effect, the return value of the test, (mymbox{from}), is stored in the num register. The value is used below.) If the message is from me, the second control escape tests to see whether the message has a To: field. If it does, the format string prints To: followed by the first 14 digits of the first address in the To: field. That's the end of the first part of line 6.
%<(zero)%17(friendly{from})%> \The (zero) function is true when the value in the num register is zero. In other words, if the last escape that modified the value of num set a "false" (zero) value, the (zero) function will test true.
What was the last escape that modified the value of num? There are two possibilities here:
But if the message is from me and it doesn't have a To: field, {to} will set the num register to "false". (Also, the To: field won't be printed.) In that case, (zero) will test true -- and the second control escape will print the first 17 characters of the From: field in a "user-friendly" format.
That wasn't so bad, was it? :-) Line 6 shows a good example of the num register: holding the result of a test to be used later.
If the message doesn't have a body, {body} evaluates false and nothing is printed after the subject. If the message has a body, << is printed, followed by as much of the body as will fit:
452 07/01 "Emma H. User" A short message from me<<Hi!>>
435+ 05/20 root <<The job you submitted to a 436 C R 06/28 Al Bok <<I have a very complicated 441 C 06/29 Jerry Peek That complicated message Al 443 DF 06/30*To:ehuser,emmab More about lunch<<The meetinThis new version has five "field letters" between the message number and date:
1> %4(msg)%<(cur)+%| %>%<{cc}C%| %>\ 2> %<{resent-to}D%?{resent-cc}D%?{resent}D%| %>\ 3> %<{forwarded}F%| %>%<{mime-version}M%| %>%<{replied}R%| %>\ 4> %02(mon{date})/%02(mday{date})\ 5> %<{date} %|*%>\ 6> %<(mymbox{from})%<{to}To:%14(friendly{to})%>%>%<(zero)%17(friendly{from})%> \ 7> %{subject}%<{body}<<%{body}>>%>The differences between scan.default and scan.hdr are in the first four lines of the Example above. Compare those to lines 4-7 of the Example Default scan format file.
Most of the changes are new control escapes to make the field letters. For example:
%<{cc}C%| %>tests for a cc: header. If there is one, it prints a C; otherwise it prints a space.
The three-part control escape on the second line prints a D or a space. It uses the %? else-if operator. Here is the same line for versions of MH before 6.7.2 which don't have %?:
%<{resent-to}D%|%<{resent-cc}D%|%<{resent}D%| %>%>%>You might try adding another column for, say, a Sender: field. To test your new format file, use a text editor to add a Sender: field to a couple of mail messages. In MH 6.7 and later, you can also use a command like the following to add a dummy Sender: field. (In MH 6.6 and before, anno doesn't have a -nodate switch.)
% anno -nodate -component Sender -text someone@somewhere
18+ 02/13 To:omderose@mvus Lunch<<Let's eat now. OK? >> 18+ 02/13 To:omderose@mvus Lunch<<Let's 18+ 02/13 To:omderose@mAs another example, notice that adding the five status letters in the Section More Header Information: scan.hdr didn't make the scan.hdr output any wider than the scan.default output.
As an output line is printed, you can get the amount of space left by using the function escape (charleft). The (width) function escape gives the total output width.
scan.dateparse isn't a format file you'd want to use every day, but it's a good demonstration of some important things:
% scan 1+-12/14 Al Bok Query about "repl -query"<<I hav 2 01/09 To:Joe Doe Re: Query about "repl -query"<<J 3 01/00 randy@atlantic.or Meeting is on!<<Be sure to get y 4 08/16 randy@atlantic.or Meeting is on!<<Be sure to get yThe scan.dateparse format file makes about 330 characters of output for each message. The amount depends on the length of the Date: field in the message. Here's an example of scanning the same folder with scan.dateparse:
% scan -form scan.dateparse -width 330 MESSAGE 1: Thu 14-Dec-89 17:31:21 est (STANDARD time) Official: Thu, 14 Dec 89 17:31:21 -0500 "Pretty": Thu, 14 Dec 89 17:31:21 EST 629677881 seconds since UNIX, 160540976 seconds before now DAY|WEEKDAY |WDAY|SDAY|MONTH|LMONTH |MON|YEAR|HOUR|MIN|SEC Thu|Thursday | 4|yes |Dec |December | 12|1989| 17| 31| 21 MESSAGE 2: Mon, 09 Jan 1995 10:25:45 -0500 (STANDARD time) Official: Mon, 09 Jan 1995 10:25:45 -0500 "Pretty": Mon, 09 Jan 1995 10:25:45 EST 789665145 seconds since UNIX, 553712 seconds before now DAY|WEEKDAY |WDAY|SDAY|MONTH|LMONTH |MON|YEAR|HOUR|MIN|SEC Mon|Monday | 1|yes |Jan |January | 1|1995| 10| 25| 45 MESSAGE 3: -0400 16 Aug 89 16:54:59 CAN'T PARSE DATE MESSAGE 4: 16 Aug 89 16:54:59 -0400 (DAYLIGHT time) Official: 16 Aug 89 16:54:59 -0400 "Pretty": 16 Aug 89 16:54:59 EDT 619304099 seconds since UNIX, 170914758 seconds before now DAY|WEEKDAY |WDAY|SDAY|MONTH|LMONTH |MON|YEAR|HOUR|MIN|SEC Wed|Wednesday| 3|no |Aug |August | 8| 89| 16| 54| 59Notice (on the first line of the listings) that each message has a different date format, but scan can parse all of them -- except the one in message 3.
Format files you've seen up to now just let scan truncate their output when the width limit is reached. But scan.dateparse checks the available width. It prints the last two lines that show the parsed date only if there is enough room for all of both lines. In this next example, the width isn't quite enough, so the last two lines for each message aren't displayed:
% scan -form scan.dateparse -width 300 ...These lines omitted... 629825145 seconds since UNIX, 8553736 seconds before now MESSAGE 3: -0400 16 Aug 89 16:54:59 CAN'T PARSE DATE MESSAGE 4: 16 Aug 89 16:54:59 -0400 (DAYLIGHT time) Official: 16 Aug 89 16:54:59 -0400 "Pretty": 16 Aug 89 16:54:59 EDT 619304099 seconds since UNIX, 170914758 seconds before nowIf you were going to use a format file like that a lot, you'd probably want to make a new version of scan called something like scandp. When you make the new version, you'd put this entry in your MH profile:
scandp: -form scan.dateparse -width 330Then, you could just type scandp to use scan.dateparse without having to remember the width.
The next Example shows scan.dateparse.
Example: Date parsing demonstration: scan.dateparse
1> MESSAGE %(msg): %{date} \ 2> %<(nodate{date})CAN'T PARSE DATE%|\ 3> (%<(dst{date})DAYLIGHT%|STANDARD%> time)\n\ 4> Official: %(tws{date})\n\ 5> "Pretty": %(pretty{date})\n\ 6> %(clock{date}) seconds since UNIX, %(rclock{date}) seconds before now\ 7> %(void(charleft))%<(gt 125)\n\ 8> DAY|WEEKDAY |WDAY|SDAY|MONTH|LMONTH |MON|YEAR|HOUR|MIN|SEC\n\ 9> %(day{date})|%9(weekday{date})|%4(wday{date})|%4(sday{date})|\ 10> %5(month{date})|%9(lmonth{date})|%3(mon{date})|%4(year{date})|\ 11> %4(hour{date})|%3(min{date})|%3(sec{date})%>%>\nNext, here's a line-by-line explanation of how scan.dateparse works:
Line 2 starts with a control escape that tests the (nodate) function escape. If the test (nodate{date}) is true, the Date: field is missing or can't be parsed. Then, the words CAN'T PARSE DATE are output -- and scan jumps ahead to the matching %>, which is at the end of line 11. For messages with unparseable dates (like message 3 here), only one line is output. On the other hand, if the test in line 2 fails (if (nodate{date}) returned zero), then the date is parseable -- and interpretation goes to line 3, where the multiline output starts.
Line 3 completes the first line of scan output with a string in parentheses that tells whether the message was sent during daylight savings time or standard time. Line 3 starts with a parenthesis that is output literally. Next is a control escape that tests the value of (dst{date}) -- if the value is nonzero, then the date is during daylight savings time and the control escape outputs DAYLIGHT. Otherwise, it outputs STANDARD. After the end of the control escape, a space and time) are output, followed by a newline (\n) which ends the first output line.
A numeric time zone tells the difference, at the sending site, before or after Coordinated Universal (Greenwich Mean) Time. For instance, the U.S. West Coast is eight hours behind Greenwich Mean Time in the winter; that's written -0800. In the summer, during Daylight Savings Time, the difference is seven hours, or -0700. Sites east of Greenwich have times starting with a plus sign (+). For example, +0030 means 30 minutes after Greenwich Mean Time.
Next, the (gt) escape compares the output of (charleft) (in the num register) to the constant 125, which is the number of characters that the next two lines of output require. If the test succeeds, then there's enough space, and we do lines 8-11 (starting with a newline from the end of line 7). Otherwise, we branch to the next-to-last %> on line 11, which is the end of this control escape.
Lines 9-11 use quite a few function escapes; they're all in the mh-format summary (the Section Summary of MH Format Strings). Line 10 and line 11 fill in the rest of the fields. Newer versions of MH use four-digit years (like 1995) internally, so %4(year{date}) will fill its field with the year. Older MH versions use two-digit years (like 95); on those versions, the %4 format specification will print two spaces followed by the two-digit year. Line 11 ends with \n, which means each message will have a blank line after it. Because \n is after the last %>, it will always be used, even for messages with unparseable dates like number 3.
%(void(year{date}))%02(modulo 100)The mhl.prodsumry format file uses that technique. It starts by writing the year into the num register. Next, the (modulo) function computes the value of num modulo 100 -- in other words, it divides the year by 100 and gives the remainder.
To make format files that are portable to both the two-and four-digit versions of MH, try this string that I found in the MH packmbox script:
%(void(year{date}))%<(gt 100)%4(putnum)%|19%02(putnum)%>If the output of (year) is over 100, the string outputs the four-digit year. Otherwise, it outputs 19 and the two-digit year.
% scan 435-443 -form scan.more -width 230 435 SENT: 20 May CHARS: 383 FROM: root (Super User) APP-TO: jdpeek <<BODY: The job you submitted to at, "/u3/acs/jdpeek/.l 436 SENT: Thursday CHARS: 29387 REPLIED: Friday FROM: Al Bok <al@phl.ph.com> TO: ehuser@asun.phl.ph.com <<BODY: I have a very complicated question about the ph 441+ SENT: Friday CHARS: 499 CC: ehuser@quack.phl.ph.com, jdpeek SUBJ: That complicated message Al Bok sent us 443 FILED: 16:44 CHARS: 52 TO: ehuser, emmab SUBJ: More about lunchIf you compare the four messages, you'll see how the output changes:
The next Example shows the format file.
Example: Lots of information: The scan.more format file
1> %; $Id: scan.more 1.3 1994/11/26 19:36:21 jerry book3 $ 2> %4(msg)%<(cur)+%| %>\ 3> %<{date} SENT%|FILED%>: \ 4> %(void(rclock{date}))\ 5> %<(gt 15768000)%03(month{date})%(void(year{date}))%02(modulo 100)\ 6> %?(gt 604800)%02(mday{date}) %03(month{date})\ 7> %?(gt 86400)%(weekday{date}) \ 8> %|%02(hour{date}):%02(min{date})%> \ 9> CHARS: %(size) \ 10> %<{forwarded} (FORWARDED)%>\ 11> %<{resent} (RESENT)%>\ 12> %<{mime-version} (MIME)%>\ 13> %<{replied} REPLIED: \ 14> %(void(rclock{replied}))\ 15> %<(gt 15768000)%03(month{replied})%(void(year{replied}))%02(modulo 100)\ 16> %?(gt 604800)%02(mday{replied}) %03(month{replied})\ 17> %?(gt 86400)%(weekday{replied}) \ 18> %|%02(hour{replied}):%02(min{replied})%>%>\n\ 19> %<{apparently-from} APP-FROM: %{apparently-from}\n%|\ 20> %<(mymbox{from})%| FROM: %{from}\n%>%>\ 21> %<{to} TO: %{to}%|\ 22> %<{apparently-to} APP-TO: %{apparently-to}%|\ 23> CC: %{cc}%>%>\ 24> %<{subject}\n SUBJ: %60{subject}%|\ 25> %<{body}\n <<BODY: %60{body}%>%>Most of scan.more uses the same techniques and escapes as other format files in this chapter. The parts of scan.more that print the SENT:/FILED: and REPLIED: fields are new, though. They were adapted from the MH scan.timely format file. Here's a look at one of the "date" sections: lines 4-8. (The REPLIED: section, lines 13-17, is almost identical.)
For example, if the number from (rclock) is 100000, that means the message was sent 100,000 seconds ago. That's 27.8 hours, which is yesterday (or before). The control escape in line 5 tests to see if the time is more than a month ago -- it isn't. (Here, as with the pick command, "one day ago" means 24 hours ago instead of the previous midnight.)
So control goes to the first %? on line 6 -- and the control escape is evaluated to see if the message was sent more than 604,800 seconds (one week) ago. It wasn't. Notice that the same number from (rclock{date}) is still stored in the num register.
The number in num does match at the test in line 7 -- because 100,000 is greater than 86,400. The date is printed with the (weekday) function escape, which prints a time as a weekday name. Otherwise, the hour and minute would be printed.
Versions of MH before 6.7.2, without the %? elseif operator, need to use a series of nested if-else escapes. Here's the same test written for older versions. (The Section The scan.answer Format File walks through a simpler nested if-else escape.)
%<(gt 15768000)%03(month{date})%02(year{date})%|\ %<(gt 604800)%02(mday{date}) %03(month{date})%|\ %<(gt 86400)%(weekday{date}) %|\ %02(hour{date}):%02(min{date})%>%>%> \This looks more complicated than the same example with the new %? operator. If you compare the two, though, you'll see a pattern. The single %? "elseif" operator has been replaced with a %| "else" operator and a %< "if" operator (plus a matching %> operator at the very end of the string). If the first part of the string matches (if the number is greater than 15768000), the first part of the string is interpreted and control goes to the final %> operator. Else, the %| operator starts a new test for numbers greater than 604800; its matching (nested) %> operator is next-to-last in the string. Similarly, the third %< is matched by the innermost %> at the end of the string; the %| operator between them handles numbers that are less than or equal to 86400.
The Example below shows a replcomps-like format file for the repl command. This one handles an addressing problem I have with some of the email I get. I can't reply directly to the From: addresses on those messages; I have to edit the To: address in my reply before I send it. Like replcomps, the replcomps.fixaddr format file gets the best reply address from the message header. Then it uses a series of (match) escapes to decide whether the address is one I can't reply to. If a bad address matches, the file outputs To: good-address.
To make the series of tests, I used the "else-if" operator %?. If you have MH 6.7.1a or before, use the nested tests shown in the Section The scan.answer Format File.
Example: The replcomps.addrfix format file
1> %(lit)\ 2> %(formataddr %<{reply-to}%?{from}%?{sender}%?{return-path}%>)\ 3> %<(nonnull)%(void(width))\ 4> %<(match isla!tim)To: tim\ 5> %?(match djkortz@apl23r)To: djkortz@apl23r.zipcom.com\ 6> %?(match !sparc2gx!vanes@uunet)To: vanes@email.imelda.ac.uk\ 7> %|%(putaddr To: )%>\n%>\ 8> %<{fcc}Fcc: %{fcc}\n%>\ 9> %(lit)%(formataddr{to})%(formataddr{cc})%(formataddr(me))\ 10> %<(nonnull)%(void(width))%(putaddr cc: )\n%>\ 11> %<{subject}Subject: Re: %{subject}\n%>\ 12> In-reply-to: Message from %<{from}%{from}\ 13> %?{sender}%{sender}%|%{apparently-from}%>\n\ 14> of "%<(nodate{date})%{date}%|%(tws{date})%>."%<{message-id} %{message-id}%>\n\ 15> --------After lines 1-3 store an address in the str register and test for it, lines 4-6 see if the address is one of the three that needs to be rewritten.
For instance, if the original message was From: isla!tim, line 4 would match it. The string To: tim would be output. The else-if operator $?, at the start of line 5, would see that the previous test succeeded; control would go to the matching end-if which is the first %> on line 7.
Here's another example. If the message had a Return-Path: field with the address ...!frobozz!sparc2gx!vanes@uunet.uu.net, it wouldn't match at line 4 or line 5. The %? operator would keep executing tests until the matching test in line 6 was found. You could add many more of these else-if tests.
If none of the %? operators match, the final else (after the %| operator) is executed. Here, the address is printed with no changes.
There's one more %? operator used. It picks an address for the In-reply-to: field in lines 12-13.
Example: The rcvtty.format file
1> [7m\ 2> * MAIL: %(size)ch @ %(hour{dtimenow}):%02(min{dtimenow}) *\n\r\ 3> [m\ 4> %<(mymbox{from})To:%14(friendly{to})%|%17(friendly{from})%>\n\r\ 5> %{subject}%<{body}<<%{body}>>%>That file uses a few tricks worth explaining:
"Hard-coding" an escape sequence this way isn't very portable. VT100-style sequences work on a lot of terminals and window systems, though. If those escape sequences don't work on your terminal, check your terminal or window system manual for its termcap or terminfo entry. (The Nutshell Handbook termcap & terminfo can help, too.)
jump down the screen like thisThe same thing happens, by default, with multi-line rcvtty messages like this file creates. To fix it, I've added carriage-return characters (\r) at the end of lines 2 and 4. They move the cursor back to the left margin, something UNIX doesn't do by default in raw mode. When the terminal isn't in raw mode, these extra carriage returns don't hurt a thing.
Example: The rcvdistcomps file
%(lit)%(formataddr{addresses})\ %<(nonnull)%(void(width))%(putaddr Resent-To: )\n%>\ Resent-Fcc: outboxAddresses you use on the rcvdist command line are available in the {addresses} component escape. By default, rcvdistcomps puts a copy of every message into your outbox folder. You can change all of this by making your own rcvdistcomps file in your MH directory.
A format string consists of ordinary text and special multi-character escape sequences which begin with % (percent sign). You can use C backslash characters in a format string: \b (backspace), \f (formfeed), \n (newline), \r (carriage return), and \t (tab). Continuation lines in format files end with a backslash (\) followed by the newline character. There are three types of escape sequences: header fields (called components by MH format), built-in functions, and flow control.
Control-flow Escapes
A control escape is one of: %<, %?, %|, or %>. These are combined into the conditional execution construct:
%<condition format text 1 %?condition2 format text 2 %?condition3 format text 3 ... %| format text N %>Extra white space is shown here only for clarity. These constructs may be nested without ambiguity. They form a general if-elseif-else-endif block where only one of the format text segments is interpreted.
The %< and %? control escapes cause a condition to be evaluated. This condition may be either a component or a function. The four constructs have the following syntax:
%<{component} %<(function) %?{component} %?(function)These control escapes test whether the function or component value is non-zero (for integer-valued escapes), or non-empty (for string-valued escapes).
Function Escapes
Most functions expect an argument of a particular type, as shown in the Table below:
Table: Argument Types for MH-format Functions
%(func 1234) %(func text string)
%(func{in-reply-to})
%(func{date})
%(func{from})
%(func(func2)) %(func %<{reply-to}%|%{from}%>) %(func(func2{comp}))
All arguments except those of type expr are required. For the expr argument type, the leading % must be omitted for component and function escape arguments, and must be present (with a leading space) for control escape arguments.
The evaluation of format strings is based on a simple machine with an integer register num, and a text string register str. When a function escape is processed, if it accepts an optional expr argument which is not present, it reads the current value of either num or str as appropriate.
Return Values
Component escapes write the value of their message field in str. Function escapes write their return value in num for functions returning integer or boolean values, and in str for functions returning string values. (The boolean type is a subset of integers with usual values 0=false and 1=true.) Control escapes return a boolean value, and set num.
All component escapes, and those function escapes which return an integer or string value, pass this value back to their caller in addition to setting str or num. These escapes will print out this value unless called as part of an argument to another escape sequence. (To prevent printing, use the (void) function escape.) Escapes which return a boolean value do pass this value back to their caller in num, but will never print out the value.
Tables of Function Escapes
The next three tables list MH-format function escapes.
Table: MH-format Function Escapes (1 of 3)
Table: MH-format Function Escapes (2 of 3)
Table: MH-format Function Escapes (3 of 3)
Special Escapes
Some MH commands give different interpretations to some escapes. The next Table gives a summary. The third column refers you to sections (S) and examples (X) with more detail about each entry. For details, see the command's manual page.
Table: MH-format Special Component and Function Escapes
Nesting Escapes
When escapes are nested, evaluation is done from inner-most to outer-most. The outer-most escape must begin with %; the inner escapes must not. For example,
%<(mymbox{from}) To: %{to}%>writes the value of the header field From: to str; then (mymbox) reads str and writes its result to num; then the control escape evaluates num. If num is non-zero, the string "To: " (with a trailing space) is printed followed by the value of the header field To:.
Field Width and Output Width
When a function or component escape is interpreted and the result will be immediately printed, an optional field width can be specified to print the field in exactly a given number of characters. For example, a numeric escape like %4(size) will print at most 4 digits of the message size; overflow will be indicated by a ? in the first position (like ?234). A string escape like %4(me) will print the first 4 characters and truncate at the end. Short fields are padded at the right with the fill character (normally, a blank). If the field width argument begins with a leading zero, then the fill character is set to a zero.
As above, the functions (putnumf) and (putstrf) print their result in exactly the number of characters specified by their leading field width argument. For example, %06(putnumf(size)) will print the message size in a field six characters wide filled with leading zeros; %14(putstrf{from}) will print the From: header field in fourteen characters with trailing spaces added as needed. For (putstrf), using a negative value for the field width causes right-justification of the string within the field, with padding on the left up to the field width. The functions (putnum) and (putstr) print their result in the minimum number of characters required, and ignore any leading field width argument.
The available output width is kept in an internal register; any output past this width will be truncated. The functions (width) and (charleft) are useful here; there are examples in the Sections scan Widths and The scan.dateparse Format File.
[Table of Contents] [Index] [Previous: mhl] [Next: Chapter Introduction: Processing New Mail Automatically]
This file is from the third edition of the book MH & xmh: Email for Users & Programmers, ISBN 1-56592-093-7, by Jerry Peek. Copyright © 1991, 1992, 1995 by O'Reilly & Associates, Inc. This file is freely-available; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. For more information, see the file copying.htm.
Suggestions are welcome: Jerry Peek <jpeek@jpeek.com>