尝试设置 bash 脚本来自动发送数据文件。期望的最终结果是每周检查一次指定的目录,列出最近 2 天内修改的所有 Excel 文件,并将它们通过电子邮件发送到指定的收件人列表。
我已配置并运行 sendmail(使用 Gmail 中继)。我已经配置并工作了。一般来说,如果直接从 CLI 发送(发送和接收邮件,带有附件),我尝试发送的命令会起作用,但在尝试从脚本调用它们时,我会反复失败。一切似乎都源于目录和文件名包含空格的事实。我无法改变这一点 - 我无法控制文件的命名 - 但这似乎是我的脚本中的症结所在。
两个主要问题:
- 如果我尝试一次性发送所有文件,mutt 会报告无法附加附件:
Can't stat "/path\ to/file1.xls
/path\ to/file2.xls": No such file or directory
换行符作为 $FILES 变量的一部分传递。
- 如果我尝试循环遍历目录一次发送一个文件(我想要的结果,因为有些文件相当大),脚本会将空格解释为分隔符,无论是否转义 - 因此
/path/to/the\ files/file\ 1.xls
被视为 3 个值(/path/to/the\
、files/file\
、1.xls
)。
我确实让脚本的前半部分运行起来(一次发送所有文件),但在尝试添加循环时却让它中断了。当然,我没有保存早期的运行版本。尝试使用循环set ifs=$'\n'
来获取正确的分隔符,但当我将其放到位时,mutt 告诉我文件的完整路径不是文件,或者没有指定收件人。这有点令人抓狂。
脚本:
#!/bin/bash
# This file should send an email to specified recipients with data files for the week attached.
# Set reply-to address for Mutt
export REPLYTO="[email protected]"
# Replace with space separated email address of all recipients
EMAILS="[email protected] [email protected]"
# Get today's date for subject
# Date is in YYYY-MM-DD format
TODAY=$(date +%F)
# Set the message body
MBODY="Sending this week's data files.\n"
# Set the starting directory
# Don't bother escaping it, this is fixed in FILES variable below
DIR="/home/user/path to files"
# Get the list of files to send
FILES=$(find "$DIR" -type f -mtime -2 | sed 's/ /\\ /g' | grep ".xls")
# Check to see if we found any files or not
if [ -z "$FILES" ]; then
MBODY="No matching files added or modified within last 2 days. No files sent.\n"
echo "$MBODY" | mutt -s "Data files for $TODAY" $EMAILS
fi
# Send all files in a single email
echo $MBODY | mutt -s "Data files for $TODAY" -a "$FILES" -- $EMAILS
为了单独发送文件,我尝试了以下方法而不是上面的最后两行:
# Cycle through FILES array and send each file individually
for datafile in ${FILES[*]}
do
set IFS=$'\n\t'
echo $MBODY | mutt -s \"Data files for $TODAY\" -a $datafile -- $EMAILS
unset $IFS
done
有什么帮助吗?我已经被困在这个问题上有一段时间了。我不喜欢使用 mutt,甚至不喜欢使用 bash,如果用另一种语言更容易做到的话。
答案1
set "/home/user/path to files/"*.xls
for f do [ "$f" -nt "$two_day_old_file" ] && set "$@" "$f" ; shift ; done
touch "$two_day_old_file"
echo $MBODY | mutt -s "Data files for $TODAY" -a "$@" -- $EMAILS
要一次邮寄一封,请将echo
行更改为:
for mailf do echo "$MBODY" |
mutt -s "Data files for $TODAY" -a "$mailf" -- $EMAILS
done
可能会起作用,但你真正的问题在于:
...
set IFS=...
...
这根本不会影响内部字段分隔符的值,而是将该值分配IFS=...
给第一个位置参数或$1
。$IFS
仍然重视你之前的一切set
$1
。你只需要做:
IFS='
'
或者...
IFS=${IFS# }
...if设置为默认值,如果这是可执行脚本并且您没有更改脚本中的其他任何位置$IFS
,则必须使用默认值。$IFS
答案2
问题是$FILE
不是一个数组,但你像它一样访问它
for datafile in ${FILES[*]}
它只是返回一个巨大的字符串,因此一次返回所有文件。
要解决此问题,请在$FILE
循环中的每个文件中附加一个换行符,用于echo -e
单独返回每一行
# Get the list of files to send
FILES=$(find "$DIR" -type f -mtime -2 | sed 's/ /\\ /g' | grep ".xls" | sed '/.xls$/ a\\\n')
然后
for datafile in $(echo -e $FILES)
do
echo $MBODY | mutt -s \"Data files for $TODAY\" -a "$datafile" -- $EMAILS
done
此外,添加双引号应该$datafile
可以解决文件名中空格的问题
答案3
似乎已经想出了一个工作方法。避免$IFS
并且不尝试将整个目录列表作为字符串或数组处理 - 对找到的每个条目进行处理。将最后一个循环替换为以下内容:
find "$DIR" -type f -mtime -2 -name '*.xls*' -exec sh -c '
for file do
# Check to see if we found any files or not
if [ -z "$file" ]; then
echo "$NOBODY" | mutt -s "Data files for $TODAY" $EMAILS
# If we found files, then email them
else
echo "$MBODY" | mutt -s "Data files for $TODAY" -a "$file" -- $EMAILS
fi
done
' sh {} +
其中$NOBODY
是不同的消息正文,表明未找到匹配的文件。用于"*.xlsx*"
确保 .xls 和 .xlsx 文件都匹配。这有效,但是上面声明的变量没有传递到循环中。我在每个命令前面都加上了一个export
命令,然后上面的命令就完成了。 (如果有更优雅的方法将变量放入循环中,我没有找到。99% 关于循环和变量的讨论都是关于如何将它们从循环中取出,而不是放入循环中。)