Chuẩn bị dấu thời gian cho từng dòng đầu ra từ một lệnh


182

Tôi muốn thêm dấu thời gian cho từng dòng đầu ra từ một lệnh. Ví dụ:

foo
bar
baz

sẽ trở thành

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

... Trong đó thời gian được tiền tố là thời gian mà dòng được in. Làm thế nào tôi có thể đạt được điều này?


Câu trả lời:


274

moreutils bao gồm tscái này khá độc đáo:

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

Nó cũng loại bỏ sự cần thiết của một vòng lặp, mỗi dòng đầu ra sẽ có dấu thời gian đặt trên nó.

$ 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

Bạn muốn biết khi máy chủ đó hoạt động trở lại, bạn đã khởi động lại? Chỉ cần chạy ping | ts, vấn đề được giải quyết: D.


8
Làm thế nào tôi không biết về điều này?!?!?! Điều này bổ sung đuôi -f tuyệt vời! tail -f /tmp/script.results.txt | ts
Bruno Bronosky

Còn trong Cygwin thì sao? Có một cái gì đó tương tự? Nó không xuất hiện thêm Jout'sututils ở đó.
CrazyPenguin

3
Nếu tôi không có lệnh ts, tôi nên sử dụng cái gì?
ek Khung

1
Nếu nó không hoạt động, hãy thử chuyển hướng stderr sang stdout, ví dụ:ssh -v 127.0.0.1 2>&1 | ts
jchook 20/03/2017

3
Tôi nghĩ rằng chỉ ra các tham số -slà hữu ích. Như hiển thị thời gian chạy của lệnh. Cá nhân tôi thích sử dụng cả hai tsts -scùng một lúc. Trông giống như thế này : command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Điều này chuẩn bị các dòng nhật ký như thế này:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone

100

Đầu tiên, nếu bạn đang mong đợi các dấu thời gian này thực sự đại diện cho một sự kiện, hãy nhớ rằng vì nhiều chương trình thực hiện đệm dòng (một số tích cực hơn các chương trình khác), điều quan trọng là phải nghĩ về điều này gần với thời gian mà dòng ban đầu sẽ có đã được in chứ không phải là dấu thời gian của một hành động đang diễn ra.

Bạn cũng có thể muốn kiểm tra xem lệnh của bạn chưa có tính năng sẵn có để thực hiện việc này. Ví dụ, ping -Dtồn tại trong một số pingphiên bản và in thời gian kể từ thời kỳ Unix trước mỗi dòng. Tuy nhiên, nếu lệnh của bạn không chứa phương thức riêng, có một số phương thức và công cụ có thể được sử dụng, trong số các phương thức khác:

Vỏ POSIX

Hãy nhớ rằng vì nhiều shell lưu trữ các chuỗi bên trong của chúng dưới dạng các chuỗi, nếu đầu vào chứa ký tự null ( \0), nó có thể khiến dòng kết thúc sớm.

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'

Con trăn

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 )))'

Hồng ngọc

command | ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'

3
Một vấn đề ở đây là nhiều chương trình bật bộ đệm đầu ra thậm chí nhiều hơn khi thiết bị xuất chuẩn của chúng là một đường ống thay vì thiết bị đầu cuối.
cjm

3
@cjm - Đúng. Một số bộ đệm đầu ra có thể được giảm bớt bằng cách sử dụng stdbuf -o 0, nhưng nếu chương trình xử lý thủ công bộ đệm đầu ra, nó sẽ không giúp ích (trừ khi có tùy chọn để vô hiệu hóa / giảm kích thước của bộ đệm đầu ra).
Chris Down

2
Đối với python, bạn có thể vô hiệu hóa bộ đệm dòng vớipython -u
ibizaman

@Bwmat Số ... for x in sys.stdinlặp lại qua các dòng mà không đệm tất cả chúng vào bộ nhớ đầu tiên.
Chris Xuống

Làm điều này và bạn có được bộ đệm ... cho 1 trong 1 1 1 1; ngủ 1 giấc; tiếng vang; xong | python -c 'nhập sys, time; sys.stdout.write ("". tham gia (("" .join ((time.strftime ("[% Y-% m-% d% H:% M:% S] ", Time.gmtime ()), dòng)) cho dòng trong sys.stdin))) '
ChuckCottrill

41

Để đo delta theo từng dòng, hãy thử gnomon .

Nó là một tiện ích dòng lệnh, giống như ts của moreutils, để thêm thông tin dấu thời gian vào đầu ra tiêu chuẩn của một lệnh khác. Hữu ích cho các quy trình dài mà bạn muốn ghi lại lịch sử về những gì mất quá nhiều thời gian.

