Предварительная отметка времени для каждой строки вывода команды

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
Как я не знал об этом?!?!?! Это прекрасно дополняет хвост! 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
Одна из проблем здесь заключается в том, что многие программы включают еще большую буферизацию вывода, когда их стандартный вывод представляет собой канал, а не терминал.
CJM
3
@cjm - правда. Некоторая буферизация вывода может быть уменьшена с помощью stdbuf -o 0, но если программа вручную обрабатывает свою буферизацию вывода, это не поможет (если нет опции отключить / уменьшить размер буфера вывода).
Крис Даун
2
Для python вы можете отключить буферизацию строки с помощьюpython -u
ibizaman
@Bwmat No. ... for x in sys.stdinперебирает строки без предварительной буферизации их всех в памяти.
Крис Даун
Сделайте это, и вы получите буферизацию ... для 1 в 1 1 1 1; спать 1; эхо; сделано | python -c 'import sys, time; sys.stdout.write ("". join (("" .join ((time.strftime ("[% Y-% m-% d% H:% M:% S]) ", time.gmtime ()), line)) для строки в sys.stdin))) '
ChuckCottrill
41

Для построчного измерения дельты попробуйте gnomon .

Это утилита командной строки, немного похожая на ts moreutils, для добавления информации о временной метке к стандартному выводу другой команды. Полезно для длительных процессов, где вы хотите получить исторический отчет о том, что занимает так много времени.

Передача чего-либо в gnomon приведет к добавлению временной метки к каждой строке, указывающей, как долго эта строка была последней строкой в ​​буфере, то есть сколько времени потребовалось для появления следующей строки. По умолчанию, gnomon будет отображать секунды, прошедшие между каждой строкой, но это настраивается.

демо гномон

Янус Троелсен
источник
Выглядит как отличная альтернатива 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сообщениями.

Bash может быть заменен любым Борна типа оболочки, может быть сделано либо с 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 возвращает исходную настройку $ |, оборачивая select внутри select, мы также сбрасываем $ | по умолчанию STDOUT.

И да, вы можете вырезать и вставить как есть. Я разложил это на части для разборчивости.

И если вы действительно хотите получить точные данные (и у вас установлен 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)'
mpersico
источник
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
Решение близко, но не правильно помечает временные метки, когда дело доходит до вывода, разделенного длительными периодами времени. Кроме того, вы используете обратные пометки и не цитируете $1. Это не хороший стиль. Всегда указывайте переменные. Кроме того, вы используете echo, который не является портативным. Это нормально, но может не работать должным образом на некоторых системах.
Сергей Колодяжный
После тестирования кажется, что вы абсолютно правы ... знаете ли вы какой-либо способ сделать dateпереоценку каждой строки, или это в значительной степени безнадежно?
Райан