Sertakan stempel waktu untuk setiap baris output dari sebuah perintah

182

Saya ingin menambahkan cap waktu ke setiap baris output dari sebuah perintah. Sebagai contoh:

foo
bar
baz

akan menjadi

[2011-12-13 12:20:38] foo
[2011-12-13 12:21:32] bar
[2011-12-13 12:22:20] baz

... di mana waktu yang diawali adalah waktu di mana garis itu dicetak. Bagaimana saya bisa mencapai ini?


sumber

Jawaban:

274

moreutils termasuk tsyang melakukan ini dengan cukup baik:

command | ts '[%Y-%m-%d %H:%M:%S]'

Ini menghilangkan kebutuhan untuk loop juga, setiap baris output akan memiliki timestamp yang diletakkan di atasnya.

$ echo -e "foo\nbar\nbaz" | ts '[%Y-%m-%d %H:%M:%S]'
[2011-12-13 22:07:03] foo
[2011-12-13 22:07:03] bar
[2011-12-13 22:07:03] baz

Anda ingin tahu kapan server itu muncul kembali Anda me-restart? Jalankan saja ping | ts, masalah terpecahkan: D.

Mark McKinstry
sumber
8
Bagaimana saya tidak tahu tentang ini?!?!?! Ini melengkapi ekor-f luar biasa! tail -f /tmp/script.results.txt | ts
Bruno Bronosky
Bagaimana dengan di cygwin? Apakah ada yang serupa? Tidak terlihat lagi Joey'sutils ada di sana.
CrazyPenguin
3
jika saya tidak memiliki perintah ts, apa yang harus saya gunakan?
ekassis
1
Jika tidak berhasil, coba arahkan ulang stderr ke stdout misalnyassh -v 127.0.0.1 2>&1 | ts
jchook
3
Saya pikir menunjukkan parameter -sberguna. Ketika itu menampilkan runtime dari perintah. Saya pribadi suka menggunakan keduanya tsdan ts -spada saat yang sama. Terlihat seperti ini: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Ini menambahkan baris log seperti ini:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone
100

Pertama, jika Anda mengharapkan cap waktu ini benar-benar mewakili suatu peristiwa, ingatlah bahwa karena banyak program melakukan buffering garis (beberapa lebih agresif daripada yang lain), penting untuk menganggap ini sedekat waktu dengan garis asli akan memiliki telah dicetak daripada cap waktu tindakan yang sedang berlangsung.

Anda mungkin juga ingin memeriksa apakah perintah Anda belum memiliki fitur bawaan yang didedikasikan untuk melakukan ini. Sebagai contoh, ping -Dada dalam beberapa pingversi, dan mencetak waktu sejak zaman Unix sebelum setiap baris. Namun, jika perintah Anda tidak mengandung metode sendiri, ada beberapa metode dan alat yang dapat digunakan, antara lain:

Shell POSIX

Ingatlah bahwa karena banyak shell menyimpan string mereka secara internal sebagai cstring, jika input berisi karakter null ( \0), ini dapat menyebabkan garis berakhir sebelum waktunya.

command | while IFS= read -r line; do printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$line"; done

GNU awk

command | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }'

Perl

command | perl -pe 'use POSIX strftime; print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'

Python

command | python -c 'import sys,time;sys.stdout.write("".join(( " ".join((time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime()), line)) for line in sys.stdin )))'

Rubi

command | ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'
Chris Down
sumber
3
Satu masalah di sini adalah bahwa banyak program menghidupkan buffering keluaran bahkan lebih ketika stdout mereka adalah pipa bukan terminal.
cjm
3
@ cjm - Benar. Beberapa buffering output dapat dikurangi dengan menggunakan stdbuf -o 0, tetapi jika program secara manual menangani buffering outputnya, itu tidak akan membantu (kecuali ada opsi untuk menonaktifkan / mengurangi ukuran buffer output).
Chris Down
2
Untuk python, Anda dapat menonaktifkan line buffering denganpython -u
ibizaman
@Bwmat No. ... for x in sys.stdiniterates melalui baris tanpa buffering mereka semua ke dalam memori terlebih dahulu.
Chris Down
Lakukan ini dan Anda mendapatkan buffering ... untuk in 1 1 1 1 1; tidurlah 1; gema; selesai | python -c 'sys import, time; sys.stdout.write ("". join (("" .join ((time.strftime ("[% Y-% m-% d% H:% M:% S] ", time.gmtime ()), baris)) untuk baris di sys.stdin))) '
ChuckCottrill
41

Untuk pengukuran delta baris demi baris, coba gnomon .

Ini adalah utilitas baris perintah, sedikit seperti ts lebihututils, untuk menambahkan informasi timestamp ke output standar dari perintah lain. Berguna untuk proses jangka panjang di mana Anda ingin catatan sejarah tentang apa yang memakan waktu begitu lama.

Memipiskan apa pun ke gnomon akan menambahkan cap waktu ke setiap baris, yang menunjukkan berapa lama garis itu adalah baris terakhir di buffer - yaitu, berapa lama garis berikutnya muncul. Secara default, gnomon akan menampilkan detik yang berlalu di antara setiap baris, tetapi itu dapat dikonfigurasi.

gnomon demo

Janus Troelsen
sumber
Sepertinya alternatif yang bagus untuk tssaat menggunakan proses langsung. Sementara tslebih cocok untuk proses non-interaktif.
BrainStone
7

Posting Ryan memang memberikan ide yang menarik, namun gagal dalam beberapa hal. Saat menguji dengan tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 , saya perhatikan bahwa stempel waktu tetap sama bahkan jika stdoutdatang kemudian dengan perbedaan dalam detik terpisah. Pertimbangkan output ini:

[2016-07-14 01:44:25] Jul 14 01:44:32 eagle dhclient[16091]: DHCPREQUEST of 192.168.0.78 on wlan7 to 255.255.255.255 port 67 (xid=0x411b8c21)
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: Joining mDNS multicast group on interface wlan7.IPv6 with address fe80::d253:49ff:fe3d:53fd.
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: New relevant interface wlan7.IPv6 for mDNS.

Solusi yang saya usulkan serupa, namun memberikan cap waktu yang tepat dan menggunakan yang agak lebih portabel printfdaripadaecho

| xargs -L 1 bash  -c 'printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" ' bash

Mengapa bash -c '...' bash? Karena karena -copsi, argumen pertama ditugaskan $0dan tidak akan muncul di output. Lihat halaman buku panduan shell Anda untuk deskripsi yang tepat-c

Menguji solusi ini dengan tail -f /var/log/syslogdan (karena Anda mungkin bisa menebak) memutuskan dan menghubungkan kembali ke wifi saya, telah menunjukkan cap waktu yang tepat yang disediakan oleh keduanya datedan syslogpesan

Bash dapat digantikan oleh shell seperti bourne, dapat dilakukan dengan salah satu , kshatau dashsetidaknya mereka yang memiliki -copsi.

Masalah potensial:

Solusinya memerlukan memiliki xargs, yang tersedia pada sistem yang sesuai dengan POSIX, sehingga sebagian besar sistem seperti Unix harus dicakup. Jelas tidak akan berfungsi jika sistem Anda tidak memenuhi syarat POSIX atau tidakGNU findutils

Sergiy Kolodyazhnyy
sumber
5

Saya lebih suka berkomentar di atas tetapi saya tidak bisa, secara reputasi. Bagaimanapun, sampel Perl di atas dapat di-unbuffered sebagai berikut:

command | perl -pe 'use POSIX strftime; 
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'

