Apakah ada utilitas Unix untuk menambahkan cap waktu ke stdin?

168

Saya akhirnya menulis skrip kecil cepat untuk ini dengan Python, tapi saya bertanya-tanya apakah ada utilitas yang dapat Anda masukkan teks yang akan menambah setiap baris dengan beberapa teks - dalam kasus khusus saya, cap waktu. Idealnya, penggunaannya akan seperti:

cat somefile.txt | prepend-timestamp

(Sebelum Anda menjawab sed, saya mencoba ini:

cat somefile.txt | sed "s/^/`date`/"

Tapi itu hanya mengevaluasi perintah tanggal sekali ketika sed dieksekusi, jadi stempel waktu yang sama salah dituliskan ke setiap baris.)

Joe Shaw
sumber
Apakah cat somefile.txtagak "menyesatkan"? Saya berharap itu terjadi "sekaligus" dan memiliki cap waktu tunggal. Bukankah ini menjadi program tes yang lebih baik: (echo a; sleep 1; echo b; sleep 3; echo c; sleep 2)?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

185

Bisa mencoba menggunakan awk:

<command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); }'

Anda mungkin perlu memastikan bahwa <command>menghasilkan output buffered garis, yaitu itu mengalir aliran output setelah setiap baris; timestamp awkmenambahkan akan menjadi waktu di mana ujung garis muncul pada pipa inputnya.

Jika awk menunjukkan kesalahan, maka cobalah gawk.

Kieron
sumber
31
Di sistem saya 'awk' sendiri buffering. (Ini bisa bermasalah untuk file log). Saya memperbaikinya dengan menggunakan: awk '{print strftime ("% Y-% m-% dT% H:% M:% S"), $ 0; fflush (); } '
user47741
19
strftime () tampaknya merupakan ekstensi awk GNU, jadi jika Anda menggunakan Mac OS, misalnya, gunakan gawk alih-alih awk.
Joe Shaw
Itu tidak berhenti membuat saya takjub betapa kuatnya bash. Saya tidak berpikir akan ada solusi yang mudah untuk tugas yang rumit ini.
recluze
2
Untuk pengguna OS X, Anda harus menginstal gawkdan menggunakannya awk. Jika Anda memiliki MacPorts:sudo port install gawk
Matt Williamson
5
@teh_senaus Sejauh yang saya tahu, awk's strftime()tidak memiliki presisi milidetik. Namun Anda dapat menggunakan dateperintah. Pada dasarnya Anda hanya memotong nanodetik menjadi tiga karakter. Ini akan terlihat seperti ini: COMMAND | while read -r line; do echo "$(date '+%Y-%m-%d %T.%3N') $line"; done. Perhatikan bahwa Anda dapat menyingkat% H:% M:% S dengan% T. Jika Anda masih ingin menggunakan awkuntuk beberapa alasan, Anda dapat melakukan hal berikut:COMMAND | awk '{ "date +%Y-%m-%d\\ %T.%3N" | getline timestamp; print timestamp, $0; fflush(); }'
Enam
190

tsdari moreutils akan menambahkan cap waktu ke setiap baris input yang Anda berikan. Anda dapat memformatnya menggunakan strftime juga.

$ echo 'foo bar baz' | ts
Mar 21 18:07:28 foo bar baz
$ echo 'blah blah blah' | ts '%F %T'
2012-03-21 18:07:30 blah blah blah
$ 

Untuk menginstalnya:

sudo apt-get install moreutils
Mark McKinstry
sumber
2
untuk menangkap dan mencap stderr secara bersamaan: bad command 2>&1 | tsmenghasilkanJan 29 19:58:31 -bash: bad: command not found
rymo
Saya ingin menggunakan format waktu 2014 Mar 21 18:07:28. Bagaimana saya mendapatkannya?
becko
@becko - sesuai dengan halaman buku panduan , berikan strftimestring format sebagai argumen :, [.. command ..] | ts '%Y %b %d %T'misalnya.
Toby Speight
2
Untuk OS X brew install moreutils,. Untuk menghasilkan UTC, Anda dapat mengatur TZ secara lokal, misalnyals -l |TZ=UTC ts '%Y-%m-%dT%H:%M:%SZ'
karmakaze
1
@Dorian ini karena ruby ​​sedang buffering output; jika Anda menyiram penyangga sebelum tidur berfungsi dengan baik:ruby -e "puts 1; STDOUT.flush; sleep 1; puts 2; STDOUT.flush; sleep 2; puts 3" | ts '%F %T'
umläute
45

