Text-Processing

如何用其他格式替換文件中的紀元時間戳?

  • November 18, 2018

我有一個包含紀元日期的文件,我需要將其轉換為人類可讀的。我已經知道如何進行日期轉換,例如:

[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

假設文件格式一致,bash您可以逐行讀取文件,測試它是否為給定格式,然後進行轉換:

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

BASH_REMATCH是一個數組,其第一個元素是正則表達式匹配中第一個擷取的組=~,在本例中為 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

雖然 GNU 可以sed通過以下方式實現:

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

這將非常低效(並且很容易引入任意命令注入漏洞1),因為這意味著date每行執行一個 shell 和一個命令#xxxx,實際上與shell循環一樣糟糕while read。在這裡,最好使用perlor之類的東西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為外殼註釋

引用自:https://unix.stackexchange.com/questions/306576