如何使用 bash 脚本从文件中提取日期并转换为 unix 时间戳?

如何使用 bash 脚本从文件中提取日期并转换为 unix 时间戳?

我有一个文件,内容如下(可能超过 3 个条目)

A Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
B Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
C Version: 0.01.011 #3 PREEMPT Tue Apr 4 09:14:17 UTC 2023

现在我想unix timestamp从所有条目中提取日期和时间。也就是说,我对2019/05/21 03:33:042019/05/21 03:33:04和感兴趣Tue Apr 4 09:14:17 UTC 2023。这些条目应该采用相同的格式,以便我以后可以比较它们。另外,位置不固定(但它将是一行的最后两个字段)。

以下是bash脚本的一部分:

#!/bin/bash

ver_file="/home/test/tmp.txt"

ver_c=$(grep -E "C Version:" $ver_file | cut -d" " -f3-)

echo "$ver_c"

请问有人可以告诉我如何从文件中提取日期吗?

PS:我正在 Ubuntu 上使用 WSL2 进行开发,但目标将使用busybox date.如果缺少任何信息,请告诉我。

答案1

这个描述实际上并不是一个问题,而且有些令人困惑。但是,约会总是很有趣,所以我希望这会有所帮助。

提取非结构化日期取决于来源。文件中的日期是臭名昭著的。在给出的示例中,我看到的唯一明显的一致性是日期字符串位于行的末尾,并且全部从第 6 列开始。这就是我首先要查找的内容。

如果位置不是“固定”的,假设它们不会全部从第 6 列开始,那么第三行中的日期也不是最后两列。一个令人困惑的例子。不管怎样,还是可以做到的。需要更多的逻辑来评估不同类型的日期字符串以及如何处理每种字符串。同样,这实际上取决于输入数据质量(GIGO)。

这可以通过 GNU bash 和核心实用程序以不同的方式完成。需要强调的主要工具是 GNU date 命令,它可以评估日期的有效性并将其规范化。在本例中,“UTC 2023”是技术上一个有效的日期,这样 GNU 日期就不会出错(并且必须用 bash 捕获)。尽管如此,像这样的问题仍然可以非常简单地以高精度解决。

像这样,假设所有日期字符串都从第 6 列开始或有效日期位于最后两列......

while read line; do
    echo $line

    DATE_SIX="$(echo $line | cut -f6- -d' ')"
    if date --utc --date "${DATE_SIXE}" &> /dev/null; then
        DATE_SIX_NORMAL="$(date --utc --date "${DATE_SIX}")"
        DATE_SIX_EPOCH="$(date --utc --date "${DATE_SIX}" +%s)"
    else
        DATE_SIX_NORMAL="BAD DATE"
        DATE_SIX_EPOCH=0
    fi
    echo "DATE_SIX='${DATE_SIX}', DATE_SIX_NORMAL='${DATE_SIX_NORMAL}', DATE_SIX_EPOCH=${DATE_SIX_EPOCH}"

    DATE_LAST_TWO="$(echo $line | awk '{print $(NF-1)" "$(NF)}')"
    if [[ "${DATE_LAST_TWO}" != *":"* ]] || [[ "${DATE_LAST_TWO}" != *"/"* ]]; then
        # GNU date evaluates "UTC 2023" as a valid date, but it's not what's wanted ...
        DATE_LAST_TWO_NORMAL="BAD DATE"
        DATE_LAST_TWO_EPOCH=0
    else
        if date --utc --date "${DATE_LAST_TWO}" &> /dev/null; then
            DATE_LAST_TWO_NORMAL="$(date --utc --date "${DATE_LAST_TWO}")"
            DATE_LAST_TWO_EPOCH="$(date --utc --date "${DATE_LAST_TWO}" +%s)"
        else
            DATE_LAST_TWO_NORMAL="BAD DATE"
            DATE_LAST_TWO_EPOCH=0
        fi
    fi
    echo "DATE_LAST_TWO='${DATE_LAST_TWO}', DATE_LAST_TWO_NORMAL='${DATE_LAST_TWO_NORMAL}', DATE_LAST_TWO_EPOCH=${DATE_LAST_TWO_EPOCH}"

    echo
done < in.tmp

其输出如下所示。当然,DATE_EPOCH可以用作整数来进行比较。

A Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
DATE_SIX='2019/05/21 03:33:04', DATE_SIX_NORMAL='Tue May 21 03:33:04 AM UTC 2019', DATE_SIX_EPOCH=1558409584
DATE_LAST_TWO='2019/05/21 03:33:04', DATE_LAST_TWO_NORMAL='Tue May 21 03:33:04 AM UTC 2019', DATE_LAST_TWO_EPOCH=1558409584

B Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
DATE_SIX='2019/05/21 03:33:04', DATE_SIX_NORMAL='Tue May 21 03:33:04 AM UTC 2019', DATE_SIX_EPOCH=1558409584
DATE_LAST_TWO='2019/05/21 03:33:04', DATE_LAST_TWO_NORMAL='Tue May 21 03:33:04 AM UTC 2019', DATE_LAST_TWO_EPOCH=1558409584

C Version: 0.01.011 #3 PREEMPT Tue Apr 4 09:14:17 UTC 2023
DATE_SIX='Tue Apr 4 09:14:17 UTC 2023', DATE_SIX_NORMAL='Tue Apr  4 09:14:17 AM UTC 2023', DATE_SIX_EPOCH=1680599657
DATE_LAST_TWO='UTC 2023', DATE_LAST_TWO_NORMAL='BAD DATE', DATE_LAST_TWO_EPOCH=0

...除了 cut 之外还有其他方法,例如使用 awk、bash 字符串操作等。

GNU date 命令可以转换和标准化时间戳。

我还假设没有时区的原始日期是 UTC。

但是,也可以指定自定义时区(即​​在日期之前使用 TZ)。

例如,

$ date --utc --date="2019/05/21 03:33:04"
Tue May 21 03:33:04 AM UTC 2019
$ date --utc --date="Tue Apr 4 09:14:17 UTC 2023"
Tue Apr  4 09:14:17 AM UTC 2023

或者,将原始日期字符串转换为纪元时间...

$ date --utc --date="2019/05/21 03:33:04" +%s
1558409584
 date --utc --date="Tue Apr 4 09:14:17 UTC 2023" +%s
1680599657

...或者,使用 man date(1) 中的选项或 FORMAT 控件的任意组合,例如

$ date --utc --date="2019/05/21 03:33:04" --rfc-email
Tue, 21 May 2019 03:33:04 +0000
$ date --utc --date="2019/05/21 03:33:04" +%Y%m%d%H%M%S
20190521033304
 date --utc --date="Tue Apr 4 09:14:17 UTC 2023" +%s
1680599657
$ TZ=America/New_York date --date="Tue Apr 4 09:14:17 UTC 2023"
Tue Apr  4 05:14:17 AM EDT 2023

为了进行比较,我更喜欢 unix 纪元时间戳。

答案2

您尚未为第一个值定义时区,因此我假设您所在的世界任何地方都是“当地时间”。

我用过 GNU grepdate这里:

grep -oE '..../../.. ..:..:..$|... [[:digit:]]+ ..:..:.. [[:alnum:]]+ ....$' datafile |
    while IFS= read date
    do
        esec=$(date --date "$date" +%s)
        printf "%s --> %d\n" "$date" "$esec"
    done

对于您的示例数据,这里是在我的(英国 - GMT/BST)时区运行时的输出。除非您也处于 GMT/BST 或等效的 WET 时区,否则您的秒值将会有所不同。

2019/05/21 03:33:04 --> 1558405984
2019/05/21 03:33:04 --> 1558405984
Apr 4 09:14:17 UTC 2023 --> 1680599657

答案3

这些日期很容易被 perl 解析Date::Parse

要在前面添加 Unix 纪元时间(可以进行数字比较):

perl -MDate::Parse -pe '
  $_ = str2time(m{( 20\d\d/\d\d.*|\S+ \S+ \d+ \S+ UTC 20\d\d$)}) . " $_"
  ' < your-file

这使:

1558409584 A Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
1558409584 B Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
1680599657 C Version: 0.01.011 #3 PREEMPT Tue Apr 4 09:14:17 UTC 2023

或者对于 ISO8601 风格的时间格式(可以进行词法比较):

perl -MDate::Parse -MPOSIX -pe '
  $_ = strftime("%FT%T", strptime m{(20\d\d/\d\d.*|\S+ \S+ \d+ \S+ UTC 20\d\d$)}) . " $_"
  ' < your-file

这使:

2019-05-21T02:33:04 A Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
2019-05-21T02:33:04 B Version: x_02.28.03.03 000000 aaa 2019/05/21 03:33:04
2023-04-04T09:14:17 C Version: 0.01.011 #3 PREEMPT Tue Apr 4 09:14:17 UTC 2023

相关内容