membubuhi keterangan , tersedia melalui tautan itu atau seperti annotate-outputdalam devscriptspaket Debian .

$ echo -e "a\nb\nc" > lines
$ annotate-output cat lines
17:00:47 I: Started cat lines
17:00:47 O: a
17:00:47 O: b
17:00:47 O: c
17:00:47 I: Finished with exitcode 0
Ted Percival
sumber
1
Ini adalah solusi yang bagus, tetapi tidak akan bekerja dengan output piped atau output subshelled. Itu hanya akan memformat ulang output untuk program atau skrip yang Anda berikan sebagai argumen.
slm
1
annotate-output bekerja dengan baik dan mudah digunakan, tetapi BENAR-BENAR lambat.
Paul Brannan
31

Menyaring jawaban yang diberikan ke yang paling sederhana:

unbuffer $COMMAND | ts

Di Ubuntu, mereka datang dari paket-paket harapan-dev dan moreutils.

sudo apt-get install expect-dev moreutils
Willem
sumber
1
Ini expect, bukan expect-dev, tapi tarikan yang terakhir di bekas sehingga karya ini. (Ubuntu 14.10, saya ingin tahu apakah biner dipindahkan ke paket yang berbeda di beberapa titik.)
Marius Gedminas
Untuk Ubuntu 14.04 LTS, masih diexpect-dev
Willem
2
Jika Anda ingin mendapatkan waktu yang berlalu dengan akurasi milidetik, gunakan unbuffer $COMMAND | ts -s %.s( -sjika untuk waktu yang berlalu dan %.suntuk waktu dalam detik dengan bagian fraksional)
Greg Witczak
24

Bagaimana dengan ini?

cat somefile.txt | perl -pne 'print scalar(localtime()), " ";'

Menilai dari keinginan Anda untuk mendapatkan cap waktu hidup, mungkin Anda ingin melakukan pembaruan langsung pada file log atau sesuatu? Mungkin

tail -f /path/to/log | perl -pne 'print scalar(localtime()), " ";' > /path/to/log-with-timestamps
jj33
sumber
4
Contoh perl ini sangat berguna bagi saya karena saya bekerja sebagai pengguna pada penyebaran ubuntu, yang tampaknya menggunakan 'mawk' alih-alih 'gawk' - dan tidak memiliki fungsi strftime. Terima kasih!
opello
4
Memberi +1 pada ini menjadi berguna pada instalasi Ubuntu default. Saya digunakan tail -f /path/to/log | perl -pne 'use POSIX qw(strftime); print strftime "%Y-%m-%dT%H:%M:%SZ ", gmtime();'untuk mendapatkan tanggal gaya ISO8601 / RFC3339 bukan Wacko yang menghasilkan versi sederhana.
natevw
1
Jika Anda ingin dapat melihat keluaran log timestamped saat tiba, mungkin akan membantu untuk menambahkan BEGIN { $|++; }sebelum pernyataan cetak untuk membuat Perl flush output dengan setiap baris.
2
Anda dapat mempersingkat skrip perl lebih lanjut menjadi adil ls | perl -pne 'print localtime." "'. Konversi skalar terjadi karena penggabungan string.
rahul
Mengapa Anda menggunakan keduanya -pdan -nopsi? Sepertinya -nberlebihan jika Anda telah menentukan -p.
Davor Cubranic
19

Hanya akan membuang ini di luar sana: ada sepasang utilitas di daemontools yang disebut tai64n dan tai64nlocal yang dibuat untuk stempel waktu berurutan untuk mencatat pesan.

Contoh:

cat file | tai64n | tai64nlocal
chazomaticus
sumber
18

Jawaban Kieron adalah yang terbaik sejauh ini. Jika Anda memiliki masalah karena program pertama sedang melakukan buffering, Anda dapat menggunakan program unbuffer:

unbuffer <command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; }'

Ini diinstal secara default pada sebagian besar sistem linux. Jika Anda perlu membuatnya sendiri, itu adalah bagian dari paket yang diharapkan

http://expect.nist.gov

