Попередження позначки часу до кожного рядка виводу з команди


182

Я хочу додати часову позначку до кожного рядка виводу з команди. Наприклад:

foo
bar
baz

став би

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

... де час з префіксом - це час, коли друкується рядок. Як я можу цього досягти?


Відповіді:


274

moreutils включає, tsщо робить це досить непогано:

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

Це також виключає необхідність циклу, кожен рядок виводу буде надітий часовою позначкою.

$ 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

Ви хочете знати, коли цей сервер повернувся, ви перезапустили? Просто біжіть ping | ts, проблема вирішена: D.


8
Як я про це не знав?!?!?! Це доповнює хвіст -f дивовижно! tail -f /tmp/script.results.txt | ts
Бруно Броноський

А як щодо cygwin? Чи є щось подібне? Не здається, що ще більше утисків Джої.
CrazyPenguin

3
якщо я не маю команди ts, що мені використовувати?
ekassis

1
Якщо це не працює, спробуйте перенаправити stderr на stdout, наприкладssh -v 127.0.0.1 2>&1 | ts
jchook

3
Я думаю, що вказати параметр -sкорисно. Як це відображає час виконання команди. Мені особисто подобається використовувати і одночасно, tsі ts -sодночасно. Виглядає приблизно так: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Це створює попередження таких ліній журналу:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone

100

По-перше, якщо ви очікуєте, що ці часові позначки насправді представляють подію, майте на увазі, що оскільки багато програм виконують буферизацію ліній (деякі більш агресивно, ніж інші), важливо думати про це якнайшвидше часу, який матиме початковий рядок було надруковано, а не мітка часу дії, що відбувається.

Ви також можете перевірити, що у вашої команди вже немає вбудованої функції, присвяченої цьому. Наприклад, ping -Dіснує в деяких pingверсіях і друкує час після епохи Unix перед кожним рядком. Якщо ваша команда не містить власного методу, однак є кілька методів та інструментів, серед яких можна застосувати:

Оболонка POSIX

Майте на увазі, що оскільки багато оболонок зберігають свої рядки внутрішньо як cstrings, якщо вхід містить нульовий символ ( \0), це може призвести до того, що рядок закінчується передчасно.

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'

Пітон

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

Рубін

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

3
Одна з проблем тут полягає в тому, що багато програм включають ще більше вихідну буферизацію, коли їх stdout - це труба, а не термінал.
cjm

3
@cjm - Правда. Деякі буферизації вихідних даних можна полегшити за допомогою stdbuf -o 0, але якщо програма вручну обробляє вихідну буферизацію, це не допоможе (якщо немає можливості відключити / зменшити розмір вихідного буфера).
Кріс Даун

2
Для python ви можете відключити буферизацію рядків за допомогоюpython -u
ibizaman

@Bwmat No. ... for x in sys.stdinповторює лінії, не попередньо завантажуючи їх у пам'ять.
Кріс Даун

Зробіть це, і ви отримаєте буферизацію ... для a in 1 1 1 1 1; спати 1; відлуння; зроблено | python -c 'import sys, time; sys.stdout.write ("". приєднатися (("" .join ((time.strearch ("[% Y-% m-% d% H:% M:% S] ", time.gmtime ()), рядок)) для рядка в sys.stdin))) '
ChuckCottrill

41

Для лінійного вимірювання дельти спробуйте гномон .

Це утиліта командного рядка, схожа на ts moreutils, для подання інформації про часові позначки до стандартного виводу іншої команди. Корисно для тривалих процесів, де ви хочете отримати історичний запис того, що триває так довго.

Передача нічого до gnomon додасть часову позначку до кожного рядка, вказуючи, наскільки довгий цей рядок був останнім рядком у буфері - тобто, скільки часу знадобилося, щоб з'явився наступний рядок. За замовчуванням gnomon відображатиме секунди, що минули між кожним рядком, але це можна настроювати.

gnomon demo


Виглядає як чудова альтернатива tsпри використанні живих процесів. Хоча tsкраще підходить для неінтерактивних процесів.
BrainStone

7

Повідомлення Райана дає цікаву ідею, однак воно провалюється в кількох аспектах. Під час тестування tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 я помітив, що часова марка залишається такою ж, навіть якщо stdoutз’являється пізніше з різницею в секундах один від одного. Розглянемо цей вихід:

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

Моє запропоноване рішення є подібним, однак забезпечує належне відмітку часу і використовує дещо більш портативний printf, ніжecho

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

Чому bash -c '...' bash? Тому що через -cопцію, перший аргумент присвоюється $0і не відображатиметься у висновку. Зверніться до сторінки керівництва вашої оболонки для правильного опису-c

Тестування цього рішення tail -f /var/log/syslogта (як ви, напевно, могли здогадатися), відключення та підключення до мого Wi-Fi, показало належну мітку часу, яку надають dateі syslogповідомлення, і повідомлення

Баш може бути замінений будь-якою оболонкою, що нагадує бурш, може бути виконана з будь-якою kshабо dash, принаймні, з тими, що мають -cможливість.

Потенційні питання:

Рішення вимагає наявності xargs, яке доступне на POSIX-сумісних системах, тому більшість Unix-подібних систем слід охоплювати. Очевидно, не буде працювати, якщо ваша система не сумісна з POSIX або її немаєGNU findutils


5

Я хотів би прокоментувати вище, але не можу, репутаційно. У будь-якому випадку, зразок Perl, описаний вище, можна розблокувати наступним чином:

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

Перший '$ |' розблокує STDOUT. Другий встановлює stderr як поточний вихідний канал за замовчуванням і розвантажує його. Оскільки select повертає початкові параметри $ |, загортаючи виділення всередині вибору, ми також скидаємо $ | за замовчуванням, STDOUT.

І так, ви можете вирізати пасту так, як є. Я багаторазово виклав це для розбірливості.

І якщо ви дійсно хочете отримати точну інформацію (і у вас встановлений час :: наймає ):

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
Працює як шарм, без необхідності встановлювати будь-які нестандартні пакети.
Джей Тейлор

2

Більшість відповідей пропонують використовувати date, але це досить повільно. Якщо ваша версія bash більша за 4.2.0, краще printfзамість цього використовувати , це bash вбудований. Якщо вам потрібно підтримувати застарілі версії bash, ви можете створити logфункцію, що залежить від версії 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

Дивіться різниці швидкостей:

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: замість $(date +"${TIMESTAMP_FORMAT}")цього краще використовувати $(exec date +"${TIMESTAMP_FORMAT}")або навіть $(exec -c date +"${TIMESTAMP_FORMAT}")занадто прискорене виконання.


0

Це можна зробити за допомогою dateта xargs:

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

Пояснення:

xargs -L 1каже xargs виконувати команду, що йде, для кожного 1 рядка введення, і він передає в перший рядок так, як це робиться. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1в основному перегукується дата з аргументом введення в кінці


2
Рішення близьке, але не позначає належним чином часові позначки, коли мова йде про вихід, розділений довгими періодами часу. Крім того, ви використовуєте backticks і не цитували $1. Це не гарний стиль. Завжди цитуйте змінні. Крім того, ви використовуєте echo, що не є портативним. Це добре, але може не працювати належним чином у деяких системах.
Сергій Колодяжний

Після тестування цього, здається, ви абсолютно праві ... чи знаєте ви будь-який спосіб зробити dateпереоцінку кожного рядка, чи це майже безперспективно?
Райан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.