0

I recently discovered that mutt allows me to do something I've been trying, without success, to do in my GUI e-mail client for years: (largely) automate the process of saving an e-mail message (*.eml) to a local directory of my choice.

This Unix & Linux StackExchange post shared a rough-and-ready mutt macro for handling this process. As you'll see, however, the macro's grep commands reach for the -P flag (i.e. Perl regular expressions) and, thus, do not run on the Macbook I'm currently using:

#!/usr/bin/env zsh
#Saved piped email to "$1/YYMMDD SUBJECT.eml"

# Don't overwrite existing file
set -o noclobber

message=$(cat)

mail_date=$(<<<"$message" grep -oPm 1 '^Date: ?\K.*')
formatted_date=$(date -d"$mail_date" +%y%m%d)
# Get the first line of the subject, and change / to ∕ so it's not a subdirectory
subject=$(<<<"$message" grep -oPm 1 '^Subject: ?\K.*' | sed 's,/,∕,g')

if [[ $formatted_date == '' ]]; then
  echo Error: no date parsed
  exit 1
elif [[ $subject == '' ]]; then
  echo Warning: no subject found
fi

echo "${message}" > "$1/$formatted_date $subject.eml" && echo Email saved to "$1/$formatted_date $subject.eml"

I'm far from comfortable with complex grep queries, so my meager efforts to make this script work (e.g. swapping out the -P flag for the -e flag) have met with failure.

To wit, this is the error message thrown when I swap in the -e flag:

grep: 1: No such file or directory
grep: ^Date: ?\K.*: No such file or directory
usage: date [-jnRu] [-d dst] [-r seconds] [-t west] [-v[+|-]val[ymwdHMS]] ...
            [-f fmt date | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+format]
grep: 1: No such file or directory
grep: ^Subject: ?\K.*: No such file or directory
Error: no date parsed
Press any key to continue...

Mercifully, the error messages here are pretty clear. The script's use of 1 appears to be faulty, as does the last bit of the anchored grep query (e.g. ^Date: ?\K.*).

Unfortunately, I have no idea how to begin to resolve these errors.

What I'm attempting to do is, in fact, quite simple. Rather than manually running | cat > FILE_PATH/email.eml I'd like to simply be able to hit a key in mutt, extract the selected e-mail's date (e.g. everything to the end-of-the-line after Date:) and subject (e.g. everything to the end-of-line after Subject), then use that information to generate the name of the *.eml file saved locally (e.g. YYYY-MM-DD subject.eml).

Does anyone have any suggestions on how to make this script play nice in MacOS?

iamthelabhras
  • 351
  • 2
  • 12
  • 1
    The `-P` is necessary to make `\K` work. I don't see what would be the point in using `-e`. This option is maily used if you have a pattern starting with a dash, of specify that more than one pattern is supposed to match. – user1934428 Sep 12 '22 at 06:39
  • 1
    BTW, if the only problem is the difference between GNU grep and MacOS grep, I suggest thta you install the Gnu utilities for Mac. See [here](https://ryanparman.com/posts/2019/using-gnu-command-line-tools-in-macos-instead-of-freebsd-tools/). – user1934428 Sep 12 '22 at 06:43

1 Answers1

2

One option is to use zsh parameter expansions to parse the values, so there's no need to worry about grep versions. As a bonus, this launches fewer subprocesses:

#!/usr/bin/env zsh

# Save piped email to "$1/YYMMDD SUBJECT.eml"

# Don't overwrite existing file
set -o noclobber

# stdin split into array of lines:
message=("${(@f)$(<&0)}")

mail_date=${${(M)message:#Date: *}[1]#Date: }
formatted_date=$(gdate -d"$mail_date" +%y%m%d)
# Get the subject, and change '/' to '-' 
subject=${${${(M)message:#Subject: *}[1]#Subject: }//\//-}

if [[ -z $formatted_date ]]; then
  print -u2 Error: no date parsed
  exit 1
elif [[ -z $subject ]]; then
  print -u2 Warning: no subject found
fi

outdir=${1:?Output directory must be specified}
if [[ ! -d $outdir ]]; then
  print -u2 Error: no output directory $outdir
  exit 1
fi

outfile="$outdir/$formatted_date $subject.eml"
print -l $message > "$outfile" && print Email saved to "$outfile"

This statement gets the date from the array of lines in message:

mail_date=${${(M)message:#Date: *}[1]#Date: }
  • ${(M)...:#...} - gets elements that match a pattern from the array. Here we use it to find elements that start with Date: .
  • ${...[1]} - returns the first match.
  • ${...#Date: } - removes the prefix Date: , leaving the date string.

This similar statement has an additional expansion that replaces all instances of / with -:

subject=${${${(M)message:#Subject: *}[1]#Subject: }//\//-}

The parameter expansions are documented in the zshexpn man page.


PS: trailing newlines will be removed from the message written to the file. This is a difficult-to-avoid consequence of using a command substitution like $(...). It's not likely to be a significant issue.

Gairfowl
  • 2,226
  • 6
  • 9