Mark Harrison
sumber
Perhatikan bahwa saya telah menemukan bahwa 'unbuffer' terkadang menyembunyikan hasil pengembalian dari program yang disebut - jadi itu tidak berguna dalam Jenkins atau hal-hal lain yang bergantung pada status pengembalian
oskarpearson
10

Gunakan perintah baca (1) untuk membaca satu baris pada satu waktu dari input standar, kemudian output baris yang diawali dengan tanggal dalam format yang Anda pilih menggunakan tanggal (1).

$ cat timestamp
#!/bin/sh
while read line
do
  echo `date` $line
done
$ cat somefile.txt | ./timestamp
Caerwyn
sumber
2
Agak kelas berat karena melakukan proses baru per baris, tetapi berhasil!
Joe Shaw
3

Saya bukan orang Unix, tapi saya pikir Anda bisa menggunakannya

gawk '{print strftime("%d/%m/%y",systime()) $0 }' < somefile.txt
PabloG
sumber
3
#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>) {
        ($s,$ms) = gettimeofday();
        print $s . "." . $ms . " " . $_;
}'

sumber
2

Inilah solusi awk saya (dari sistem Windows / XP dengan MKS Tools diinstal di direktori C: \ bin). Ini dirancang untuk menambahkan tanggal dan waktu saat ini dalam bentuk mm / hh: mm ke awal setiap baris setelah mengambil stempel waktu dari sistem saat setiap baris dibaca. Anda tentu saja dapat menggunakan pola BEGIN untuk mengambil stempel waktu satu kali dan menambahkan stempel waktu itu ke setiap record (semuanya sama). Saya melakukan ini untuk menandai file log yang sedang dibuat untuk stdout dengan stempel waktu pada saat pesan log dihasilkan.

/"pattern"/ "C\:\\\\bin\\\\date '+%m/%d %R'" | getline timestamp;
print timestamp, $0;

di mana "pola" adalah string atau regex (tanpa tanda kutip) untuk dicocokkan di baris input, dan opsional jika Anda ingin mencocokkan semua baris input.

Ini harus bekerja pada sistem Linux / UNIX juga, cukup singkirkan C \: \\ bin \\ meninggalkan garis

             "date '+%m/%d %R'" | getline timestamp;

Ini, tentu saja, mengasumsikan bahwa perintah "date" membawa Anda ke perintah tampilan / set tanggal Linux / UNIX standar tanpa informasi jalur spesifik (yaitu, variabel PATH lingkungan Anda dikonfigurasikan dengan benar).

ElGringoGeek
sumber
Pada OS XI diperlukan spasi sebelum perintah tanggal. Sayangnya sepertinya tidak mengevaluasi kembali perintah untuk setiap baris. Seperti yang disarankan orang lain, saya mencoba menambahkan cap waktu ke tail -f.
Mark Bennett
2

Mencampur beberapa jawaban di atas dari natevw dan Frank Ch. Eigler.

Ini memiliki milidetik, berkinerja lebih baik daripada memanggil dateperintah eksternal setiap kali dan perl dapat ditemukan di sebagian besar server.

tail -f log | perl -pne '
  use Time::HiRes (gettimeofday);
  use POSIX qw(strftime);
  ($s,$ms) = gettimeofday();
  print strftime "%Y-%m-%dT%H:%M:%S+$ms ", gmtime($s);
  '

Versi alternatif dengan flush dan baca dalam satu lingkaran:

tail -f log | perl -pne '
  use Time::HiRes (gettimeofday); use POSIX qw(strftime);
  $|=1;
  while(<>) {
    ($s,$ms) = gettimeofday();
    print strftime "%Y-%m-%dT%H:%M:%S+$ms $_", gmtime($s);
  }'
Keymon
sumber
1
Dua kekurangan dengan yang di atas: 1. $ ms tidak selaras dan nol-empuk. 2. Setiap% kode dalam log akan hancur. Jadi, alih-alih jalur cetak, saya menggunakan: printf "%s+%06d %s", (strftime "%Y-%m-%dT%H:%M:%S", gmtime($s)), $ms, $_;
Simon Pickup
2
$ cat somefile.txt | sed "s/^/`date`/"

Anda dapat melakukan ini (dengan gnu / sed ):

