Есть несколько способов получить tail чтобы выйти:
Плохой подход: заставить tail написать еще одну строку
Вы можете заставить tail написать еще одну строку вывода сразу после того, как grep найдет совпадение и завершит работу. Это заставит tail получить SIGPIPE , что приведет к его выходу. Один из способов сделать это - изменить файл, отслеживаемый tail после выхода из grep .
Вот пример кода:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
В этом примере cat не будет выходить, пока grep не закроет свой стандартный вывод, поэтому tail вряд ли сможет записать в канал, пока grep не сможет закрыть свой стандартный вывод. cat используется для распространения стандартного вывода grep без изменений.
Этот подход относительно прост, но есть несколько недостатков:
- Если
grep закрывает стандартный вывод перед закрытием стандартного ввода, всегда будет условие гонки: grep закрывает стандартный вывод, вызывая выход cat , вызывая echo , вызывая tail для вывода строки. Если эта строка отправляется в grep до того, как grep сможет закрыть stdin, tail не получит SIGPIPE пока не напишет другую строку.
- Требуется доступ для записи в файл журнала.
- Вы должны быть в порядке с изменением файла журнала.
- Вы можете повредить файл журнала, если произойдет запись одновременно с другим процессом (записи могут чередоваться, что приводит к появлению новой строки в середине сообщения журнала).
- Этот подход специфичен для
tail - он не будет работать с другими программами.
- Третий этап конвейера затрудняет получение доступа к коду возврата второго этапа конвейера (если вы не используете расширение POSIX, такое как массив
bash PIPESTATUS ). В этом случае это не имеет большого значения, потому что grep всегда будет возвращать 0, но в целом средняя стадия может быть заменена другой командой, код возврата которой вас волнует (например, что-то, что возвращает 0, когда обнаружен "сервер запущен", 1 при обнаружении "не удалось запустить сервер").
Следующие подходы позволяют избежать этих ограничений.
Лучший подход: избегайте трубопроводов
Вы можете использовать FIFO, чтобы полностью избежать конвейера, что позволит продолжить выполнение после возврата grep . Например:
fifo=/tmp/tmpfifo.$$
mkfifo "${fifo}" || exit 1
tail -f logfile.log >${fifo} &
tailpid=$! # optional
grep -m 1 "Server Started" "${fifo}"
kill "${tailpid}" # optional
rm "${fifo}"
Строки, помеченные комментарием # optional могут быть удалены, и программа все равно будет работать; tail будет просто задерживаться, пока не прочитает другую строку ввода или не будет уничтожен каким-либо другим процессом.
Преимущества этого подхода:
- вам не нужно изменять файл журнала
- подход работает для других утилит, кроме
tail
- он не страдает от состояния гонки
- вы можете легко получить возвращаемое значение
grep (или любой другой альтернативной команды, которую вы используете)
Недостатком этого подхода является сложность, особенно управление FIFO: вам нужно будет безопасно сгенерировать временное имя файла, и вам нужно будет убедиться, что временный FIFO удален, даже если пользователь нажимает Ctrl-C в середине сценарий. Это можно сделать с помощью ловушки.
Альтернативный подход: отправить сообщение Kill tail
Вы можете получить выход из этапа tail конвейера, отправив ему сигнал типа SIGTERM . Задача состоит в том, чтобы точно знать две вещи в одном и том же месте в коде: PID tail и завершился ли grep .
С трубопроводом, как tail -f ... | grep ... это легко модифицировать первый этап трубопровода , чтобы сохранить PID tail «s в переменной, tail и фоновый режим чтения $! , Также легко изменить второй этап конвейера для запуска kill при выходе из grep . Проблема заключается в том, что два этапа конвейера работают в отдельных "средах выполнения" (в терминологии стандарта POSIX), поэтому второй этап конвейера не может читать переменные, установленные первым этапом конвейера. Без использования переменных оболочки либо второй этап должен каким-то образом определять PID tail , чтобы он мог уничтожить tail при возврате grep , либо первый этап должен каким-то образом уведомляться при возврате grep .
Второй этап может использовать pgrep для получения PID tail , но это будет ненадежно (вы можете соответствовать неправильному процессу) и непереносимо (pgrep не определено стандартом POSIX).
Первый этап может отправить PID на вторую ступень через трубу echo ИНГ на PID, но эта строка будет получить смешанную с выходом tail «s. Демультиплексирование двух может потребовать сложной схемы экранирования, в зависимости от выхода tail .
Вы можете использовать FIFO, чтобы вторая ступень конвейера уведомляла первую ступень конвейера при выходе из grep . Тогда на первом этапе можно убить tail . Вот пример кода:
fifo=/tmp/notifyfifo.$$
mkfifo "${fifo}" || exit 1
{
# run tail in the background so that the shell can
# kill tail when notified that grep has exited
tail -f logfile.log &
# remember tail's PID
tailpid=$!
# wait for notification that grep has exited
read foo <${fifo}
# grep has exited, time to go
kill "${tailpid}"
} | {
grep -m 1 "Server Started"
# notify the first pipeline stage that grep is done
echo >${fifo}
}
# clean up
rm "${fifo}"
Этот подход имеет все плюсы и минусы предыдущего подхода, за исключением того, что он более сложный.
Предупреждение о буферизации
POSIX позволяет полностью буферизовать потоки stdin и stdout, что означает, что вывод tail может не обрабатываться grep течение сколь угодно длительного времени. В системах GNU не должно быть никаких проблем: GNU grep использует read() , что исключает любую буферизацию, а GNU tail -f делает регулярные вызовы fflush() при записи в stdout. В системах без GNU может потребоваться сделать что-то особенное, чтобы отключить или регулярно очищать буферы.