来自电子邮件 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 实用程序(touch
、date
...)因为它们已经完全过时,如果您使用更新的版本(例如 macports
或 brew
)。它还假设您使用的是 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")"
我在 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 实用程序(touch
、date
...)因为它们已经完全过时,如果您使用更新的版本(例如 macports
或 brew
)。它还假设您使用的是 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")"