【问题标题】:GREP date from email header and make it the files creation date来自电子邮件标题的 GREP 日期并将其设为文件创建日期
【发布时间】:2021-10-04 03:56:21
【问题描述】:

我在 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: <some@mail.com>
X-Envelope-To: <my@mail.com>
X-Delivery-Time: 1535436541
...

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

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

【问题讨论】:

    标签: bash email timestamp apple-mail


    【解决方案1】:

    在 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
    

    【讨论】:

    • 这对我不起作用,创建日期仍然保持不变。我尝试添加echo "$unixTime",但终端中没有任何输出。谁能确认这是否有效?
    • @r00ky 感谢您的反馈。我想我发现了问题:我从未将文件传递给grep。你能再试一次吗?
    • @glennjackman 感谢您改进答案。但是,此脚本应该在 macOS 上运行,其中预安装的 date 命令没有记录在案的 -j 选项,请参阅 man date。此外,手册中没有说明date -r 会更改日期:date [-nu] [-r seconds] [+format]-r Print out the date and time that is seconds from the Epoch.。所以现在我把答案回滚了。如果你有mac并且可以确认-j与预装的date一起工作,你可以重新添加。
    • 是的!这确实适用于一个文件夹中的文件。但是一旦有子文件夹,我就会收到消息grep: folderxyz: Is a directory。关于如何在具有多个子文件夹的更大文件夹结构上递归地进行这项工作的任何建议?
    • @r00ky 哦,我忽略了你问题的那一部分。要递归,请使用find
    【解决方案2】:

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

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

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

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

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

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

    t="$(awk '/^X-Delivery-Time:[[:space:]]+[[:digit:]]+$/ {print $2; 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 $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 是否为空字符串 (if [ -z "$t" ])。如果是,我们打印一条消息并跳转到下一个文件(continue)。只需将所有这些放在带有 shebang 行的文件中并运行...

    如果您必须使用具有更复杂和可变格式的Date 字段而不是X-Delivery-Time 字段(例如Date: Mon, 11 Jun 2018 10:36:14 +0200),那么最好安装一个最新版本的touch Mac PortsHomebrew 的 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*(.*)/\1/p;Ta;q;:a' "$f")"
    

    【讨论】:

    • 我会使用awk '/^X-Delivery-Time:/ {print $2; exit}' "$f",这样您就无需在看到日期后继续处理电子邮件文件。
    • 哇,非常感谢您的深入解释!你会这么好心,把它们放在一个可执行的脚本中,我可以先尝试一下,然后再仔细研究所有细节?
    • @glennjackman 是的,当然。您阅读了一个正在进行的初步版本。最后一个按照您的建议进行,并且可以更好地处理不匹配的情况。
    • @r00ky 最后一部分已经是一个可执行脚本了。把它放在一个文件中,添加 shebang 行,你就完成了。
    • 到目前为止,这对我不起作用。我没有看到任何关于创建日期的更改,也没有看到 echo 命令在终端中的输出。
    猜你喜欢
    • 1970-01-01
    • 2012-07-05
    • 2018-10-29
    • 2016-07-25
    • 2010-12-14
    • 2015-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多