mac*_*ist 10 sed text-processing date
我有一个包含纪元日期的文件,我需要将其转换为人类可读的。我已经知道如何进行日期转换,例如:
[server01 ~]$ date -d@1472200700
Fri 26 Aug 09:38:20 BST 2016
Run Code Online (Sandbox Code Playgroud)
..但我正在努力弄清楚如何sed
遍历文件并转换所有条目。文件格式如下所示:
#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web
Run Code Online (Sandbox Code Playgroud)
Sté*_*las 14
虽然 GNU 可以sed
通过以下方式实现:
sed -E 's/^#([0-9]+).*$/date -d @\1/e'
Run Code Online (Sandbox Code Playgroud)
这将是非常低效的(并且很容易引入任意命令注入漏洞1),因为这意味着date
每#xxxx
行运行一个 shell 和一个命令,几乎和shellwhile read
循环一样糟糕。在这里,最好使用诸如perl
or 之类的东西gawk
,即具有内置日期转换功能的文本处理实用程序:
perl -MPOSIX -pe 's/^#(\d+).*/ctime $1/se'
Run Code Online (Sandbox Code Playgroud)
或者:
gawk '/^#/{$0 = strftime("%c", substr($0, 2))};1'
Run Code Online (Sandbox Code Playgroud)
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
日期之后保留部分作为外壳注释
假设文件格式一致,bash
您可以逐行读取文件,测试它是否为给定格式,然后进行转换:
while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && \
date -d@"${BASH_REMATCH[1]}"; done <file.txt
Run Code Online (Sandbox Code Playgroud)
BASH_REMATCH
是一个数组,其第一个元素是 Regex 匹配中的第一个捕获组=~
,在这种情况下是纪元。
如果要保留文件结构:
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
Run Code Online (Sandbox Code Playgroud)
这会将修改后的内容输出到 STDOUT,以将其保存在一个文件中,例如out.txt
:
while ...; do ...; done >out.txt
Run Code Online (Sandbox Code Playgroud)
现在,如果您愿意,可以替换原始文件:
mv out.txt file.txt
Run Code Online (Sandbox Code Playgroud)
例子:
$ 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
Run Code Online (Sandbox Code Playgroud)