Відповіді:
$ 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.
Стандартне перенаправлення потоку, описане вище, називається трубою . Ви можете перенаправляти його не просто |
між командами в сценарії, а через файл FIFO (він також називається pipe ). Одна програма запише у файл, а інша прочитає дані та отримає їх у якості першого відправлення.
Виберіть приклад:
$ 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
приймає його, обробляє і записує в звичайний файл.
Важлива примітка - фіфо, як і будь-яка інша труба, не має сенсу, якщо одна з її сторін не підключена до жодного процесу. Якщо ви спробуєте записати на трубу, поточний процес заблокується, поки хтось не прочитає дані з іншого боку труби. Якщо ви хочете читати з труби, процес блокується, поки хтось не запише дані в трубу. sed
Цикл в прикладі вище нічого не робить (спить) , поки ви не зробите echo
.
Для вашої конкретної ситуації ви просто налаштовуєте свою програму на запис журналів у файл fifo. Якщо ви не можете його налаштувати - просто видаліть оригінальний файл журналу та створіть файл fifo. Але зауважте ще раз, що якщо sed
цикл загине з якоїсь причини - ваша програма буде заблокована при спробі переходу write
у файл, поки хтось не вийде read
з файлового каналу.
Перевага - поточна мітка часу, що оцінюється та додається до повідомлення, коли програма записує його у файл.
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
Або надрукуйте часову позначку та увійдіть до stdout
#!/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
цей спосіб, щоб отримати запис із позначкою часу в журналі помилок для сценарію, який я використовую, щоб кактуси заповнилися статистикою віддаленого хоста.
Для тестування кактусів я використовую, rand
щоб додати деякі випадкові значення, які використовую для графіків температури для моніторингу температури моєї системи.
Pushmonstats.sh - це сценарій, який збирає статистику температур системи мого ПК та надсилає його до Raspberry Pi, на якому працюють кактуси. Деякий час тому мережа застрягла. У моєму журналі помилок я отримав лише тайм-аути 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'
Можливо, це не дуже елегантний спосіб зробити це, але це працює. Цікаво, чи є до цього більш елегантний підхід.