Ответы:
$ cat input.log | sed -e "s/^/$(date -R) /" >> output.log
Как это работает:
cat
читает вызываемый файл input.log
и просто печатает его в стандартный поток вывода.
Обычно стандартный вывод подключен к терминалу, но этот небольшой скрипт содержит |
так, что оболочка перенаправляет стандартный вывод cat
на стандартный ввод sed
.
sed
читает данные (как cat
производит их), обрабатывает их (согласно сценарию, предоставленному с -e
опцией), а затем печатает их в стандартный вывод. Под сценарием "s/^/$(date -R) /"
подразумевается замена каждого начала строки текстом, сгенерированным date -R
командой (общая конструкция для команды замены:) s/pattern/replace/
.
Затем в соответствии с >>
bash
перенаправляет вывод sed
в файл с именем output.log
( >
означает заменить содержимое файла и >>
означает добавить в конец).
Проблема $(date -R)
оценивается один раз при запуске скрипта, поэтому он вставляет текущую метку времени в начало каждой строки. Текущая временная метка может быть далека от момента, когда было сгенерировано сообщение. Чтобы избежать этого, вы должны обрабатывать сообщения так, как они записаны в файл, а не с помощью задания cron.
Стандартное перенаправление потока, описанное выше, называется pipe . Вы можете перенаправить его не только |
между командами в скрипте, но и через файл FIFO (он же именованный канал ). Одна программа запишет в файл, а другая прочитает данные и получит их при первой отправке.
Выберите пример:
$ mkfifo foo.log.fifo
$ while true; do cat foo.log.fifo | sed -e "s/^/$(date -R) /" >> foo.log; done;
# have to open a second terminal at this point
$ echo "foo" > foo.log.fifo
$ echo "bar" > foo.log.fifo
$ echo "baz" > foo.log.fifo
$ cat foo.log
Tue, 20 Nov 2012 15:32:56 +0400 foo
Tue, 20 Nov 2012 15:33:27 +0400 bar
Tue, 20 Nov 2012 15:33:30 +0400 baz
Как это работает:
mkfifo
создает именованную трубу
while true; do sed ... ; done
запускает бесконечный цикл и на каждой итерации выполняет sed
перенаправление foo.log.fifo
на стандартный ввод; sed
блокирует ожидание входных данных, а затем обрабатывает полученное сообщение и печатает его на стандартный вывод, перенаправленный на foo.log
.
В этот момент вы должны открыть новое окно терминала, потому что цикл занимает текущий терминал.
echo ... > foo.log.fifo
выводит сообщение на его стандартный вывод, перенаправленное в файл fifo, sed
принимает его, обрабатывает и записывает в обычный файл.
Важное замечание - это fifo, так же как и любой другой канал не имеет смысла, если одна из его сторон не связана с каким-либо процессом. Если вы попытаетесь записать в канал, текущий процесс будет заблокирован, пока кто-нибудь не прочитает данные на другой стороне канала. Если вы хотите читать из канала, процесс будет блокироваться, пока кто-нибудь не запишет данные в канал. sed
Цикл в примере выше ничего не делает (спит) , пока вы не сделаете echo
.
Для вашей конкретной ситуации вы просто настраиваете свое приложение для записи сообщений журнала в файл fifo. Если вы не можете настроить его - просто удалите оригинальный файл журнала и создайте файл fifo. Но обратите внимание еще раз: если sed
цикл по какой-то причине прекратится - ваша программа будет заблокирована при попытке открыть write
файл, пока кто-то не выйдет read
из fifo.
Преимущество заключается в том, что текущая временная метка оценивается и прикрепляется к сообщению, когда программа записывает его в файл.
tailf
Чтобы сделать запись в журнал и обработку более независимой, вы можете использовать два обычных файла с tailf
. Приложение запишет сообщение в необработанный файл, а другой процесс прочитает новые строки (выполните асинхронную запись) и обработает данные с записью во второй файл.
Давайте возьмем пример:
# will occupy current shell
$ tailf -n0 bar.raw.log | while read line; do echo "$(date -R) $line" >> bar.log; done;
$ echo "foo" >> bar.raw.log
$ echo "bar" >> bar.raw.log
$ echo "baz" >> bar.raw.log
$ cat bar.log
Wed, 21 Nov 2012 16:15:33 +0400 foo
Wed, 21 Nov 2012 16:15:36 +0400 bar
Wed, 21 Nov 2012 16:15:39 +0400 baz
Как это работает:
Запустите tailf
процесс, который будет следовать за записью, bar.raw.log
и распечатайте их на стандартный вывод, перенаправленный в бесконечный while read ... echo
цикл. Этот цикл выполняет два действия: считывает данные из стандартного ввода в вызываемую переменную буфера line
и затем записывает сгенерированную временную метку со следующими буферизованными данными в bar.log
.
Напишите несколько сообщений в bar.raw.log
. Вы должны сделать это в отдельном окне терминала, потому что первое будет занято, tailf
которое будет следовать за операциями записи и выполнять свою работу. Достаточно просто.
Плюсы в том, что ваше приложение не заблокирует, если вы убьете tailf
. Минусы - менее точные временные метки и дублирующие файлы журналов.
tailf
, добавил правильный способ его использования. На самом деле путь с tailf
более элегантным выглядит, но я оставил путь с надеждой, что он кому-нибудь пригодится.
Вы можете использовать ts
скрипт perl из moreutils
:
$ echo test | ts %F-%H:%M:%.S
2012-11-20-13:34:10.731562 test
Модифицировано из ответа Дмитрия Васильянова.
В bash-скрипте вы можете перенаправлять и переносить вывод с меткой времени строка за строкой на лету.
Когда использовать:
tailf
файл журнала, как сказал Дмитрий Василянов.Пример с именем foo.sh
:
#!/bin/bash
exec &> >(while read line; do echo "$(date +'%h %d %H:%M:%S') $line" >> foo.log; done;)
echo "foo"
sleep 1
echo "bar" >&2
sleep 1
echo "foobar"
И результат:
$ bash foo.sh
$ cat foo.log
May 12 20:04:11 foo
May 12 20:04:12 bar
May 12 20:04:13 foobar
Как это работает
exec &>
Перенаправить stdout и stderr в одно и то же место>( ... )
труба выводит в асинхронную внутреннюю командуНапример:
временная метка канала и журнал в файл
#!/bin/bash
exec &> >(while read line; do echo "$(date +'%h %d %H:%M:%S') $line" >> foo.log; done;)
echo "some script commands"
/path-to/some-thrid-party-programs
Или напечатайте метку времени и войдите в стандартный вывод
#!/bin/bash
exec &> >(while read line; do echo "$(date +'%h %d %H:%M:%S') $line"; done;)
echo "some script commands"
/path-to/some-thrid-party-programs
затем сохраните их в /etc/crontab
настройках
* * * * * root /path-to-script/foo.sh >> /path-to-log-file/foo.log
Я использовал ts
этот способ, чтобы получить запись с отметкой времени в журнале ошибок для сценария, который я использую, чтобы заполнить Cacti статистикой удаленного хоста.
Чтобы проверить Cacti, я использую, rand
чтобы добавить некоторые случайные значения, которые я использую для температурных графиков, чтобы контролировать температуру моей системы.
Pushmonstats.sh - это скрипт, который собирает статистику температуры системы моего ПК и отправляет ее Raspberry Pi, на которой работает Cacti. Некоторое время назад сеть застряла. Я только получил тайм-ауты SSH в моем журнале ошибок. К сожалению, нет записей времени в этом журнале. Я не знал, как добавить отметку времени к записи в журнале. Итак, после некоторых поисков в Интернете, я наткнулся на этот пост, и это то, что я сделал, используя ts
.
Чтобы проверить это, я использовал неизвестную опцию rand
. Который дал ошибку в stderr. Чтобы захватить его, я перенаправляю его во временный файл. Затем я использую cat, чтобы показать содержимое файла и передать его ts
, добавить формат времени, который я нашел в этом посте, и, наконец, записать его в файл ошибок. Затем я очищаю содержимое временного файла, в противном случае я получаю двойные записи для той же ошибки.
Crontab:
* * * * * /home/monusr/bin/pushmonstats.sh 1>> /home/monusr/pushmonstats.log 2> /home/monusr/.err;/bin/cat /home/monusr/.err|/usr/bin/ts %F-%H:%M:%.S 1>> /home/monusr/pushmonstats.err;> /home/monusr/.err
Это дает следующее в моем журнале ошибок:
2014-03-22-19:17:53.823720 rand: unknown option -- '-l'
Может быть, это не очень элегантный способ сделать это, но это работает. Интересно, есть ли более элегантный подход к этому.