Видалення файлу без зриву запису на нього


12

У мене є програма, вихід якої я перенаправляю на файл журналу:

./my_app > log

Я хотів би час від часу очищати (тобто порожній) журнал (на вимогу) і спробувати різні речі на кшталт

cat "" > log

Однак завжди здається, що початкова труба потім порушується, і програма більше не перенаправляє свій вихід у файл журналу.

Чи є якийсь спосіб це зробити?

Оновлення

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


саме тому ви зазвичай використовуєте демон для реєстрації речей ...
Kiwy

@Kiwy Ви можете детально розказати, як це вирішило б проблему?
bangnab

добре, ви зазвичай використовуєте демон журналу або дозволяєте вашій програмі обробляти журнал, тому що писати речі для виведення та перенаправляти його не є надійним. ви можете подивитися syslogdабоlogrotate
Kiwy

2
Чи спрацьовують справи, якщо ви робите ./my_app >> log(змусити додавати) і cp /dev/null logусікаєте?
Марк Плотнік

1
Яке повідомлення про помилку ви отримуєте? Яку поведінку ви бачите? "Більше не перенаправляє свій вихід у файл журналу" не є дуже конкретним. Також cat "" > logне є дійсною catкомандою, оскільки файл не викликається "".
Мікель

Відповіді:


13

Інша форма цієї проблеми виникає з тривалими програмами, журнали яких періодично обертаються. Навіть якщо ви перемістите оригінальний журнал (наприклад, mv log.txt log.1) та заміните його негайно на файл з тим самим іменем до того, як відбудеться фактична реєстрація, якщо процес утримує файл відкритим, він або закінчиться записом log.1(оскільки це все ще може бути відкрита inode) або ні до чого.

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

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

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec &> log.txt
}

echo $BASHPID
exec &> log.txt

count=0;
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done          

Почніть це, розклавши на задній план:

> ./test.sh &
12356

Зауважте, він повідомляє про свій PID терміналу, а потім починає входити в систему log.txt. Тепер у вас є дві хвилини для гри. Зачекайте кілька секунд і спробуйте:

> mv log.txt log.1 && kill -s 2 12356

Просто звичайна kill -2 12356може працювати і для вас тут. Сигнал 2 - це SIGINT (це також те, що робить Ctrl-C, тому ви можете спробувати це на передньому плані та перемістити або вилучити файл журналу з іншого терміналу), який trapповинен потрапити в пастку. Перевіряти;

> cat log.1
12356 Count is now 0
12356 Count is now 1
12356 Count is now 2
12356 Count is now 3
12356 Count is now 4
12356 Count is now 5
12356 Count is now 6
12356 Count is now 7
12356 Count is now 8
12356 Count is now 9
12356 Count is now 10
12356 Count is now 11
12356 Count is now 12
12356 Count is now 13
12356 Count is now 14

Тепер давайте подивимось, чи це все ще написано, log.txtнавіть якщо ми перенесли його:

> cat log.txt
12356 Count is now 15
12356 Count is now 16
12356 Count is now 17
12356 Count is now 18
12356 Count is now 19
12356 Count is now 20
12356 Count is now 21

Зауважте, він продовжував рухатись там, де він зупинився. Якщо ви не хочете зберігати запис, просто очистіть журнал, видаливши його

> rm -f log.txt && kill -s 2 12356

Перевірка:

> cat log.txt
12356 Count is now 29
12356 Count is now 30
12356 Count is now 31
12356 Count is now 32
12356 Count is now 33
12356 Count is now 34
12356 Count is now 35
12356 Count is now 36

Ще йти.

На жаль, ви не можете зробити це в скрипті оболонки для виконаного підпроцесу, на жаль, тому що, якщо він знаходиться на передньому плані, власні обробники сигналів bash trapпризупинені, і якщо ви вилучите його на другий план, ви не можете перепризначити його вихід. Тобто це щось, що вам доведеться реалізувати у своїй заявці.

Однак ...

Якщо ви не можете змінити програму (наприклад, тому, що ви її не написали), у мене є утиліта CLI, яку ви можете використовувати як посередника. Ви також можете реалізувати просту версію цього сценарію в сценарії, який слугує трубою до журналу:

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec 1> log.txt
}

echo "$0 $BASHPID"
exec 1> log.txt

count=0;
while read; do
    echo $REPLY
done  

Давайте назвемо це pipetrap.sh. Тепер нам потрібна окрема програма для тестування, імітуючи додаток, з яким потрібно увійти:

#!/bin/bash

count=0
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done           

Це буде test.sh:

> (./test.sh | ./pipetrap.sh) &
./pipetrap.sh 15859

Це два окремих процеси з окремими PID. Щоб очистити test.shвихідний результат, який здійснюється через pipetrap.sh:

> rm -f log.txt && kill -s 2 15859

Перевірка:

>cat log.txt
15858 Count is now 6
15858 Count is now 7
15858 Count is now 8

15858,, test.shвсе ще працює, і його вихід реєструється. У цьому випадку жодних модифікацій програми не потрібно.


Дякую за приємні пояснення. Однак у моєму випадку я не можу змінити програму для реалізації вашого рішення.
bangnab

2
Якщо ви не можете реалізувати обробник сигналу у вашій програмі (оскільки ви не можете змінити його період), ви можете використовувати цю техніку для передачі журналу через сигнальну пастку - дивіться інформацію після "Однак ..."
goldilocks

