The mailme command is designed to simplify sending text files from the bash shell of a Linux or Unix system to an external e-mail address. The goal of this project is to develop a simple command and demonstrate some of the features and capabilities of bash shell script programming to assist in systems administration tasks.
Say that we decide that it is desirable to have a simple command to send text files in the body of an e-mail message which looks like this at the command-line level:
where filename is the name of the file we want to get. The syntax for the mail command to accomplish this task is:
mail -s subject address < filename
In this example, the contents of filename is used to create the body of an e-mail message sent to address with the subject line indicated by subject. In a specific example:
mail -s 'mailme test' email@example.com < resume.txt
where the contents of resume.txt is sent to firstname.lastname@example.org with the subject line of "mailme test".
We can use a feature of the bash shell to make an alias which will create a temporary command. If we want to have this available all the time, we can place the following instruction in our .bash_profile file to be implemented at each login:
alias mailme="mail -s 'mailme test' email@example.com < resume.txt"
This will create a function to send this particular file, resume.txt, to firstname.lastname@example.org. It is not very useful as is. Fortunately, the bash shell assigns variables to command lines it tries to interpret. In our target example:
the phrase "mailme" is assigned to a variable called $0 and the first argument following the command, "filename" in this case, becomes $1. If additional parameters followed "filename", they would be assigned to $2, $3, and so on. We can use this feature to designate a filename in our alias definition without "hard coding" it.
alias mailme="mail -s 'mailme test' email@example.com < $1"
This is a help and it is functional but it would be nice if we could make the subject line more meaningful. A useful subject line might be the name of the file. To accomplish this, we use the $1 variable a second time in the definition:
alias mailme="mail -s $1 firstname.lastname@example.org < $1"
Notice the use of quotation marks in these examples. By using single quotes ('), we tell the bash shell to print the contents exactly as shown. When we use double quotes ("), the contents are evaluated. That is, if special character are seen, like $1, the value will be looked up and inserted in the command line in place of the symbol.
An additional consideration is that while you can nest single quotes inside of double quotes, you cannot nest the same type of quotes within themselves. If you are using double quotes to define your entire alias, you cannot use double quotes inside that definition. It would confuse the bash shell.
This would cause a problem if we decided that we wanted to use the name of the command (represented by $0) followed by a space and the name of the file (represented by $1). To get the quotes nested in the proper order we would have to do something like:
alias mailme='mail -s "$0 $1" email@example.com < "$1" '
Here we are using single quotes for the alias definition which is allowed. As expected, the $0 and $1 variables need to be placed inside of double quotes to have them evaluated by the bash shell as the command is interpreted.
Maybe we decide that this isn't general enough. If we were a systems administrator, we might want to make this useful command available to everyone of our users. We could create an alias definition in each user's .bash_profile file or we might want a more general command which could be placed in one of the profile files which is run for each user.
To accomplish this we would need a way to get the e-mail address for each user as they use the command. Fortunately, the bash shell maintains a set of "environmental variables" for each user session. To see a list of these we can issue the following command:
The result is a screen-long list of variable names which are in ALL CAPS. A couple which are useful for us are the USER and HOSTNAME which can comprise the e-mail address of a user on this system. To display the results of one of these variables at the command-line level, we can type:
We could use these in the previous example in the following way:
alias mailme='mail -s "$0 $1 $USER@$HOSTNAME < $1" '
Notice the placement of the quotation marks. The material in the double quotes will be evaluated and replaced with their values before the command is executed.
The trouble with this particular implementation is that it sends the message to an account on this system. While it is true that e-mail on that system could be accessed with a POP3 client at home (if the server was turned on), it might be nice to send the e-mail to a completely different e-mail address.
Fairly early in the class we used the chfn command to change the information about ourselves in the finger database. We placed our main e-mail address in the "Office" field of the database. Normally, of course, this would be used to store our office phone number. To display the contents of the database for a given user, we can type:
generates the following output:
Login: keeline Name: James D. Keeline
Directory: /home/keeline Shell: /bin/bash
Office: James@Keeline.com, 4
On since Fri Sep 22 20:37 (PDT) on pts/0 from adsl-63-204-104-168.dsl.sndg02.pacbell.net
On since Fri Sep 8 08:53 (PDT) on pts/74 from san-wan-d-11.san.dsl.cerfnet.com (messages off)
Last login Fri Sep 22 20:37 (PDT) on 0 from adsl-63-204-104-168.dsl.sndg02.pacbell.net
Mail last read Tue Sep 19 14:52 2000 (PDT)
As you can see, the external e-mail address and group number appears on a line which begins with "Office:". We can use the grep command to search for "Office:" and output the line which contains it. To do this we use the "pipe" function using the pipe character (|) as follows:
finger keeline | grep 'Office:'
The result is a single line of output:
Office: James@Keeline.com, 4
As before, we can replace the single specific user name ("keeline") with the environmental variable $USER thus:
finger $USER | grep 'Office:'
to get a single line containing the user's external (or internal) e-mail address if it was placed in the "Office" field of the finger database.
The trouble is that there is extra information in the line. It is not a valid e-mail address with the extra information. As expected, the Linux operating system has utilities to assist with this. In this case we can use the cut command with certain options.
After reading the pages under man cut, we can see that two the cut command defines "fields" as separated by the [tab] character by default. In the example above, however, we see that we really want to look at the second field if the fields are defined by [space] characters.
This can be achieved by using the '-d' option to define the separation character and the '-f' option to specify the field we want. The cut command we want looks like this when attached to the previous finger example:
finger $USER | grep 'Office:' | cut -d' ' -f2
The result is almost what we want:
The extra comma (,) will cause a problem; it is not a valid e-mail address with it and it might confuse the mail command into thinking that the next item is another e-mail address. We can use the cut command again, this time by defining the separation character as the comma and grabbing the first column from the output of the previous command. The entire command looks like:
finger $USER | grep 'Office:' | cut -d' ' -f2 | cut -d',' -f1
The result is exactly what we want:
Now we want to get this into the previous mailme definition in place of the address. The definition was:
alias mailme="mail -s $0 $1 $USER@$HOSTNAME < $1"
We can use a feature of the bash shell which tells the system to evaluate a single command or a series of commands and replace the command definition with the results of that command. By using the accent mark (`) which is located on the same key as the tilde (~), we can define the beginning and end of the command we want evaluated. The result looks long and complicated (and it is) but if you look at the previous two command line examples, it should be possible to decode what is going on. We will insert our finger|grep|cut|cut command in place of the $USER@$HOSTNAME in the mail command:
alias mailme="mail -s $1 ' ' `finger $USER | grep 'Office:' | cut -d' ' -f2 | cut -d',' -f1` ' '< $1"
This line is longer than the 80 characters on our terminal session but will work if carefully typed in exactly in the order shown. The specific use of quotation marks is important.
This does not work perfectly, however. Our previous efforts to use the argument of the command ($1) as the subject of the message is not evaluated properly and the subject line is blank. The problem occurs because of the nesting of the quotes.
We can simplify the quotation marks considerably if we don't use the alias command. To do this, we must use another method to create a command. In this case, we create a text file with vi or some other method containing the following line:
mail -s $1 `finger $USER | grep 'Office:' | cut -d' ' -f2 | cut -d',' -f1` < $1
and save it as 'mailme'. Notice that we can remove the space holders (' ') from the previous example.
This will not execute as it is so we use the chmod command to alter the permissions for the file to allow us to execute the file but no one else (700):
chmod 700 mailme
If our home directory is not included in the command search path, we will need to use the following syntax to execute the command until the $PATH variable is changed:
This time, the subject line is appropriately evaluated with the filename sent as the subject of the e-mail message.
If we want to have this as a regularly avaliable executable command, we can do one of two things. The first is to place this file in a directory which is listed in the $PATH. One of these which is available to us is a bin directory (not /bin which is for the entire system unless we want to give it to everyone in which case the permission would have to be changed to 711) within our home directory. For me, the full path is:
If it is not present, you can add the directory by typing:
and then move the file to that directory:
mv mailme ~/bin/mailme
The other method involves examining how the $PATH variable is set in .bash_profile and adding your home directory (/home/keeline in my case) to the string with the vi editor. Either method will work.
This is how I added this command to my system. What commands would you add with these techniques?
James D. Keeline