Đường ống bất cứ điều gì đến gnomon sẽ đưa ra dấu thời gian cho mỗi dòng, cho biết dòng đó là dòng cuối cùng trong bộ đệm bao lâu - nghĩa là mất bao lâu để dòng tiếp theo xuất hiện. Theo mặc định, gnomon sẽ hiển thị các giây trôi qua giữa mỗi dòng, nhưng đó là cấu hình.

bản demo của Gnomon


Trông giống như một sự thay thế tuyệt vời tskhi sử dụng các quy trình trực tiếp. Trong khi tsphù hợp hơn cho các quá trình không tương tác.
BrainStone

7

Bài viết của Ryan cung cấp một ý tưởng thú vị, tuy nhiên, nó thất bại trong một số vấn đề. Trong khi thử nghiệm với tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 , tôi nhận thấy rằng dấu thời gian vẫn giữ nguyên ngay cả khi stdoutđến sau với sự khác biệt trong vài giây. Xem xét đầu ra này:

[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.

Giải pháp đề xuất của tôi là tương tự, tuy nhiên cung cấp thời gian thích hợp và sử dụng phần nào dễ mang theo printfhơn làecho

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

Tại sao bash -c '...' bash? Bởi vì -ctùy chọn, đối số đầu tiên được gán cho $0và sẽ không hiển thị trong đầu ra. Tham khảo trang hướng dẫn sử dụng shell của bạn để biết mô tả thích hợp về-c

Thử nghiệm giải pháp này với tail -f /var/log/syslogvà (như bạn có thể đoán) việc ngắt kết nối và kết nối lại với wifi của tôi, đã cho thấy thời gian thích hợp được cung cấp bởi cả hai datesyslogtin nhắn

Bash có thể được thay thế bằng bất kỳ vỏ giống như bourne, có thể được thực hiện bằng một trong hai kshhoặc dash, ít nhất là những cái có -ctùy chọn.

Các vấn đề tiềm ẩn:

Giải pháp yêu cầu phải có xargs, có sẵn trên các hệ thống tuân thủ POSIX, vì vậy hầu hết các hệ thống giống Unix nên được bảo hiểm. Rõ ràng là sẽ không hoạt động nếu hệ thống của bạn không tuân thủ POSIX hoặc không cóGNU findutils


5

Tôi muốn bình luận ở trên nhưng tôi không thể, nổi tiếng. Dù sao, mẫu Perl ở trên có thể được bỏ qua như sau:

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

'$ |' Đầu tiên unbuffers STDOUT. Cái thứ hai đặt stderr làm kênh đầu ra mặc định hiện tại và giải nén nó. Vì select trả về cài đặt gốc của $ |, bằng cách gói lựa chọn bên trong một lựa chọn, chúng tôi cũng đặt lại $ | đến mặc định của nó, STDOUT.

Và vâng, bạn có thể cắt 'n dán như vậy. Tôi đa lót nó cho dễ đọc.

Và nếu bạn thực sự muốn nhận chính xác (và bạn đã cài đặt Time :: Hires ):

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)'

1
Hoạt động như một bùa mê, mà không phải cài đặt bất kỳ gói không chuẩn nào.
Jay Taylor

2

Hầu hết các câu trả lời đề nghị sử dụng date, nhưng nó đủ chậm. Nếu phiên bản bash của bạn lớn hơn 4.2.0 thì tốt hơn nên sử dụng printf, đó là một bash dựng sẵn. Nếu bạn cần hỗ trợ các phiên bản bash cũ, bạn có thể tạo logchức năng tùy thuộc vào phiên bản 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

Xem sự khác biệt về tốc độ:

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

CẬP NHẬT: thay vì $(date +"${TIMESTAMP_FORMAT}")sử dụng tốt hơn $(exec date +"${TIMESTAMP_FORMAT}")hoặc thậm chí $(exec -c date +"${TIMESTAMP_FORMAT}")quá tốc độ thực hiện.


0

Bạn có thể làm điều này với datexargs:

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

Giải trình:

xargs -L 1yêu cầu xargs chạy lệnh tiếp tục cho mỗi 1 dòng đầu vào và nó chuyển qua dòng đầu tiên khi nó làm như vậy. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1về cơ bản lặp lại ngày với đối số đầu vào ở cuối của nó


2
Giải pháp gần đúng, nhưng không đúng thời gian khi đầu ra được phân tách bằng khoảng thời gian dài. Ngoài ra, bạn đang sử dụng backticks và chưa được trích dẫn $1. Đó không phải là phong cách tốt. Luôn trích dẫn các biến. Ngoài ra, bạn đang sử dụng echo, không phải là di động. Không sao, nhưng có thể không hoạt động đúng trên một số hệ thống.
Sergiy Kolodyazhnyy

Sau khi thử nghiệm điều này, có vẻ như bạn hoàn toàn đúng ... bạn có biết cách nào để dateđánh giá lại mọi dòng, hay nó là vô vọng?
Ryan
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.