Гаразд, я спробую, і я дам вам знати, як це пішло.
bangnab

Нарешті у мене для цього написано додаток CLI (вибачте, що це зайняло трохи більше часу, ніж передбачалося спочатку): cognitivedissonance.ca/cogware/pipelog
goldilocks

6

TL; DR

Відкрийте свій файл журналу в режимі додавання :

cmd >> log

Тоді ви можете сміливо усікати його за допомогою:

: > log

Деталі

З оболонкою, що нагадує Борну, є три основні способи, яким файл може бути відкритий для запису. У режимі лише для запису ( >) читайте + пишете ( <>) або додайте (і лише для запису >>) режиму.

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

Коли ви робите:

cmd > log

logвідкрита в режимі лише для запису оболонкою для stdout cmd.

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

Наприклад, якщо cmdспочатку записується zzz, позиція буде зміщена у байт 4 у файл, а наступного разу cmdабо його діти записують у файл, саме там будуть записуватися дані незалежно від того, що файл зростав чи скоротився в інтервалі .

Якщо файл скоротився, наприклад, якщо він був усічений з

: > log

і cmdпише xx, вони xxбудуть записані на компенсації 4, а перші 3 символи будуть замінені символами NUL.

$ exec 3> log # open file on fd 3.
$ printf zzz >&3
$ od -c log
0000000   z   z   z
0000003
$ printf aaaa >> log # other open file description -> different cursor
$ od -c log
0000000   z   z   z   a   a   a   a
0000007
$ printf bb >&3 # still write at the original position
$ od -c log
0000000   z   z   z   b   b   a   a
0000007
$ : > log
$ wc log
0 0 0 log
$ printf x >&3
$ od -c log
0000000  \0  \0  \0  \0  \0   x
0000006

Це означає, що ви не можете вкоротити файл, який був відкритий у режимі лише для запису (і це те саме для читання + запису ), як якщо б ви це зробили, процеси, у яких були відкриті дескриптори файлів, залишать символи NUL на початку файл (ті, крім ОС / X, зазвичай не займають місця на диску, хоча вони стають рідкісними файлами).

Натомість (і ви помітите, що більшість програм це робить, коли вони записують у файли журналу), ви повинні відкрити файл у режимі додавання :

cmd >> log

або

: > log && cmd >> log

якщо ви хочете почати з порожнього файлу.

У режимі додавання всі записи виконуються в кінці файлу, незалежно від того, де було останнє записування:

$ exec 4>> log
$ printf aa >&4
$ printf x >> log
$ printf bb >&4
$ od -c log
0000000   a   a   x   b   b
0000005
$ : > log
$ printf cc >&4
$ od -c log
0000000   c   c
0000002

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

На останніх версіях Linux ви можете перевірити поточну позицію та чи був відкритий дескриптор файлу в режимі додавання , переглянувши /proc/<pid>/fdinfo/<fd>:

$ cat /proc/self/fdinfo/4
pos:        2
flags:      0102001

Або з:

$ lsof +f G -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE  FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG 0x8401;0x0 252,18        2 59431479 /home/chazelas/log
~# lsof +f g -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG   W,AP,LG 252,18        2 59431479 /home/chazelas/log

Ці прапори відповідають O ..._ прапорам, переданим в openсистемний виклик.

$ gcc -E - <<< $'#include <fcntl.h>\nO_APPEND O_WRONLY' | tail -n1
02000 01

( O_APPENDстановить 0x400 або восьмеричний 02000)

Отже, оболонка >>відкриває файл з O_WRONLY|O_APPEND(і 0100000 тут є O_LARGEFILE, що не має відношення до цього питання), поки >є O_WRONLYлише (і <>є O_RDWRлише).

Якщо ви робите:

sudo lsof -nP +f g | grep ,AP

для пошуку файлів, відкритих за допомогою O_APPEND, ви знайдете більшість файлів журналів, які зараз відкриті для запису у вашій системі.


Для чого використовується :(двокрапка) в : > ?
mvorisek

1
@Mvorisek, це перенаправити висновок команди , яка не виробляє ніякого висновку: :. Без команди поведінка змінюється між оболонками.
Стефан Шазелас

1

Якщо я правильно розумію, це teeвиглядає як розумний підхід:

$ ./myapp-that-echoes-the-date-every-second | tee log > /dev/null &
[1] 20519
$ head log
Thu Apr  3 11:29:34 EDT 2014
Thu Apr  3 11:29:35 EDT 2014
Thu Apr  3 11:29:36 EDT 2014
$ > log
$ head log
Thu Apr  3 11:29:40 EDT 2014
Thu Apr  3 11:29:41 EDT 2014
Thu Apr  3 11:29:42 EDT 2014

1

Як швидке рішення можна використовувати журнал з обертанням (наприклад, щоденне обертання):

date=`date +%Y%m%d`
LOGFILE=/home/log$date.log

і перенаправити вхід до нього ./my_app >> log$date.log


Я хотів би мати можливість обертатись на вимогу. Це фактично журнал, який виробляється під час автоматизованого тесту, і я хотів би очистити його перед запуском тесту.
bangnab

0

Це проблема, яка давно вирішується за допомогою syslog (у всіх її варіантах), але є два інструменти, які вирішили б вашу конкретну проблему мінімум зусиль.

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

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

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