7

Я хотел бы проследить за изменениями в файле с помощью tail -f а затем распечатать первую строку, соответствующую команде grep перед завершением. Какой самый простой способ сделать это? До сих пор я экспериментировал с такими вещами, как:

tail -f foo.log | grep 'bar' | head -1

Однако конвейер просто зависает, предположительно из-за буферизации. Я тоже пробовал

tail -f foo.log | grep --line-buffered 'bar' | head -1

Это выводит строку, но процесс не завершается, пока я не нажму ^ C, предположительно потому, что для завершения head -1 необходима вторая строка ввода. Какой лучший способ решить эту проблему?

5 ответов5

10
tail -f foo.log | grep -m 1 bar

если файл foo.log записан в редких случаях, вы можете сделать:

grep -m 1 bar <( tail -f foo.log )

Следует отметить, что tail -f будет оставаться в фоновом режиме, пока не получит еще одну строку для вывода. Если это занимает много времени, это может быть проблематично. Решение в таком случае:

grep -m 1 bar <( exec tail -f foo.log ); kill $! 2> /dev/null

kill убьет оставшийся процесс tail -f, и мы скрываем ошибки, потому что, возможно, хвост исчезнет к тому времени, когда будет вызвано kill.

1

tail -f ожидает SIGPIPE , который, как уже указывалось в depesz, может быть инициирован только другой записью tail -f в закрытый канал после успешного совпадения grep -m 1 , можно избежать, если команда tail -f получает Background и его фоновый pid отправляются в подоболочку grep , которая, в свою очередь, реализует trap при выходе, которая явно убивает tail -f .

#(tail -f foo.log & echo ${!} ) |
(tail -f foo.log & echo ${!} && wait ) | 
   (trap 'kill "$tailpid"; trap - EXIT' EXIT; tailpid="$(head -1)"; grep -m 1 bar)
1

Конструкция <() работает только в bash. Мое решение:

sleep $timeout &
pid=$!
tail -n +0 --pid=$pid -F "$file" | ( grep -q -m 1 "$regexp" && kill -PIPE $pid )
wait $pid

Помимо работы в оболочке POSIX, еще одним преимуществом является то, что период ожидания может быть ограничен значением времени ожидания. Обратите внимание, что код результата этого скриптлета инвертирован: 0 означает, что истекло время ожидания.

0

Опираясь на ответ https://superuser.com/a/275962/37154 , я понял, что, если tail начнется слишком поздно, какой-то экземпляр указанной строки, возможно, уже прошел. Решение состоит в том, чтобы составить список файлов целиком.

grep -m 1 bar <( exec tail -n +1 -f foo.log ); kill $!
-1

Я думаю, что применение unbuffer, как это,

tail -f foo.log | unbuffer -p grep 'bar' | head -1

должно сработать. Я не в системе, где я могу проверить это, и я не могу объяснить, почему это будет работать, а grep --line-buffered - нет. Тем не менее, я попробовал эту альтернативу, которая, кажется, работает:

tail -f foo.log | sed -n '/bar/{p;q}'

Когда совпадение с bar найдено, p печатает его, и q немедленно завершает работу.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .