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

GREP date from email header and make it the files creation date

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

结构可能看起来像这样:

#!/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

邮件 header 看起来像这样

X-Envelope-From: <some@mail.com>
X-Envelope-To: <my@mail.com>
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:' "" | grep -Eo '[0-9]+') &&
  humanTime=$(date -r "$unixTime" +%Y%m%d%H%M.%S) &&
  touch -t "$humanTime" ""
}
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...)因为它们已经完全过时,如果您使用更新的版本(例如 macportsbrew)。它还假设您使用的是 bash.

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

$ find . -type f -name '*.emlx'
foo/bar.emlx
baz.emlx
...

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

接下来我们需要遍历所有这些文件并处理它们中的每一个。由于多种原因(大量文件、带空格的文件名...),这比 for f in ... 稍微复杂一点。执行此操作的可靠方法是将 find 命令的输出重定向到while 循环:

while IFS= read -r -d '' f; do
  <process file "$f">
done < <(find . -type f -name '*.emlx' -print0)

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

现在我们必须对每个文件的处理进行编码。让我们先检索交货时间,假设它始终是最后一行的第二个单词 X-Delivery-Time::

awk '/^X-Delivery-Time:/ {t = } END {print t}' "$f"

这样做。如果您还不知道 awk,那么是时候了解一下它了。它是非常有用的文本处理瑞士刀具之一(sed 是另一个)。但是让我们稍微改进一下,让它 returns 遇到第一个而不是最后一个,一遇到它就停止,并检查时间戳是否是真实时间戳(位数):

awk '/^X-Delivery-Time:[[:space:]]+[[:digit:]]+$/ {print ; exit}' "$f"

正则表达式的 [[:space:]]+ 部分匹配 1 个或多个空格、制表符...,[[:digit:]]+ 匹配 1 个或多个数字。 ^$ 分别匹配行的开头和结尾。结果可以赋给一个shell变量:

t="$(awk '/^X-Delivery-Time:[[:space:]]+[[:digit:]]+$/ {print ; exit}' "$f")"

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

一旦我们有了这个交付时间,在您的示例中它看起来像 UNIX 时间戳(自 1970/01/01 以来的秒数),我们必须使用它来更改电子邮件文件的最后修改时间。执行此操作的命令是 touch:

$ man touch
...
touch [-A [-][[hh]mm]SS] [-acfhm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]] file ...
...

很遗憾,touch 想要 CCYYMMDDhhmm.SS 格式的时间。不用担心,date 实用程序可用于将 UNIX 时间戳转换为我们喜欢的任何格式。例如,使用您的示例时间戳 (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 ; 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 是否为空字符串 (if [ -z "$t" ])。如果是,我们打印一条消息并跳转到下一个文件 (continue)。只需将所有这些放在一个带有 shebang 行和 运行...

的文件中

如果您必须使用具有更复杂和可变格式的 Date 字段而不是 X-Delivery-Time 字段(例如 Date: Mon, 11 Jun 2018 10:36:14 +0200),最好安装一个touch 的最新版本和 Mac Ports or Homebrew 的 coreutils 包。那么:

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)

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

t="$(sed -rn 's/^Date:\s*(.*)//p;Ta;q;:a' "$f")"