来自电子邮件标题的 GREP 日期并将其设为文件创建日期

问题描述

我在 Mac 终端上,想从电子邮件标头中“grep”一个字符串(这是一个 UNIX 时间戳),将其转换为操作系统可以使用的格式,并将其作为文件的创建日期。我想对文件夹中的所有邮件(具有多个可能的子文件夹)递归执行此操作。

结构大概是这样的:

#!/bin/bash

for i in `ls`
do
  # Find the date field (X-Delivery-Time) inside an email header and grep the UNIX timestamp
  # convert timestamp to a format the OS can work with
  # overwrite the existing creation date with the new one
done

邮件标题看起来像这样

X-Envelope-From: <[email protected]>
X-Envelope-To: <[email protected]>
X-Delivery-Time: 1535436541
...

一些背景:Apple Mail 使用文件创建日期作为 Apple Mail 中显示的日期。这就是为什么将邮件从一台服务器移动到另一台服务器后,所有邮件现在都显示相同的日期,这使得无法进行排序。

由于我是 Terminal/Bash 的新手,所以不胜感激。谢谢

解决方法

在 Mac 上这应该可以工作,但由于我没有 Mac,我无法自己测试。我假设您的电子邮件文件具有 .emlx 扩展名。

对于单个目录:

for i in ./*.emlx; do
    unixTime=$(grep -m1 '^X-Delivery-Time:' "$i" | grep -Eo '[0-9]+') &&
    humanTime=$(date -r "$unixTime" +%Y%m%d%H%M.%S) &&
    touch -t "$humanTime" "$i"
done

对于整个目录树:

fixdate() {
  unixTime=$(grep -m1 '^X-Delivery-Time:' "$1" | grep -Eo '[0-9]+') &&
  humanTime=$(date -r "$unixTime" +%Y%m%d%H%M.%S) &&
  touch -t "$humanTime" "$1"
}
export -f fixdate
find . -name '*.emlx' -exec bash -c 'fixdate "$@"' . {} \;

或者,如果您安装了 bash 4 或更高版本(macOS 仍然默认使用 3)

shopt -s globstar
for i in ./**/*.emlx; do
    unixTime=$(grep -m1 '^X-Delivery-Time:' "$i" | grep -Eo '[0-9]+') &&
    humanTime=$(date -r "$unixTime" +%Y%m%d%H%M.%S) &&
    touch -t "$humanTime" "$i"
done
,

以下内容假设您使用的是默认的 macOS 实用程序(touchdate...),因为它们已经完全过时了,如果您使用更新的版本(例如 {{1 }} 或 macports)。它还假定您使用的是 brew

如果您有子文件夹,bash 不是正确的工具。无论如何,ls 的输出不是针对计算机的,而是针对人类的。因此,首先要做的是找到所有电子邮件文件。你猜怎么着?执行此操作的实用程序名为 ls:

find

从当前目录 ($ find . -type f -name '*.emlx' foo/bar.emlx baz.emlx ... ) 开始搜索名称为 -type f (.) 的真实文件 (anything.emlx)。适应你的情况。如果所有文件都是电子邮件文件,您可以跳过 -name '*.emlx' 部分。

接下来我们需要遍历所有这些文件并处理每个文件。这比 -name ... 稍微复杂一些,原因有很多(文件数量多、文件名带有空格……)一个可靠的方法是将 for f in ... 命令的输出重定向到while循环:

find

while IFS= read -r -d '' f; do <process file "$f"> done < <(find . -type f -name '*.emlx' -print0) -print0 选项用于使用空字符而不是默认换行符分隔文件名。 find 部分是一种将 < <(find...) 的输出重定向到 while 循环输入的方法。 find 读取 while IFS= read -r -d '' f; do 生成的每个文件名,将其存储在 shell 变量 find 中,保留前导和尾随空格(f),反斜杠({{ 1}}) 并使用空字符作为分隔符 (IFS=)。

现在我们必须对每个文件的处理进行编码。我们首先检索交货时间,假设它总是以-r开头的最后一行的第二个单词:

-d ''

这样做。如果您还不了解 awk,那么现在是学习它的时候了。它是非常有用的文本处理瑞士刀之一(sed 是另一个)。但是让我们稍微改进一下,使其返回第一个遇到的交付时间而不是最后一个,在遇到它时立即停止,并检查时间戳是否是真实的时间戳(数字):

X-Delivery-Time:

正则表达式的 awk '/^X-Delivery-Time:/ {t = $2} END {print t}' "$f" 部分匹配 1 个或多个空格、制表符等,而 awk '/^X-Delivery-Time:[[:space:]]+[[:digit:]]+$/ {print $2; exit}' "$f" 匹配 1 个或多个数字。 [[:space:]]+[[:digit:]]+ 分别匹配行的开头和结尾。结果可以分配给一个shell变量:

^

请注意,如果没有匹配项,$ 变量将存储空字符串。稍后我们将使用它来跳过此类文件。

一旦我们有了这个交付时间,在您的示例中它看起来像一个 UNIX 时间戳(自 1970/01/01 以来的秒数),我们必须使用它来更改电子邮件文件的最后修改时间。执行此操作的命令是 t="$(awk '/^X-Delivery-Time:[[:space:]]+[[:digit:]]+$/ {print $2; exit}' "$f")" :

t

不幸的是,touch 需要 $ man touch ... touch [-A [-][[hh]mm]SS] [-acfhm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]] file ... ... 格式的时间。不用担心,touch 实用程序可用于将 UNIX 时间戳转换为我们喜欢的任何格式。例如,使用您的示例时间戳 (CCYYMMDDhhmm.SS):

date

我们快完成了:

1535436541

注意我们如何测试 $ date -r 1535436541 +%Y%m%d%H%M.%S 201808280809.01 是否为空字符串 (while IFS= read -r -d '' f; do # uncomment for debugging # echo "processing $f" t="$(awk '/^X-Delivery-Time:[[:space:]]+[[:digit:]]+$/ {print $2; exit}' "$f")" if [ -z "$t" ]; then echo "no delivery time found in $f" continue fi # uncomment for debugging # echo touch -t "$(date -r "$t" +%Y%m%d%H%M.%S)" "$f" touch -t "$(date -r "$t" +%Y%m%d%H%M.%S)" "$f" done < <(find . -type f -name '*.emlx' -print0) )。如果是,我们打印一条消息并跳转到下一个文件 (t)。只需将所有这些放在一个带有 shebang 行的文件中并运行...

如果您必须使用具有更复杂和可变格式(例如 if [ -z "$t" ])的 continue 字段而不是 X-Delivery-Time 字段,那么最好安装一个最近的带有 Mac PortsHomebrew 的 coreutils 包的 Date 版本。然后:

Date: Mon,11 Jun 2018 10:36:14 +0200

awk 命令稍微复杂一些。它打印没有 touch 前缀的匹配行。以下 sed 命令将以更紧凑的形式执行相同的操作,但实际上并不会更具可读性:

while IFS= read -r -d '' f; do
  t="$(awk '/^Date:/ {print gensub(/^Date:[[:space:]+](.*)$/,"\\1","1"); exit}' "$f")"
  if [ -z "$t" ]; then
    echo "no delivery time found in $f"
    continue
  fi
  touch -d "$t" "$f"
done < <(find . -type f -name '*.emlx' -print0)