完成 Bash 邮件合并脚本

作者:Dave Taylor

最后,我将完成邮件合并脚本,正好赶上复制人日。

还记得我之前开始写的邮件合并脚本吗? 是的,那是很久以前的事了。 我被Linux Journal周年纪念特刊分散了注意力(参见我的文章“回到过去:UNIX、Minix 和 Linux”),然后我在上一篇文章中完全跑题了(“分解 Apache 日志文件进行分析”)。 我将其归咎于……

松鼠!

哦,抱歉,回到正题。 我正在开发一个 shell 脚本,它可以让您指定一个文本文件,其中包含嵌入式字段名称,这些字段名称可以在包含大量字段值的文件中迭代替换。

每个字段都用 #fieldname# 表示,我确定了两种字段名称类别:固定和动态。 固定值可能是 #name#,它将直接来自数据文件,而动态值可能是 #date#,它将是当前日期。

更有趣的是,我还提出了计算值,特别是 #suggested#,它将是基于 #donation# 计算的值,以及 #date#,它将被当前日期替换。 超级高级版本将具有一种简单的语言,您可以在其中定义变量之间的关系,但让我们面对现实。 邮件合并。 这只是邮件合并。

读取和赋值

事实证明,此脚本所需的添加内容并不太难。 基本数据文件具有逗号分隔的字段名称,然后后续行具有与这些字段关联的值。

这是核心代码


if [ $lines -eq 1 ] ; then # field names
# grab variable names
declare -a varname=($f1 $f2 $f3 $f4 $f5 $f6 $f7)
else # process fields

# grab values for this line (can contain spaces)
declare -a value=("$f1" "$f2" "$f3" "$f4" "$f5" "$f6" "$f7")

事实证明,declare 函数非常适合此目的,它允许您基于第一行的内容创建数组 varname,然后不断替换数组 value 的值,以便 varname[1] = value[1],依此类推。

要添加额外的变量 #date##suggested#,您只需将它们附加到 varnamevalue 数组即可。 第一个很容易,但它确实突出了原始代码中的一个弱点,我必须通过添加引号来修复,如下所示


declare -a varname=("$f1" "$f2" "$f3" "$f4" "$f5"
  "$f6" "$f7" "date" "suggested")

需要引用 f1–f7 值,以确保 varname 数组中始终具有相同数量的值,而与实际值(如果有)无关。

将值添加到 value 数组稍微棘手一些,因为您实际上需要计算值。 日期很容易; 它可以计算一次


thedate=$(date "+%b %d, %Y")

计算建议值—donation/2—也相当容易完成,但必须在主循环中完成,以便它为发送的每封信件而更改。 演示中的原始捐款金额是字段 3,因此必要的代码是


# amount=f3, so suggested=(f3/2)
suggested="$(( $f3 / 2 ))"

幸运的是,主代码块根本不需要任何更改,因此只需进行少量调整,您现在就可以使用邮件合并脚本来生成,是的,一封完全自定义的电子邮件


$ subs.sh
------------------------
Apr 13, 2019

Dear Eldon Tyrell, I wanted to start by again thanking you
for your generous donation of $500 in July. We couldn't do
our work without support from humans like you, Eldon.
This year we're looking at some unexpected expenses,
particularly in Sector 5, which encompasses California, as
you know. I'm hoping you can start the year with an
additional contribution? Even $250 would be tremendously
helpful.
Thanks for your ongoing support.
Rick Deckard
Society for the Prevention of Cruelty to Replicants

请注意,datesuggested 都被替换为逻辑值,前者以令人愉悦的格式(上面的日期格式字符串)显示当前日期,后者显示建议值,即捐款的 50%。

多次循环

此时脚本中仍然存在的最大错误是,尽管捐赠者源列表列出了多个捐赠者,但脚本实际上只显示第一个捐赠者的结果,然后退出。

要调试这部分,让我们只看一下主循环中的关键行


while IFS=',' read -r f1 f2 f3 f4 f5 f6 f7
do
if [ $lines -eq 1 ] ; then # field names
# grab variable names
declare -a varname=("$f1" "$f2" "$f3" "$f4" "$f5"
    "$f6" "$f7" "date" "suggested")
else # process fields
. . .
echo "------------------------"
exec $sed "$SUBS" $inputfile

fi
done < "$datafile"

您能看到这里的问题吗? 出于对高效编码和快速执行的热情,脚本实际上通过 exec 调用而不是仅运行 sed 并继续循环来执行某种数字切腹自杀。

哎呀。 我的错!

解决方案很简单,只需从循环中删除单词 exec,它就会突然按预期工作。 那么问题是如何拆分所有单独的信件? 将所有内容作为一个长文本序列流式传输出来是相当无用的。

创建单独的输出文件

有许多可能的解决方案,但我将基于捐赠者的姓名创建单独的文件。 由于一旦解析数据,该值是 $f1,因此这很容易


outfile="$(echo $f1 | sed 's/ /-/g')-letter.txt"
echo "Letter for $f1. Output = $outfile"
$sed "$SUBS" $inputfile > $outfile

您可以看到,outfile 值是通过将所有空格替换为破折号来组成的,随后的 echo 语句提供了状态输出。 最后,实际的 sed 调用现在避开了邪恶的 exec 调用(好吧,它并不邪恶)并添加了输出重定向。

这是源捐赠者文件


$ cat donors.txt
name,first,amount,month,state
Eldon Tyrell,Eldon,500,July,California
Rachel,Rachel,100,March,New York
Roy Batty,Roy,50,January,Washington

这是运行脚本时发生的情况


$ sh bulkmail-subs.sh
Letter for Eldon Tyrell. Output = Eldon-Tyrell-letter.txt
Letter for Rachel. Output = Rachel-letter.txt
Letter for Roy Batty. Output = Roy-Batty-letter.txt

太棒了。 现在,其中一封信怎么样? 让我们看看您将要发送给那位富有的行业领袖 Eldon Tyrell 的信件


$ cat Eldon-Tyrell-letter.txt
Apr 13, 2019
Dear Eldon Tyrell, I wanted to start by again thanking you
for your generous donation of $500 in July. We couldn't do
our work without support from humans like you, Eldon.
This year we're looking at some unexpected expenses,
particularly in Sector 5, which encompasses California, as
you know. I'm hoping you can start the year with an
additional contribution? Even $250 would be tremendously
helpful.
Thanks for your ongoing support.
Rick Deckard
Society for the Prevention of Cruelty to Replicants

解决了—而且非常整洁。 现在,您会做哪些不同的事情或添加哪些内容来使此脚本更有用? 当然,不要过度杀伤。

在我的下一篇文章中,我计划采取完全不同的方向。 我不确定是什么,但我会想出一些东西。

Dave Taylor 在 UNIX 和 Linux 系统上编写 shell 脚本已经很长时间了。 他是Learning Unix for Mac OS XWicked Cool Shell Scripts 的作者。 您可以在 Twitter 上通过 @DaveTaylor 找到他,您也可以通过他的技术问答网站联系他:Ask Dave Taylor

加载 Disqus 评论