จะแทนที่ epoch timestamps ในไฟล์ด้วยรูปแบบอื่นได้อย่างไร?


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ตัวแปรเชลล์เพื่อควบคุมรูปแบบในขณะที่เขียน
Toby Speight

@Toby ค่าของ HISTTIMEFORMAT จะใช้เมื่อแสดง (เป็น stdout) แต่เฉพาะสถานะของมัน (ตั้งค่าเป็นอะไรก็ได้ที่เป็นโมฆะและไม่ได้ตั้งค่า) สำคัญเมื่อเขียน HISTFILE
dave_thompson_085

ขอบคุณ @dave ฉันไม่ทราบว่า (ไม่ได้เป็นผู้ใช้ประวัติศาสตร์ครั้งตัวเอง)
Toby Speight

date -dไม่สามารถพกพาไปบอกว่า Solaris ได้ ... ฉันคิดว่านี่เป็นระบบที่มีเครื่องมือของ GNU เป็นส่วนใหญ่ใช่ไหม (GNU AWK / Perl มีแนวโน้มที่จะเป็นวิธีที่พกพาได้มากขึ้นในการจัดการกับการแปลงวันที่) gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimeดูเหมือนจะไม่ใช่พกพา ... )
Gert van den Berg

คำตอบ:


6

สมมติว่ารูปแบบไฟล์สอดคล้องกันโดยbashคุณสามารถอ่านไฟล์ได้ทีละบรรทัดทดสอบว่ามันอยู่ในรูปแบบที่กำหนดหรือไม่จากนั้นทำการแปลง:

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

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

สิ่งนี้จะเอาท์พุทเนื้อหาที่แก้ไขไปยัง 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

Nice .... ที่พิมพ์วันที่แปลงเป็นหน้าจอตอนนี้ฉันจะรับคำสั่งเพื่อแทนที่รายการในไฟล์ได้อย่างไร
ช่างเครื่อง

@machinist ตรวจสอบการแก้ไขของฉัน ..
heemayl

1
หากคุณกำลังใช้รุ่นล่าสุดbash, สามารถทำแปลงตัวเอง:printf printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}"
chepner

14

ในขณะที่มันเป็นไปได้ด้วย GNU sedกับสิ่งที่ชอบ:

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

ที่จะไม่มีประสิทธิภาพชะมัด (และเป็นเรื่องง่ายที่จะแนะนำช่องโหว่ฉีดคำสั่งโดยพล1 ) ในฐานะที่จะหมายถึงการทำงานอย่างใดอย่างหนึ่งเปลือกและเป็นหนึ่งในdateคำสั่งสำหรับแต่ละ#xxxxสายแทบเป็นไม่ดีเท่าที่เปลือก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เช่นการทำงาน ในขณะที่มีการเสริม$สายเหล่านั้นจะไม่ถูกทดแทน วิธีการทางเลือกจะเป็น: s/^#([0-9])/date -d @\1 #/eนั่นคือปล่อยส่วนหลัง#xxxวันที่เป็นความคิดเห็นเปลือก


1
แล้วการใช้เพียงอินสแตนซ์เดียวของdate -fการทำ Conversion ทั้งหมดในลักษณะที่ชาญฉลาดล่ะ
Digital Trauma

ดูเหมือนว่าคำสั่ง perl จะเพิ่มบรรทัดใหม่หลังจาก ctime $ 1 และฉันไม่สามารถหาวิธีลบมันได้
Alex Harvey

1
@ Alex ขวา. ดูการแก้ไข การเพิ่มการsตั้งค่าสถานะทำให้.*รวมถึงการขึ้นบรรทัดใหม่กับอินพุต strftime "%c", localtime $1นอกจากนี้คุณยังสามารถใช้
Stéphane Chazelas

@ StéphaneChazelasขอบคุณมาก มันเป็นคำตอบที่ยอดเยี่ยม
Alex Harvey

3

คำตอบอื่น ๆ ทั้งหมดวางไข่dateกระบวนการใหม่สำหรับทุกวันเวลาที่จะต้องมีการแปลง สิ่งนี้อาจเพิ่มค่าใช้จ่ายประสิทธิภาพหากอินพุตของคุณมีขนาดใหญ่

อย่างไรก็ตามวันที่ของ GNU มี-fตัวเลือกที่ใช้งานง่ายซึ่งอนุญาตให้อินสแตนซ์กระบวนการเดียวของdateการอ่านวันที่ที่ป้อนอย่างต่อเนื่องโดยไม่ต้องใช้ทางแยกใหม่ ดังนั้นเราจึงสามารถใช้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แปลงวันที่ที่ต้องการสำหรับทุกบรรทัดอินพุตที่ได้รับ
  • ทั้งสองลำธาร interlaced pasteแล้วลงในการส่งออกต้องเดียวโดยใช้ <( )สร้างเป็นแทนกระบวนการทุบตีที่มีประสิทธิภาพเคล็ดลับวางโดยคิดว่ามีการอ่านจากชื่อไฟล์ที่ได้รับเมื่อในความเป็นจริงการอ่านเอาท์พุทประปาจากภายในคำสั่ง -d '\n'บอกpasteให้แยกบรรทัดคี่และเอาท์พุทด้วยการขึ้นบรรทัดใหม่ คุณสามารถเปลี่ยน (หรือลบ) สิ่งนี้ได้เช่นหากคุณต้องการให้ประทับเวลาในบรรทัดเดียวกันกับข้อความอื่น

โปรดทราบว่ามี GNUisms และ Bashisms หลายคำสั่งนี้ สิ่งนี้ไม่สอดคล้องกับ Posix และไม่ควรคาดว่าจะพกพาออกนอกโลก GNU / Linux เช่นdate -fทำอย่างอื่นใน OSXes dateตัวแปรBSD


date -d(จากคำถาม) ยังไม่สามารถพกพาได้ ... (บน FreeBSD มันจะพยายามยุ่งกับการตั้งค่า DST บน Solaris มันจะให้ข้อผิดพลาด ... ) คำถามไม่ได้ระบุระบบปฏิบัติการแม้ว่า ...
Gert van เดนเบิร์ก

@GertvandenBerg ใช่นี่คือการแก้ไขในวรรคสุดท้ายของคำตอบนี้
บาดเจ็บทางระบบดิจิตอล

ฉันหมายความว่าตัวอย่างของผู้ถามมีปัญหาเรื่องความสะดวกในการพกพา ... (พวกเขาน่าจะติดแท็กระบบปฏิบัติการ ... )
Gert van den Berg

1

สมมติว่ารูปแบบวันที่ที่คุณมีในโพสต์เป็นสิ่งที่คุณต้องการ regex ต่อไปนี้ควรตรงกับความต้องการของคุณ

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.