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


10

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

[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
为了将来参考(假设这是一个Bash历史记录文件;它看起来像一个),请HISTTIMEFORMAT在编写时查看shell变量以控制格式。
Toby Speight

@Toby在显示(到stdout)时使用HISTTIMEFORMAT的值,但是在写入HISTFILE时,仅其状态(设置为null或unset)。
dave_thompson_085 '16

谢谢@dave,我不知道这一点(不是历史用户,而是我自己)。
Toby Speight 2016年

date -d不能说Solaris吗?...我假设这是在大多数使用GNU工具的系统上吗?(GNU AWK / Perl往往是处理日期转换的更可移植的方法)。gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < filestrftime似乎不可携带...)
Gert van den Berg

Answers:


6

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

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

BASH_REMATCH是一个数组,其第一个元素是Regex匹配中的第一个捕获组=~,在本例中为epoch。


如果要保留文件结构:

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

很好...将转换后的日期打印到屏幕上,现在如何获取该命令来替换文件中的条目?
机械师

@machinist检查我的编辑..
heemayl

1
如果您使用的最新版本bash,则printf可以自己进行转换:printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}"
chepner '16

14

虽然GNU可能sed具有以下功能:

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

这将是非常低效的(并且很容易引入任意命令注入漏洞1),因为这将意味着date为每#xxxx行运行一个shell和一个命令,几乎和shell while 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日期之后将零件保留为外壳注释


1
只使用一个实例date -f以流方式进行所有转换呢?
Digital Trauma

perl命令似乎在ctime $ 1之后添加了新行,而我找不到删除它的任何方法。
亚历克斯·哈维

1
@Alex。对。参见编辑。添加s标志使得.*输入中也包括换行符。您也可以使用strftime "%c", localtime $1
斯特凡Chazelas

@StéphaneChazelas非常感谢。这是一个很好的答案。
Alex Harvey

3

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

但是,GNU date有一个方便的-f选项,它允许单个流程实例date连续读取输入日期,而无需新的fork。因此,我们可以使用sedpaste并且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变体上执行其他操作。


date -d(从问题出发)也是不可移植的……(在FreeBSD上,它将尝试弄乱DST设置,在Solaris上,它将给出一个错误……)这个问题虽然没有指定操作系统,但是…
Gert van den Berg

@GertvandenBerg是的,此答案的最后一段已解决。
Digital Trauma

我的意思是,问问者的示例还存在可移植性问题……(他们可能应该标记了一个操作系统……)
Gert van den Berg,

1

假设您所需要的日期格式是您想要的,以下正则表达式将满足您的需求。

sed -E 's/\#(1[0-9]{9})(.*)/echo \1 $(date -d @\1)/e' log.file

请注意,每行只会替换一个纪元。


我收到该命令的以下错误: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
机械师

1
我的错误,修改了帖子。
Hatclock

0

使用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

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


0

我的解决方案如何在管道中做到这一点

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.