Làm cách nào để thay thế dấu thời gian epoch trong một tệp bằng các định dạng khác?


10

Tôi có một tệp chứa ngày kỷ nguyên mà tôi cần chuyển đổi thành người có thể đọc được. Tôi đã biết cách thực hiện chuyển đổi ngày, ví dụ:

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

..nhưng tôi đang vật lộn để tìm ra cách sedđi qua tập tin và chuyển đổi tất cả các mục. Định dạng tệp trông như thế này:

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

1
Để tham khảo trong tương lai (giả sử đây là tệp lịch sử Bash; trông giống như tệp này), hãy tìm đến HISTTIMEFORMATbiến shell để kiểm soát định dạng tại thời điểm viết.
Toby Speight

@Toby giá trị của HISTTIMEFORMAT được sử dụng khi hiển thị (tới thiết bị xuất chuẩn), nhưng chỉ trạng thái của nó (được đặt thành bất kỳ thứ gì ngay cả null so với unset) mới quan trọng khi viết HISTFILE.
dave_thndry_085

Cảm ơn @dave, tôi đã không biết điều đó (bản thân tôi không phải là người sử dụng lịch sử).
Toby Speight

date -dkhông thể di động để nói Solaris ... Tôi cho rằng đây là trên một hệ thống có hầu hết các công cụ GNU? (GNU AWK / Perl có xu hướng là phương thức di động hơn để xử lý chuyển đổi ngày). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimedường như không di động ...)
Gert van den Berg

Câu trả lời:


6

Giả sử định dạng tệp nhất quán, với bashbạn có thể đọc từng dòng tệp, kiểm tra xem nó có ở định dạng nhất định không và sau đó thực hiện chuyển đổi:

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

BASH_REMATCHlà một mảng có phần tử đầu tiên là nhóm được bắt đầu tiên trong kết hợp Regex =~, trong trường hợp này là epoch.


Nếu bạn muốn giữ cấu trúc tệp:

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

điều này sẽ xuất nội dung đã sửa đổi thành STDOUT, để lưu nó trong một tệp, ví dụ out.txt:

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

Bây giờ nếu bạn muốn, bạn có thể thay thế tệp gốc:

mv out.txt file.txt

Thí dụ:

$ 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

Thật tuyệt .... nó in ngày chuyển đổi ra màn hình, bây giờ làm thế nào để tôi nhận lệnh đó để thay thế các mục trong tệp?
thợ máy

@machinist Kiểm tra các chỉnh sửa của tôi ..
heemayl

1
Nếu bạn đang sử dụng phiên bản gần đây bash, printfcó thể tự thực hiện chuyển đổi : printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
chepner

14

Trong khi điều đó có thể với GNU sedvới những thứ như:

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

Điều đó sẽ rất kém hiệu quả (và dễ dàng đưa ra các lỗ hổng tiêm lệnh tùy ý 1 ) vì điều đó có nghĩa là chạy một shell và một datelệnh cho mỗi #xxxxdòng, hầu như tệ như một while readvòng lặp shell . Ở đây, sẽ tốt hơn nếu sử dụng những thứ như perlhoặc gawk, đó là các tiện ích xử lý văn bản có tích hợp khả năng chuyển đổi ngày:

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

Hoặc là:

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

1 Nếu chúng ta đã viết ^#([0-9]).*thay vì ^#([0-9]).*$(như tôi đã làm trong phiên bản trước của câu trả lời này), thì ở các địa phương nhiều byte như UTF-8 (tiêu chuẩn hiện nay), với đầu vào như #1472047795<0x80>;reboot, trong đó <0x80>có giá trị byte 0x80 không tạo thành một ký tự hợp lệ, slệnh đó cuối cùng sẽ chạy date -d@1472047795<0x80>; reboot. Trong khi với phần phụ $, những dòng đó sẽ không được thay thế. Một cách tiếp cận khác sẽ là : s/^#([0-9])/date -d @\1 #/e, đó là để lại phần sau #xxxngày nhận xét


