Является эхо-атомом при написании отдельных строк

В настоящее время я пытаюсь заставить script правильно записать вывод из других запущенных команд в файл журнала. script будет записывать собственные сообщения в файл журнала с помощью эха, и есть способ, с помощью которого я могу передать строки из другой программы.

Основная проблема заключается в том, что программа, которая производит вывод, запускается в фоновом режиме, поэтому моя функция, которая выполняет чтение, может писать в файл журнала. Это может быть проблема? Эхо всегда записывает только одну строку, поэтому не должно быть трудно обеспечить атомарность. Однако я посмотрел в google, и я не нашел способа убедиться, что он на самом деле является атомарным.

Вот текущий script:

LOG_FILE=/path/to/logfile

write_log() {
  echo "$(date +%Y%m%d%H%M%S);$1" >> ${LOG_FILE}
}

write_output() {
  while read data; do
    write_log "Message from SUB process: [ $data ]"
  done
}

write_log "Script started"
# do some stuff
call_complicated_program 2>&1 | write_output &
SUB_PID=$!
#do some more stuff
write_log "Script exiting"
wait $SUB_PID

Как вы можете видеть, script может писать как самостоятельно, так и из-за перенаправленного вывода. Может ли это вызвать хавок в файле?

Ответы

Ответ 1

echo просто простая обертка вокруг write (это упрощение, см. ниже), чтобы определить, является ли echo атомарным, полезно искать запись. Из единой спецификации UNIX:

Атомный/неатомный: запись является атомарной, если вся сумма, записанная в одной операции, не чередуется с данными из любого другого процесса. Это полезно, когда несколько авторов отправляют данные одному читателю. Приложениям необходимо знать, как большой запрос на запись может выполняться атомарно. Этот максимум называется {PIPE_BUF}. Этот объем IEEE Std 1003.1-2001 не говорит, являются ли запросы на запись более чем для {PIPE_BUF} байтов атомарными, но требует, чтобы записи {PIPE_BUF} или меньшее количество байтов были атомарными.

Вы можете проверить PIPE_BUF на вашей системе с помощью простой программы на языке C. Если вы просто печатаете одну строку вывода, это не смешно долго, она должна быть атомарной.

Вот простая программа для проверки значения PIPE_BUF:

#include <limits.h>
#include <stdio.h>

int main(void) {
  printf("%d\n", PIPE_BUF);

  return 0;
}

В Mac OS X это дает мне 512 (минимальное допустимое значение для PIPE_BUF). В Linux я получаю 4096. Поэтому, если ваши строки довольно длинные, убедитесь, что вы проверяете их в рассматриваемой системе.

изменить для добавления: я решил проверить реализацию echo в Bash, чтобы подтвердить, что он будет печатать атомарно, Оказывается, echo использует putchar или printf в зависимости от того, используете ли вы параметр -e. Это буферизованные операции stdio, что означает, что они заполняют буфер и фактически записывают его только при достижении новой строки (в режиме буферизации в строке), буфер заполняется (в режиме с буферизацией) или вы явно скрываете выход с fflush. По умолчанию поток будет в режиме буферизации в строке, если он является интерактивным терминалом, и блокирует буферный режим, если это любой другой файл. Bash никогда не устанавливает тип буферизации, поэтому для вашего файла журнала он должен по умолчанию блокировать режим буферизации. Затем конец echo встроенный, Bash вызывает fflush, чтобы очистить выходной поток, Таким образом, вывод всегда будет очищаться в конце echo, но может быть сброшен раньше, если он не помещается в буфер.

Размер используемого буфера может быть BUFSIZ, хотя он может быть другим; BUFSIZ - это размер по умолчанию, если вы явно устанавливаете буфер с помощью setbuf, но нет никакого переносного способа определить фактический размер вашего буфера. Также нет портативных руководств для того, что BUFSIZ, но когда я тестировал его на Mac OS X и Linux, он был в два раза меньше PIPE_BUF.

Что все это значит? Поскольку вывод echo буферизуется, он фактически не вызывает write до тех пор, пока буфер не будет заполнен или не будет вызван fflush. В этот момент вывод должен быть записан, и должна быть указана гарантия атомарности, упомянутая выше. Если размер буфера stdout больше, чем PIPE_BUF, то PIPE_BUF будет наименьшей атомной единицей, которая может быть выписана. Если PIPE_BUF больше размера буфера stdout, тогда поток будет выписывать буфер, когда буфер заполняется.

Таким образом, echo гарантируется, что атомарно записывает последовательности короче, чем меньше PIPE_BUF, и размер буфера stdout, что наиболее вероятно BUFSIZ. В большинстве систем BUFSIZ больше, чем PIPE_BUF.

tl; dr: echo будет атомарно выводить строки, если эти линии достаточно коротки. В современных системах вы, вероятно, можете хранить до 512 байт, но невозможно определить предел портативно.

Ответ 2

Нет принудительной блокировки файла, но оператор → безопасен, оператоp > небезопасен. Таким образом, ваша практика безопасна.