$ some-command | sed "x;s/.*/date +%T/e;G;s/\n/ /g"

contoh:

$ { echo 'line1'; sleep 2; echo 'line2'; } | sed "x;s/.*/date +%T/e;G;s/\n/ /g"
20:24:22 line1
20:24:24 line2

tentu saja, Anda dapat menggunakan opsi lain dari tanggal program . ganti saja date +%Tdengan yang Anda butuhkan.

aleksandr barakin
sumber
1

Jawaban caerwyn dapat dijalankan sebagai subrutin, yang akan mencegah proses baru per baris:

timestamp(){
   while read line
      do
         echo `date` $line
      done
}

echo testing 123 |timestamp
crumplecrap
sumber
3
bagaimana hal itu mencegah datemelahirkan di setiap baris?
Gyom
@Gyom Itu tidak mencegah tanggal untuk menelurkan setiap baris. Ini mencegah shell dari memunculkan setiap baris. Saya akan menambahkan bahwa dalam bash Anda tidak dapat melakukan sesuatu seperti: timestamp() { while read line; do echo "$(date) $line"; done; }; export -f timestampdalam skrip yang Anda sumber.
smbear
Memanggil tanggal sebagai subproses masih berarti menelurkannya untuk setiap baris, namun Anda meluncurkan skrip di tempat pertama.
Peter Hansen
3
Untuk mencegah perintah spawning dateuntuk setiap baris, coba gunakan bash printfbuiltin dengan format T% (Datepec) . Jadi mungkin terlihat seperti timestamp() { while IFS= read -r line; do printf "%(%F %T)T %s\n" -1 "$line"; done; }. Shell bawaan tidak akan muncul. Format Datespec seperti format strftime(3)ie date. Ini membutuhkan bash, tidak yakin karena versi mana yang mendukungnya printf.
Mindaugas Kubilius
1

Penafian : solusi yang saya usulkan bukanlah utilitas bawaan Unix.

Saya menghadapi masalah yang sama beberapa hari yang lalu. Saya tidak menyukai sintaks dan keterbatasan solusi di atas, jadi saya segera membuat program di Go untuk melakukan pekerjaan untuk saya.

Anda dapat memeriksa alat di sini: preftime

Ada executable prebuilt untuk Linux, MacOS, dan Windows di bagian Rilis proyek GitHub.

Alat ini menangani jalur keluaran yang tidak lengkap dan memiliki (dari sudut pandang saya) sintaksis yang lebih ringkas.

<command> | preftime

Ini tidak ideal, tetapi saya pikir saya akan membagikannya jika itu membantu seseorang.

Momchil Atanasov
sumber
0

melakukannya dengan datedan trdan xargsdi OSX:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S\" | tr \"\n\" \" \"; echo \"{}\"'"
<command> | predate

jika Anda ingin milidetik:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"

tetapi perhatikan bahwa pada OSX, date tidak memberi Anda opsi% N, jadi Anda harus menginstal gdate ( brew install coreutils) dan akhirnya sampai pada ini:

alias predate="xargs -I{} sh -c 'gdate +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"
orion elenzil
sumber
-1

Jika nilai yang Anda prabayar sama pada setiap baris, jalankan emacs dengan file tersebut, lalu:

Ctrl + <space>

di awal file (untuk menandai tempat itu), kemudian gulir ke bawah ke awal baris terakhir (Alt +> akan pergi ke akhir file ... yang mungkin akan melibatkan tombol Shift juga, lalu Ctrl + a untuk menuju ke awal baris itu) dan:

Ctrl + x r t

Yang merupakan perintah untuk menyisipkan pada persegi panjang yang baru saja Anda tentukan (persegi panjang dengan lebar 0).

2008-8-21 18:45 <enter>

Atau apa pun yang ingin Anda tambahkan terlebih dahulu ... maka Anda akan melihat teks tersebut ditambahkan ke setiap baris dalam persegi panjang 0 lebar.

UPDATE: Saya baru sadar Anda tidak ingin tanggal SAMA, jadi ini tidak akan berhasil ... meskipun Anda mungkin dapat melakukan ini di emacs dengan makro kustom yang sedikit lebih rumit, tapi tetap saja, penyuntingan persegi panjang semacam ini adalah cukup baik untuk mengetahui tentang ...

Mike Stone
sumber