MH & nmh: Email for Users & Programmers

May, 2006

MH and the Shell

Unlike most other email user agents, MH is used from the UNIX shell. This section lists two shell features that can make MH easier to use: aliases or functions, and shell variables.

Using Shell Aliases and Functions with MH

Most UNIX shells (except earlier Bourne shells) have aliases, functions, or both. Aliases let you execute complicated command lines by typing a short alias name. In general, functions can handle more complicated problems than aliases -- but let's ignore that for now. Let's look at how aliases and functions can save time with MH.

Making Aliases and Functions

When I'm reading mail, I usually type rmm to remove a message, then next to read the next one. An alias or function named something like n can do both. In the C shell, for example, you can type the following alias at a prompt or into your .cshrc file. The semicolon (;) is a command separator:

    alias n 'rmm; next'
Now, for the Bourne shell -- on the command line or in your .profile file:
    $ n()
    > {
    > rmm
    > next
    > }
Lots of times, you'll need to pass arguments to an alias or function. That is, you need to give the name of a folder, some message numbers, some switches, or a combination of those. In the C shell, the most general way is to use the sequence \!* at the place you want the arguments inserted. (You can "sprinkle" the arguments at different places in the alias, too. But there's not room here to handle specialized cases like that.)

The next alias and function, scap, will run scan with the arguments you give it, if any. It pipes scan's output to a pager program to show one screen at a time. So all three of the following command lines will work fine:

    % scap
    % scap +outbox
    % scap +outbox -form scan.timely last:20
First, for the C shell:
    alias scap 'scan \!* | more'
In a Bourne shell function, use $* to pass arguments. Here's scap written as a function with the System V pg pager:
    scan $* | pg
As you saw above, the semicolon (;) is a command separator -- after the first command finishes, the second command is executed. Sometimes, you only want the second command to run if the first command succeeded. For example, a cleanup alias or function might run pick to find certain messages, then rmm to remove them. You only want rmm to run if pick found some messages! (For an example, see cleandrafts in the Example Shell aliases for MH.) That's where the && (two ampersands) operator is handy. The && means "if the first command returned a zero exit status, run the second command." As the Section Using Exit Status explains, most MH commands return a zero status when they succeed. (If you're going to do something that's hard to undo, like removing folders, don't depend on a useful exit status from the previous command until you check its manual page or test it.)

Here's an example of the && operator. Most modern pagers can move backward or forward through a file. But they may not be able to back up when they're reading from a pipe. (The less pager, supplied with MH, can do both. So can modern versions of more.) Let's fix the scap alias from above to write its scan output to a temporary file. Then, if scan succeeds, run more to read the file. Finally, remove the temporary file. I'll call this alias scap2:

    alias scap2 'scan \!* > /tmp/s$$ && more /tmp/s$$; rm -f /tmp/s$$'
That says "if scan succeeded and wrote lines to the temporary file, run more to read the file." (/tmp/s$$ makes a temporary filename.) The semicolon doesn't test for success. So, whether scan succeeded or not, the temporary file will be removed next. In the Bourne shell, scap2 looks like this:
    scan $* > /tmp/s$$ && pg /tmp/s$$
    rm -f /tmp/s$$

Some Handy Aliases

You can write the commands in this section as aliases or functions. I'll show them as aliases. If you need functions:

The fls alias only works on shells with a history command:

Example: Shell aliases for MH

    # Handle folder stacks:
    alias puf 'folder -push \!*'
    alias pof 'folder -pop'
    alias lsf 'folder -list'
    # Search history list for folders I've worked on  (not for Bourne shell):
    alias fls 'history >/tmp/f$$; grep "+[A-Za-z0-9/]" /tmp/f$$; rm /tmp/f$$'
    # Handle draft folder (uses stack aliases above):
    alias scandrafts 'puf +drafts; scan; pof'
    alias cleandrafts 'puf +drafts; pick -before -14 -seq temp && rmm temp; pof'
    # List unseen messages; show first message, if any (for MH 6.7.2 and later):
    alias unseen 'scan unseen && show unseen:1'
    # Page through "raw" message(s) without any MH formatting (mhl, mhn, etc.):
    alias showv 'show -noshowproc \!* | more'
(You can also grab those aliases from the book's online archive in examples/mh/misc/aliases.csh.) A lot of the command versions in the Chapter New Versions of MH Commands can be written as aliases and functions.

They Won't Always Work

Aliases and functions generally work only when you're using a shell. Programs executed directly by the UNIX kernel can't use them. For example, the MH refile command calls an rmmproc to remove the message after it's been refiled. If you make a myrmm alias or function, you can use it from the shell prompt -- but refile can't use it:

    % alias myrmm 'something'
    % refile -rmmproc myrmm +somewhere
    refile: unable to exec myrmm: No such file or directory
MH also lets you make what I call versions of MH commands. Those can be executed directly by other programs. On many UNIX systems, shell scripts and other interpreted programs can also be executed directly with #!; the Section How Does Your System Execute Files? has tests to see if your system can.

Using Shell Variables with MH

Before the shell executes a command line, it expands the values stored in any shell variables you use. You can store folder names (especially long names) in shell variables. MH has other ways to handle options you use often (see the Chapter New Versions of MH Commands) and lists of message numbers (sequences) -- but variables can be used there, too.

Here's an example with folder names. I use a couple of folders with long names, +mh-book/revisions and +/u/jerry/Mail/bookquestions. I store those names in shell variables. I type a short shell variable name; the shell expands it into the long folder name before it starts the MH command.

As a general rule: when you define a shell variable, use single quotes ('), not backquotes (`), around the folder name. In the C shell, the set command stores a shell variable. The following commands, typed at a prompt or in my .cshrc file, set the variables:

    set revs='+mh-book/revisions'
    set bookq='+/u/jerry/Mail/bookquestions'
To set the variables in the Bourne shell, you don't need the set. Again, type the commands at a $ prompt or put them in your .profile file:
When you use the variables, put a $ before the variable name. The $ works in both shells. For example, to change my current folder, I type:
    % folder $bookq
    /u/jerry/Mail/bookquestions+ has  774 messages (   1- 823); cur= 823
    % show last $revs
        ...Last message in +mh-book/revisions appears...

Using Environment Variables with MH

MH programs read their environment for certain environment variables. For instance, if the MH environment variable is set, it overrides the default location ($HOME/.mh_profile) of the MH profile file. You can set environment variables from your shell setup file (like .login or .profile) or from a shell prompt. The section Changing the MH Environment lists all of the environment variables that MH checks.

The Bourne-type shells and C-type shells have different syntax for setting an environment variable. Bourne-type shells have two ways to change the environment: permanently (until you exit the current shell) and temporarily (for just one command). For example, let's say you made a test MH profile in your home directory named testMHprofile. The first setting below would make it take effect for just the single repl command. The second setting would affect all commands from then on:

    $ MH=$HOME/testMHprofile repl last +inbox
    $ MH=$HOME/testMHprofile; export MH
In the C shell, there's no direct way to set an environment variable temporarily. You can do it with a subshell, though. Here's how to write the previous Bourne shell examples for csh:
    % (setenv MH ~/testMHprofile; repl last +inbox)
    % setenv MH ~/testMHprofile