Довольно популярный и актуальный вопрос, который возникает, например, при чистке интенсивно растущих журналов постоянно запущенных процессов.
Ответ в двух словах
Обнулять можно, но для этого пишущий процесс должен открывать этот файл с использованием флага O_APPEND
. Ниже приводится соответствующий отрывок из open(2)
:
…
O_APPEND
The file is opened in append mode. Before eachwrite(2)
, the file offset is positioned at the end of the file, as if withlseek(2)
.O_APPEND
may lead to corrupted files on NFS file systems if more than one process appends data to a file at once. This is because NFS does not support appending to a file, so the client kernel has to simulate it, which can't be done without a race condition.
…
Несколько процессов, пишущих в один файл на NFS — это отдельный клинический случай. Пусть у нас есть один-единственный процесс, пишущий в файл.
Как же быть, если наш процесс не использует флаг O_APPEND?
Есть два варианта решения в зависимости от того, как создается этот файл:
- Путем перенаправления стандартных потоков вывода (stdout/stderr):
$ /path/binary >/path/log 2>&1
Здесь при открытии файла флаг
O_APPEND
будет использоваться в том случае, если вместо оператора перенаправления>
использовать оператор перенаправления с добавлением>>
:$ /path/binary >>/path/log 2>&1
- Процесс пишет напрямую в предопределенный файл
/path/log_predefined
. В этом случае перед запуском процесса создается связка из именованного канала (named pipe) и, например,cat(1)
, приводящая этот случай к предыдущему:$ mkfifo /path/log_predefined && cat </path/log_predefined >>/path/log &
Кстати, при завершении нашего процесса «фоновый» cat будет также завершаться. Как вариант решения этой проблемы можно запускать cat в бесконечном цикле:
$ bash -c "while :; do cat <log_predefined >>log; done" &
Теперь можно безболезненно обнулить файл журнала в любой момент, например, так:
$ : > /path/log
Однако следует заметить, что флаг O_APPEND
не поддерживается некоторыми старыми оболочками, например, Bourne shell (/bin/sh
), все еще использующимся в большинстве скриптов.
Проверим это утверждение, например, в Solaris. При этом предупрежу заранее, что пользователям Linux проблема со старой /bin/sh
не грозит, т.к.:
$ uname -o && ls -l /bin/sh
GNU/Linux
lrwxrwxrwx 1 root root 4 2008-11-10 17:16 /bin/sh -> bash
Открытие файлов оператором '>>' в разных оболочках
Приведённый ниже скрипт выполняет команду echo
c перенаправлением вывода в новый или существующий файл оператором '>>' в разных оболочках, отслеживая при этом интересующие нас системные вызовы creat(2), open(2), lseek(2)
и их варианты:
1 #!/bin/sh
2
3 trace() {
4 eval "truss -tcreat,open,lseek $* 2>&1 | sed -ne '/my_test/,$p;'"
5 }
6
7 for shell in sh csh tcsh ksh bash zsh; do
8 printf "nRedirecting with '>>' to a non-existing file in '$shell'n"
9 [ -f my_test ] && rm my_test
10 trace "$shell -c 'echo test >> my_test'"
11
12 printf "nRedirecting with '>>' to an existing file in '$shell'n"
13 [ -f my_test ] || : > my_test
14 trace "$shell -c 'echo test >> my_test'"
15 done
В следующей таблице приводятся результаты нашего исследования — флаги системного вызова open(2)
, использовавшиеся оболочками для открытия нового или существующего файла оператором '>>'. При этом учитывается, что системный вызов creat(2)
является синонимом open()
, вызванным с флагами O_WRONLY | O_CREAT | O_TRUNC
.
Оболочка | Файл | |
---|---|---|
новый | существующий | |
sh csh |
O_WRONLY | O_CREAT | O_TRUNC | O_WRONLY |
tcsh | O_WRONLY | O_CREAT | O_TRUNC | O_WRONLY | O_APPEND |
bash ksh |
O_WRONLY | O_APPEND | O_CREAT | |
zsh | O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY |
Флаг O_NOCTTY
в zsh
используется на тот случай, если в качестве имени файла будет указано терминальное устройство, см. open(2)
:
…
O_NOCTTY
If set and path identifies a terminal device,open()
does not cause the terminal device to become the controlling terminal for the process.
…
Таким образом, оболочки sh и csh, а также tcsh при открытии нового файла, не используют флаг O_APPEND
, а описанный выше способ обнуления используемых файлов в этих оболочках использовать не получится. В более современных оболочках (ksh, bash, zsh) этой проблемы нет.
В следующей статье я постараюсь разобраться подробнее в нюансах работы со стандартными потоками и каналами в UNIX применительно к задаче ротации журналов.
Автор: nfubh