Bagaimana cara mengganti cap waktu zaman dalam file dengan format lain?

10

Saya memiliki file yang berisi tanggal zaman yang perlu saya konversi agar dapat dibaca oleh manusia. Saya sudah tahu bagaimana melakukan konversi tanggal, misalnya:

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

..tapi saya berjuang untuk mencari cara seduntuk berjalan melalui file dan mengkonversi semua entri. Format file terlihat seperti ini:

#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web
masinis
sumber
1
Untuk referensi di masa mendatang (dengan asumsi ini adalah file Bash history; sepertinya salah satunya), lihat ke HISTTIMEFORMATvariabel shell untuk mengontrol format pada saat penulisan.
Toby Speight
@Toby nilai HISTTIMEFORMAT digunakan ketika menampilkan (ke stdout), tetapi hanya statusnya (disetel ke apa pun bahkan nol vs tidak disetel) yang penting saat menulis HISTFILE.
dave_thompson_085
Terima kasih @ Dave, saya tidak tahu itu (tidak menjadi pengguna sejarah kali sendiri).
Toby Speight
date -dtidak portabel untuk mengatakan Solaris ... Saya menganggap ini pada sistem dengan sebagian besar alat GNU? (GNU AWK / Perl cenderung menjadi metode yang lebih portabel untuk menangani konversi tanggal). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimesepertinya tidak bisa dibawa-bawa ...)
Gert van den Berg

Jawaban:

6

Dengan asumsi format file yang konsisten, dengan bashAnda dapat membaca file baris demi baris, uji apakah itu dalam format yang diberikan dan kemudian lakukan konversi:

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

BASH_REMATCHadalah array yang elemen pertama adalah grup yang ditangkap pertama kali dalam pencocokan Regex =~,, dalam hal ini zaman.


Jika Anda ingin mempertahankan struktur file:

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

ini akan menampilkan konten yang dimodifikasi ke STDOUT, untuk menyimpannya dalam file misalnya out.txt:

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

Sekarang jika mau, Anda dapat mengganti file aslinya:

mv out.txt file.txt

Contoh:

$ 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
heemayl
sumber
Bagus .... yang mencetak tanggal dikonversi ke layar, sekarang bagaimana cara mendapatkan perintah itu untuk mengganti entri dalam file?
masinis
@machinist Periksa hasil edit saya ..
heemayl
1
Jika Anda menggunakan versi terbaru dari bash, printfdapat melakukan konversi itu sendiri: printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
chepner
14

Meskipun dimungkinkan dengan GNU seddengan hal-hal seperti:

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

Itu akan sangat tidak efisien (dan mudah untuk memperkenalkan kerentanan perintah injeksi sewenang-wenang 1 ) karena itu berarti menjalankan satu shell dan satu dateperintah untuk setiap #xxxxbaris, hampir sama buruknya dengan shell while readloop . Di sini, akan lebih baik menggunakan hal-hal seperti perlatau gawk, yaitu utilitas pemrosesan teks yang memiliki kemampuan konversi tanggal bawaan:

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

Atau:

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

1 Jika kita telah menulis ^#([0-9]).*alih-alih ^#([0-9]).*$(seperti yang saya lakukan dalam versi sebelumnya dari jawaban ini), maka di multi-byte locales seperti UTF-8 (norma saat ini), dengan input like #1472047795<0x80>;reboot, di mana itu <0x80>adalah nilai byte 0x80 yang tidak membentuk karakter yang valid, sperintah itu akhirnya akan berjalan date -d@1472047795<0x80>; rebootmisalnya. Sementara dengan tambahan $, garis-garis itu tidak akan diganti. Suatu pendekatan alternatif s/^#([0-9])/date -d @\1 #/eadalah:, yaitu meninggalkan bagian setelah #xxxtanggal sebagai komentar shell

Stéphane Chazelas
sumber
1
Bagaimana dengan menggunakan hanya satu contohdate -f untuk melakukan semua konversi secara stream-wise?
Digital Trauma
Perintah perl tampaknya menambahkan baris baru setelah waktu $ 1 dan saya tidak dapat menemukan cara untuk menghapusnya.
Alex Harvey
1
@Alex. Baik. Lihat edit. Menambahkan sflag menjadikannya .*juga termasuk baris baru pada input. Anda juga bisa menggunakan strftime "%c", localtime $1.
Stéphane Chazelas
@ StéphaneChazelas terima kasih banyak. Itu jawaban yang bagus.
Alex Harvey
3

Semua jawaban lain menelurkan dateproses baru untuk setiap tanggal yang perlu dikonversi. Ini berpotensi menambah overhead kinerja jika input Anda besar.

Namun tanggal GNU memiliki -fopsi praktis yang memungkinkan instance proses tunggal dateuntuk terus membaca tanggal input tanpa perlu garpu baru. Jadi kita bisa menggunakan sed, pastedan datedengan cara ini masing-masing hanya akan muncul sekali (2x untuk sed) terlepas dari seberapa besar inputnya:

$ 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
$ 
  • Kedua sedperintah tersebut masing-masing pada dasarnya menghapus garis genap dan ganjil dari input; yang pertama juga diganti #dengan @untuk memberikan format cap waktu zaman yang benar.
  • sedOutput pertama kemudian disalurkan ke date -fyang melakukan konversi tanggal yang diperlukan, untuk setiap baris input yang diterimanya.
  • Kedua aliran ini kemudian disambungkan ke dalam output yang dibutuhkan tunggal menggunakan paste. The <( )konstruksi yang proses substitusi pesta yang efektif trick sisipkan ke dalam pemikiran itu adalah membaca dari nama file yang diberikan saat itu sebenarnya membaca output pipa dari dalam perintah. -d '\n'memberitahu pasteuntuk memisahkan garis keluaran ganjil dan genap dengan baris baru. Anda dapat mengubah (atau menghapus) ini jika misalnya Anda ingin cap waktu di baris yang sama dengan teks lainnya.

Perhatikan bahwa ada beberapa GNUisme dan Bashisme dalam perintah ini. Ini bukan Posix-compliant dan seharusnya tidak diharapkan portabel di luar dunia GNU / Linux. Misalnya date -fmelakukan sesuatu yang lain pada datevarian OSXes BSD .

Trauma Digital
sumber
date -d(dari pertanyaan) juga non-portabel ... (Pada FreeBSD akan mencoba mengacaukan pengaturan DST, pada Solaris akan memberikan kesalahan ...) Pertanyaannya tidak menentukan OS meskipun ...
Gert van den Berg
@ GertvandenBerg ya, ini dibahas di paragraf terakhir dari jawaban ini.
Trauma Digital
Maksud saya bahwa sampel penanya juga memiliki masalah portabilitas ... (Mereka mungkin seharusnya menandai OS ...)
Gert van den Berg
1

Dengan asumsi format tanggal yang Anda miliki di pos adalah yang Anda inginkan, regex berikut harus sesuai dengan kebutuhan Anda.

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

Perhatikan fakta bahwa ini hanya akan menggantikan satu zaman per baris.

Hatclock
sumber
Saya mendapatkan kesalahan berikut dengan perintah itu: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
masinis
1
Kesalahan saya, mengedit posting.
Hatclock
0

menggunakan sed:

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

keluaran:

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

karena bahasa lokal saya adalah bahasa Arab :)

hassan
sumber
0

Solusi saya bagaimana melakukan itu di saluran pipa

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
kayn
sumber