1
Điều gì về việc chỉ sử dụng một ví dụ duy nhấtdate -f để thực hiện tất cả các chuyển đổi theo cách khôn ngoan?
Chấn thương kỹ thuật số

Lệnh perl dường như thêm một dòng mới sau ctime $ 1 và tôi không thể tìm thấy bất kỳ cách nào để loại bỏ nó.
Alex Harvey

1
@Alex. Đúng. Xem chỉnh sửa. Thêm scờ làm cho nó .*cũng bao gồm dòng mới trên đầu vào. Bạn cũng có thể sử dụng strftime "%c", localtime $1.
Stéphane Chazelas

@ StéphaneChazelas cảm ơn rất nhiều. Đó là một câu trả lời tuyệt vời.
Alex Harvey

3

Tất cả các câu trả lời khác sinh ra một datequy trình mới cho mỗi ngày kỷ nguyên cần được chuyển đổi. Điều này có khả năng có thể thêm chi phí hiệu suất nếu đầu vào của bạn lớn.

Tuy nhiên, ngày GNU có một -ftùy chọn tiện dụng cho phép một cá thể quy trình duy nhất dateđọc liên tục ngày đầu vào mà không cần một ngã ba mới. Vì vậy, chúng ta có thể sử dụng sed, pastedatetheo cách này như vậy mà mỗi người duy nhất được sinh ra một lần (2x cho sed) bất kể như thế nào lớn đầu vào là:

$ 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
$ 
  • Hai sedlệnh tương ứng xóa các dòng chẵn và lẻ của đầu vào; cái đầu tiên cũng thay thế #bằng@ để cung cấp cho các định dạng kỷ nguyên dấu thời gian chính xác.
  • Đầu sedra đầu tiên sau đó được chuyển sang date -fthực hiện chuyển đổi ngày bắt buộc, cho mỗi dòng đầu vào mà nó nhận được.
  • Hai luồng này sau đó được xen kẽ vào đầu ra cần thiết bằng cách sử dụng paste. Các <( )cấu trúc là các thay thế quá trình bash giúp đánh lừa hiệu quả khi nghĩ rằng nó đang đọc từ các tên tệp đã cho khi thực tế nó đang đọc đầu ra được dẫn từ lệnh bên trong. -d '\n'nói pasteđể phân tách các dòng đầu ra lẻ và chẵn với một dòng mới. Bạn có thể thay đổi (hoặc xóa) cái này nếu ví dụ bạn muốn dấu thời gian trên cùng dòng với văn bản khác.

Lưu ý rằng có một số GNU và Bashism trong lệnh này. Điều này không tuân thủ Posix và không được dự kiến ​​là có thể mang theo bên ngoài thế giới GNU / Linux. Ví dụ, date -flàm một cái gì đó khác trên datebiến thể BSD của OSXes .


date -d(từ câu hỏi) cũng không khả dụng ... (Trên FreeBSD, nó sẽ cố gắng gây rối với cài đặt DST, trên Solaris, nó sẽ báo lỗi ...) Câu hỏi không chỉ định HĐH mặc dù ...
Gert van den Berg

@GertvandenBerg vâng, điều này được đề cập trong đoạn cuối của câu trả lời này.
Chấn thương kỹ thuật số

Ý tôi là mẫu của người hỏi cũng có vấn đề về tính di động ... (Có lẽ họ đã gắn thẻ HĐH ...)
Gert van den Berg

1

Giả sử định dạng ngày bạn có trong bài viết của bạn là những gì bạn muốn, regex sau đây sẽ phù hợp với nhu cầu của bạn.

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

Hãy chú ý đến thực tế điều này sẽ chỉ thay thế một epoch trên mỗi dòng.


Tôi nhận được lỗi sau với lệnh đó: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
thợ máy

1
Sai lầm của tôi, chỉnh sửa bài viết.
Hatclock

0

sử dụng sed:

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

đầu ra:

ر أغس 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

như ngôn ngữ địa phương của tôi là tiếng Ả Rập :)


0

Giải pháp của tôi làm thế nào để làm điều đó trong một đường ống

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.