如何用其他格式替换文件中的纪元时间戳?

如何用其他格式替换文件中的纪元时间戳?

我有一个包含纪元日期的文件,我需要将其转换为人类可读的日期。我已经知道如何进行日期转换,例如:

[server01 ~]$ date -d@1472200700
Fri 26 Aug 09:38:20 BST 2016

..但我正在努力弄清楚如何遍历sed文件并转换所有条目。文件格式如下所示:

#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web

答案1

虽然 GNU 可以实现sed以下功能:

sed -E 's/^#([0-9]+).*$/date -d @\1/e'

这将是非常低效的(并且很容易引入任意命令注入漏洞1),因为这意味着date为每一行运行一个 shell 和一个命令#xxxx,实际上就像像 shellwhile read循环一样糟糕。在这里,最好使用诸如perl或 之类的东西gawk,即内置日期转换功能的文本处理实用程序:

perl  -MPOSIX -pe 's/^#(\d+).*/ctime $1/se'

或者:

gawk '/^#/{$0 = strftime("%c", substr($0, 2))};1'

1如果我们写的是^#([0-9]).*代替^#([0-9]).*$(就像我在这个答案的早期版本中所做的那样),那么在多字节语言环境中,例如 UTF-8 语言环境(当今的规范),输入类似 ,#1472047795<0x80>;reboot其中<0x80>字节值 0x80 是不形成有效的字符,例如该s命令将最终运行。date -d@1472047795<0x80>; reboot虽然使用 extra $,但这些行不会被替换。另一种方法是:s/^#([0-9])/date -d @\1 #/e,即将日期后面的部分保留#xxx为 shell 注释

答案2

假设文件格式一致,bash您可以逐行读取文件,测试它是否为给定格式,然后进行转换:

while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && \
      date -d@"${BASH_REMATCH[1]}"; done <file.txt

BASH_REMATCH是一个数组,其第一个元素是正则表达式匹配中第一个捕获的组,=~在本例中为纪元。


如果你想保留文件结构:

while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' \
   "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt

这会将修改后的内容输出到 STDOUT,以将其保存在文件中,例如out.txt

while ...; do ...; done >out.txt

现在,如果您愿意,您可以替换原始文件:

mv out.txt file.txt

例子:

$ cat file.txt
#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web

$ while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && date -d@"${BASH_REMATCH[1]}"; done <file.txt
Wed Aug 24 20:09:55 BDT 2016
Wed Aug 24 20:11:46 BDT 2016
Wed Aug 24 20:13:58 BDT 2016

$ while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt
#Wed Aug 24 20:09:55 BDT 2016
ll /data/holding/email
#Wed Aug 24 20:11:46 BDT 2016
cat /etc/rsyslog.conf
#Wed Aug 24 20:13:58 BDT 2016
ll /data/holding/web

答案3

所有其他答案都会date为每个需要转换的纪元日期生成一个新过程。如果您的输入很大,这可能会增加性能开销。

然而,GNU date 有一个方便的-f选项,允许单个进程实例date连续读取输入日期,而不需要新的 fork。因此,我们可以使用sed,paste并以这种方式,使得无论输入有多大,date每个都只生成一次(对于 2x ):sed

$ paste -d '\n' <( sed '2~2d;y/#/@/' epoch.txt | date -f - ) <( sed '1~2d' epoch.txt )
Wed Aug 24 07:09:55 PDT 2016
ll /data/holding/email
Wed Aug 24 07:11:46 PDT 2016
cat /etc/rsyslog.conf
Wed Aug 24 07:13:58 PDT 2016
ll /data/holding/web
$ 
  • 这两个sed命令基本上分别删除输入的偶数行和奇数行;第一个也替换#@以给出正确的纪元时间戳格式。
  • 然后,第一个sed输出通过管道传输到date -f它接收的每一行输入,进行所需的日期转换。
  • 然后使用将这两个流交织成单个所需的输出paste。构造<( )bash 进程替换这有效地欺骗了粘贴,使其认为它正在从给定的文件名中读取,而实际上它正在读取从内部命令通过管道传输的输出。 -d '\n'告诉paste用换行符分隔奇数和偶数输出行。例如,如果您希望时间戳与其他文本位于同一行,则可以更改(或删除)此设置。

请注意,此命令中有多个 GNUism 和 Bashism。这不符合 Posix 标准,并且不应期望能够在 GNU/Linux 世界之外移植。例如,date -f在 OSXes BSD 变体上执行其他操作date

答案4

使用 sed :

sed -r 's/\#([0-9]*)/echo $(date -d @\1)/eg' test.txt

输出 :

ر أغس 24 16:09:55 EET 2016
ll /data/holding/email
ر أغس 24 16:11:46 EET 2016
cat /etc/rsyslog.conf
ر أغس 24 16:13:58 EET 2016
ll /data/holding/web

因为我的语言是阿拉伯语:)

相关内容