'$ |' Pertama unbuffers STDOUT. Yang kedua menetapkan stderr sebagai saluran keluaran default saat ini dan melepaskannya. Karena pilih mengembalikan pengaturan asli $ |, dengan membungkus pilih di dalam pilih, kami juga mengatur ulang $ | ke standarnya, STDOUT.

Dan ya, Anda dapat memotong dan menempelkan apa adanya. Saya multi-baris untuk keterbacaan.

Dan jika Anda benar-benar ingin mendapatkan yang tepat (dan Anda memiliki Waktu :: Karyawan yang diinstal):

command | perl -pe 'use POSIX strftime; use Time::HiRes gettimeofday;
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    ($s,$ms)=gettimeofday();
                    $ms=substr(q(000000) . $ms,-6);
                    print strftime "[%Y-%m-%d %H:%M:%S.$ms]", localtime($s)'
mpersico
sumber
1
Bekerja seperti pesona, tanpa harus menginstal paket non-standar.
Jay Taylor
2

Sebagian besar jawaban menyarankan untuk digunakan date, tetapi cukup lambat. Jika versi bash Anda lebih besar dari 4.2.0 lebih baik digunakan printf, itu bash builtin. Jika Anda perlu mendukung versi bash lawas, Anda dapat membuat logfungsi bergantung pada versi bash:

TIMESTAMP_FORMAT='%Y-%m-%dT%H:%M:%S'
# Bash version in numbers like 4003046, where 4 is major version, 003 is minor, 046 is subminor.
printf -v BV '%d%03d%03d' ${BASH_VERSINFO[0]} ${BASH_VERSINFO[1]} ${BASH_VERSINFO[2]}
if ((BV > 4002000)); then
log() {
    ## Fast (builtin) but sec is min sample for most implementations
    printf "%(${TIMESTAMP_FORMAT})T %5d %s\n" '-1' $$ "$*"  # %b convert escapes, %s print as is
}
else
log() {
    ## Slow (subshell, date) but support nanoseconds and legacy bash versions
    echo "$(date +"${TIMESTAMP_FORMAT}") $$ $*"
}
fi

Lihat perbedaan kecepatan:

user@host:~$time for i in {1..10000}; do printf "%(${TIMESTAMP_FORMAT})T %s\n" '-1' "Some text" >/dev/null; done

real    0m0.410s
user    0m0.272s
sys     0m0.096s
user@host:~$time for i in {1..10000}; do echo "$(date +"${TIMESTAMP_FORMAT}") Some text" >/dev/null; done

real    0m27.377s
user    0m1.404s
sys     0m5.432s

UPD: daripada $(date +"${TIMESTAMP_FORMAT}")lebih baik menggunakan $(exec date +"${TIMESTAMP_FORMAT}")atau bahkan $(exec -c date +"${TIMESTAMP_FORMAT}")mempercepat eksekusi.

Mikhail
sumber
0

Anda dapat melakukan ini dengan datedan xargs:

... | xargs -L 1 echo `date +'[%Y-%m-%d %H:%M:%S]'` $1

Penjelasan:

xargs -L 1memberi tahu xargs untuk menjalankan perintah proses untuk setiap 1 baris input, dan ia meneruskan di baris pertama saat melakukannya. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1pada dasarnya gema tanggal dengan argumen input di akhir itu

Ryan
sumber
2
Solusinya hampir, tetapi tidak cap waktu dengan benar ketika datang ke output dipisahkan oleh periode waktu yang lama. Juga, Anda menggunakan backticks dan belum mengutip $1. Itu bukan gaya yang baik. Selalu kutip variabelnya. Selain itu, Anda menggunakan echo, yang tidak portabel. Tidak apa-apa, tetapi mungkin tidak berfungsi dengan baik pada beberapa sistem.
Sergiy Kolodyazhnyy
Setelah menguji ini, tampaknya Anda benar sekali ... apakah Anda tahu cara untuk datemengevaluasi ulang setiap baris, atau apakah itu cukup sia-sia?
Ryan