Using just Bash and Sendmail to send multiple input files and / or a pipe as attachments in an email

Published on
14,910 Points
Last Modified:
Over the years I've spent many an hour playing on hardened, DMZ'd servers, with only a sub-set of the usual GNU toy's to keep me company; frequently I've needed to save and send log or data extracts from these server back to my PC, or to others, and more often than not, to do this on a regular basis.

Simple enough on an open box, just cron a script to extract and format the necessary data and either send it as a uuencoded / base 64 mmencoded attachment in an email, or simply copy / scp's the file to somewhere you can get at it e.g. a NFS or SMB mount. BUT if the server is in a DMZ you'll likely find access to any shared internal file systems and / or permission to store a private key to scp / ssh into your network not available to you, leaving email as the only practical method to send data to yourself or others being.

If the box is performing a non trivial function in the DMZ it's more than likely been hardened to some extent, and all non essential toys removed, so you'll probably find your lacking the likes of uuencode, mmencode, and perl, leaving you none of the standard mechanism to encode and to attach files to email.

You could take the quick and dirty option of just piping the data / log into an email body an hoping Outlook will treat your data kindly, and not assume it's a malicious script, or part of the email header, or in need of some character-set conversion. This approach kept me happy for a number of years, but while working with Rockroads several  year back, I was made aware of plain text mime attachments, and haven't looked back.

Plain text mime encoding attachments offer you the ability to:
Attach CSV, XML or TXT files to a mail,.

To Name the attachments, as they are appear to the recipient.

Allows you to add a BODY to the email, explaining the attachments

Offer a mechanism to specify the encoding bits and character set

Support multiple attachments in a single mail (multi-part mime)

Allow the recipient to simply save an attachments, rather than having to cut-n-paste the data out of the email.

Requires only shell and sendmail to be available to you on the box.

Anyway per the Wikipedia article, all you need to convert a plain old smtp email in to a mime encoded one is a few content type entries, in your message body, define a unique boundary separator (a string that won't appear in your data), and to add a separator and a few more content type entries between your message body and the attachments data, another separator and finally a couple of -- to mark the end of the mail.

So in a few minutes you can replace your boring old mail line in your existing script with something a bit more sophisticated, but as I've found I regularly need to send output to myself I have knocked out a few generic scripts over the years that provide a mechanism to send myself one or more file, or the output from a command.

Basically the script assumes all the command line argument along with any input piped or redirected in are files you want attached to a predefined email, to be sent to a predefined user. If you have a look through the script you'll obviously spot and realise you'll need to tweak some of the entries in the ---Tweak to suit--- block. Other than that the script is much ready to run.

One line possibly worth noting is the following line:

inputMethod=`stat -L -c %F /proc/$$/fd/0`

Open in new window

The output from the stat call, on the current processes standard input [/prod/$$/fd/0], will indicate if a file [regular file] or stream [fifo] have been piped / redirected into the script, then via a couple of additional if, then, fi blocks that input is ALSO attached to the email, permitting the script to be used in-line, anyway the script:
# Purpose: email the parametrised list of files to a set user along with any content piped XOR redirected in.

# ========================
DT=`date '+%Y-%m-%d'`
BOUNDARY=`date +%s|md5sum`
#----- Tweak to suit -----
MAILTO="To: arober11@somehostorother.com"
SUBJECT_LINE="Files from `uname -a` on $DT"
MSGBODY="Files from `uname -a` on $DT"
#----- CMD locations  -----
#----- Tweak to suit ends-----

#----- Test if anything has been piped or directed at us -----
inputMethod=`$statCMD -L -c %F /proc/$$/fd/0`

# ========================
# Attach file to msg
# ========================
attach_file() {

  cat >> $TMP_FL << EOF

Content-Type: $MIMETYPE; name="$MAIL_FL"
Content-Transfer-Encoding: 8bit
Content-Disposition: attachment; filename="$MAIL_FL"


# ========================
# Create tmp msg file
# ========================
create_msg() {

  cat > $TMP_FL <<EOF
From: $FROM
`echo -e $MAILTO`
Reply-To: $REPLY_TO
Content-Type: multipart/mixed; boundary="$BOUNDARY"

This is a MIME formatted message.  If you see this text it means that your
email software does not support MIME formatted messages, but as plain text
encoded you should be ok, with a plain text file.

Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 8bit
Content-Disposition: inline



  while [ $# -gt 0 ]
    if [ -r $1 -a -f $1 -a -s $1 ]
        MAIL_FL="`$basenameCMD $1`"

  #See if anything has been piped in via STDIN, if it has add it as another attachment
  if [ "$inputMethod" = "fifo" ]
    cat > "${TMP_FL}.from_pipe.stdin"

  if [ "$inputMethod" = "regular file" ]
    cat > "${TMP_FL}.from_fl.stdin"
    flName="`$readlinkCMD \`$statCMD  -L -c %n /proc/$$/fd/0\``"
    MAIL_FL="`$basenameCMD $flName`"

  echo -e "\n--$BOUNDARY--\n" >> $TMP_FL

# ====
# Main
# ====
if [ $# -gt 0 -a -f "$1" -a -r "$1" ]
  create_msg $@
elif echo $inputMethod | egrep -q "character special"
  thisCMD=`$basenameCMD $0`
  echo "Error: No files passed as arguments, piped in, or re-directed in :("
  echo "usage:    [|]$thisCMD [<file1.txt> [<file2.txt>*]] [<]"
  echo "Purpose:  email the paramertised list of files, to a set user, along with any content piped XOR redirected in"
  echo "examples: cat file1.txt | $thisCMD"
  echo "                          $thisCMD file1.txt file2.txt"
  echo "                          $thisCMD                     < file1.txt"
  echo "          cat file1.txt | $thisCMD file2.txt"
  echo "                          $thisCMD file1.txt file2.txt < file3.txt"
  exit 1

# =================
# Email the file(s)
# =================
$sendmail $sendmailParms -t <$TMP_FL
rm -f $TMP_FL*

Open in new window

Andrew Roberts, Oct 2011
Ask questions about what you read
If you have a question about something within an article, you can receive help directly from the article author. Experts Exchange article authors are available to answer questions and further the discussion.
Get 7 days free