用于通过电子邮件发送目录中所有匹配文件的脚本

用于通过电子邮件发送目录中所有匹配文件的脚本

尝试设置 bash 脚本来自动发送数据文件。期望的最终结果是每周检查一次指定的目录,列出最近 2 天内修改的所有 Excel 文件,并将它们通过电子邮件发送到指定的收件人列表。

我已配置并运行 sendmail(使用 Gmail 中继)。我已经配置并工作了。一般来说,如果直接从 CLI 发送(发送和接收邮件,带有附件),我尝试发送的命令会起作用,但在尝试从脚本调用它们时,我会反复失败。一切似乎都源于目录和文件名包含空格的事实。我无法改变这一点 - 我无法控制文件的命名 - 但这似乎是我的脚本中的症结所在。

两个主要问题:

  1. 如果我尝试一次性发送所有文件,mutt 会报告无法附加附件:
Can't stat "/path\ to/file1.xls
/path\ to/file2.xls": No such file or directory

换行符作为 $FILES 变量的一部分传递。

  1. 如果我尝试循环遍历目录一次发送一个文件(我想要的结果,因为有些文件相当大),脚本会将空格解释为分隔符,无论是否转义 - 因此/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% 关于循环和变量的讨论都是关于如何将它们从循环中取出,而不是放入循环中。)

相关内容