Мониторинг файла, пока не найдена строка

60

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

В настоящее время я использую:

tail -f logfile.log | grep -m 1 "Server Started"

Когда строка найдена, grep завершает работу, как и ожидалось, но мне нужно найти способ заставить команду tail выйти так, чтобы скрипт мог продолжаться.

Алекс Хофстиде
источник
Интересно, на какой операционной системе работал оригинальный постер? В системе Linux RHEL5 я с удивлением обнаружил, что команда tail просто умирает, когда команда grep нашла совпадение и завершила работу.
Застер
4
@ZaSter: tailУмирает только на следующей строке. Попробуйте это: date > log; tail -f log | grep -m 1 triggerи затем в другой оболочке: echo trigger >> logи вы увидите выходные данные triggerв первой оболочке, но без завершения команды. Затем попробуйте: date >> logво второй оболочке и команда в первой оболочке прекратит работу. Но иногда это слишком поздно; мы хотим завершить работу, как только появится триггерная линия, а не после завершения строки после триггерной линии.
Alfe
Это отличное объяснение и пример, @Alfe.
ZaSter
1
элегантное надежное решение, состоящее из одной строки, - использовать tail+ grep -qкак ответ 00metheus
Тревор Бойд Смит
superuser.com/a/375331/166461
Вольфганг Фал

Ответы:

41

Простой POSIX с одним вкладышем

Вот простая однострочная. Для этого не нужны специфичные для bash или не POSIX трюки, или даже именованный канал. Все, что вам действительно нужно, это отделить окончание tailот grep. Таким образом, после grepзавершения сценарий может продолжаться, даже если он tailеще не завершен. Итак, этот простой метод доставит вас туда:

( tail -f -n0 logfile.log & ) | grep -q "Server Started"

grepбудет блокировать, пока не найдет строку, после чего он выйдет. Сделав tailзапуск из его собственной вложенной оболочки, мы можем поместить его в фоновом режиме, чтобы он работал независимо. Между тем основная оболочка может продолжить выполнение скрипта сразу после grepвыхода. tailбудет задерживаться в своей вложенной оболочке до тех пор, пока в лог-файл не будет записана следующая строка, а затем завершится (возможно, даже после завершения основного сценария). Суть в том, что конвейер больше не ожидает tailзавершения, поэтому конвейер завершается сразу после grepвыхода.

Некоторые незначительные изменения:

  • Опция -n0 tailпозволяет начинать чтение с текущей последней строки файла журнала, если строка существует ранее в файле журнала.
  • Вы можете tailуказать -F, а не -f. Это не POSIX, но он позволяет tailработать, даже если журнал вращается во время ожидания.
  • Опция -q вместо -m1 делает grepвыход после первого появления, но без распечатки строки триггера. Также это POSIX, а не -m1.
00prometheus
источник
3
Этот подход навсегда оставит tailработу на заднем плане. Как бы вы захватили tailPID внутри фоновой вложенной оболочки и выставили ее в качестве основной оболочки? Я могу только предложить необязательный обходной путь, убив все tailпроцессы, связанные с сеансом , используя pkill -s 0 tail.
Рик ван дер Цвет
1
В большинстве случаев это не должно быть проблемой. Причина, по которой вы делаете это в первую очередь, заключается в том, что вы ожидаете, что в файл журнала будет записано больше строк. tailзавершится, как только он попытается записать в сломанный канал. Канал прервется, как только grepзавершится, поэтому, как только он grepбудет завершен, tailон прекратит работу после того, как файл журнала получит еще одну строку в нем.
00promeheus
когда я использовал это решение я не фон tail -f.
Тревор Бойд Смит
2
@Trevor Бойд Смит, да, это работает в большинстве ситуаций, но проблема OP заключалась в том, что grep не будет завершен, пока не завершится tail, и tail не завершится, пока не появится другая строка в файле журнала после того, как grep завершит работу (когда tail пытается кормить трубопровод, который был прерван по окончании grep). Поэтому, если вы не справитесь с задним фоном, ваш скрипт не продолжит выполнение до тех пор, пока в файле журнала не появится дополнительная строка, а не именно эта строка, которую перехватывает grep.
00promeheus
«След не закончится, пока не появится другая строка после [требуемого строкового шаблона]»: это очень тонко, и я полностью пропустил это. Я не заметил, потому что шаблон, который я искал, был посередине, и все это было распечатано быстро. (Опять же, поведение, которое вы описываете, очень тонкое)
Тревор Бойд Смит
59

Принятый ответ не работает для меня, плюс он сбивает с толку и меняет файл журнала.

Я использую что-то вроде этого:

tail -f logfile.log | while read LOGLINE
do
   [[ "${LOGLINE}" == *"Server Started"* ]] && pkill -P $$ tail
done

Если строка журнала соответствует шаблону, уничтожьте tailзапущенный этим сценарием.

Примечание: если вы хотите также просмотреть вывод на экране, либо | tee /dev/ttyпередайте эхо-строку , либо протестируйте в цикле while.

Роб Уилан
источник
2
Это работает, но pkillне определено POSIX и не доступно везде.
Ричард Хансен
2
Вам не нужно время цикла. используйте часы с опцией -g, и вы можете избавиться от неприятной команды pkill.
1
@ l1zard Можете ли вы сделать это? Как бы вы смотрели хвост файла журнала, пока не появилась конкретная строка? (Менее важно, но мне также любопытно, когда был добавлен watch -g; у меня есть более новый сервер Debian с этой опцией и еще один старый на основе RHEL без него).
Роб Уилан
Мне не совсем понятно, зачем здесь нужен хвост. Насколько я понимаю, это правильно, пользователь хочет выполнить определенную команду, когда появляется определенное ключевое слово в файле журнала. Команда, приведенная ниже с использованием watch, выполняет эту задачу.
1
Не совсем - это проверка, когда данная строка добавляется в файл журнала. Я использую это для проверки, когда Tomcat или JBoss полностью запущены; они пишут «Сервер запущен» (или подобный) каждый раз, когда это происходит.
Роб Уилан
16

Если вы используете Bash (по крайней мере, но кажется, что он не определен POSIX, поэтому он может отсутствовать в некоторых оболочках), вы можете использовать синтаксис

grep -m 1 "Server Started" <(tail -f logfile.log)

Он работает почти так же, как уже упоминавшиеся решения FIFO, но гораздо проще в написании.

Пэтч
источник
1
Это работает, но хвост все еще работает, пока вы не отправите SIGTERM(Ctrl + C, команду выхода или убейте его)
мемы
3
@mems, подойдет любая дополнительная строка в файле журнала. Он tailпрочитает его, попытается вывести его, а затем получит SIGPIPE, который прекратит его. Итак, в принципе вы правы; они tailмогут работать бесконечно, если в файл журнала никогда ничего не записывается. На практике это может быть очень аккуратным решением для многих людей.
Alfe
14

Есть несколько способов добраться 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закрыть stdout перед закрытием stdin, всегда будет условие гонки: grepзакрытие stdout, запуск catдля выхода, запуск echo, запуск tailдля вывода строки. Если эта строка отправлена ​​до того, grepкак grepона успела закрыть стандартный ввод, tailона не будет получена, пока не будет SIGPIPEзаписана другая строка.
  • Требуется доступ для записи в файл журнала.
  • Вы должны быть в порядке с изменением файла журнала.
  • Вы можете повредить файл журнала, если произойдет запись одновременно с другим процессом (записи могут чередоваться, что приводит к появлению новой строки в середине сообщения журнала).
  • Этот подход специфичен для tail- он не будет работать с другими программами.
  • Этап третий трубопровод делает его трудно получить доступ к коду возврата второго этапа трубопровода (если вы не используете расширение POSIX , таких как bash«s 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. Задача состоит в том, чтобы точно знать две вещи в одном и том же месте в коде: tailPID и был ли grepвыход.

При использовании конвейерного типа tail -f ... | grep ...легко изменить первый этап конвейера, чтобы сохранить tailPID в переменной с помощью фонового изображения tailи чтения $!. Также легко изменить второй этап конвейера для запуска killпри grepвыходе. Проблема заключается в том, что два этапа конвейера работают в отдельных «средах выполнения» (в терминологии стандарта POSIX), поэтому второй этап конвейера не может считывать переменные, установленные первым этапом конвейера. Без использования переменных оболочки либо второй этап должен каким-то образом вычислять tailPID, чтобы он мог завершиться tailпри grepвозврате, либо первый этап должен быть каким-то образом уведомлен при grepвозврате.

Второй этап можно использовать pgrepдля получения tailPID, но это будет ненадежно (вы могли бы соответствовать неправильному процессу) и непереносимо ( pgrepне указано в стандарте POSIX).

Первый этап может отправить PID на второй этап через канал, echoиспользуя PID, но эта строка будет смешана с tailвыходными данными. Демультиплексирование двух может потребовать сложной схемы экранирования, в зависимости от вывода 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 может потребоваться сделать что-то особенное, чтобы отключить или регулярно очищать буферы.

Ричард Хансен
источник
Ваше решение (как и другие, я не буду винить вас) пропустит то, что уже записано в файл журнала до начала вашего мониторинга. tail -fБудет выводить только последние десять строк, а затем все следующие. Чтобы улучшить это, вы можете добавить опцию -n 10000к хвосту, чтобы также выдавались последние 10000 строк.
Alfe
Другая идея: Ваше ФИФО решение может быть выпрямлено, я думаю, пропускание выхода из tail -fчерез ФИФО и на нем оглавлению: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe
@Alfe: Я могу ошибаться, но я полагаю, что tail -f logзапись в FIFO приведет к тому, что некоторые системы (например, GNU / Linux) будут использовать блочную буферизацию вместо строчной буферизации, что означает, что grepможет не увидеть совпадающую строку, когда она появляется в журнале. Система может предоставить утилиту для изменения буферизации, например, stdbufиз GNU coreutils. Однако такая утилита была бы непереносимой.
Ричард Хансен
1
@Alfe: На самом деле, похоже, что POSIX ничего не говорит о буферизации, кроме как при взаимодействии с терминалом, поэтому, с точки зрения стандартов, я думаю, что ваше более простое решение так же хорошо, как и мое сложное. Однако я не уверен на 100% о том, как различные реализации ведут себя в каждом конкретном случае.
Ричард Хансен
На самом деле, теперь я перехожу к еще более простому grep -q -m 1 trigger <(tail -f log)предложению в другом месте и согласен с тем фактом, что tailв фоновом режиме он работает на одну строку длиннее, чем нужно.
Alfe
9

Позвольте мне расширить ответ @ 00promeheus (который является лучшим).

Может быть, вы должны использовать тайм-аут, а не ждать бесконечно.

Приведенная ниже функция bash будет блокироваться до тех пор, пока не появится заданное условие поиска или не истечет заданное время ожидания.

Статус выхода будет 0, если строка найдена в течение времени ожидания.

wait_str() {
  local file="$1"; shift
  local search_term="$1"; shift
  local wait_time="${1:-5m}"; shift # 5 minutes as default timeout

  (timeout $wait_time tail -F -n0 "$file" &) | grep -q "$search_term" && return 0

  echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'"
  return 1
}

Возможно, файл журнала еще не существует сразу после запуска вашего сервера. В этом случае вам следует подождать, пока он появится, прежде чем искать строку:

wait_server() {
  echo "Waiting for server..."
  local server_log="$1"; shift
  local wait_time="$1"; shift

  wait_file "$server_log" 10 || { echo "Server log file missing: '$server_log'"; return 1; }

  wait_str "$server_log" "Server Started" "$wait_time"
}

wait_file() {
  local file="$1"; shift
  local wait_seconds="${1:-10}"; shift # 10 seconds as default timeout

  until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done

  ((++wait_seconds))
}

Вот как вы можете использовать это:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"
Elifarley
источник
Итак, где же timeoutкоманда?
ayanamist
На самом деле, использование timeout- это единственный надежный способ не зависать бесконечно в ожидании сервера, который не может запуститься и уже вышел.
gluk47
1
Этот ответ самый лучший. Просто скопируйте функцию и назовите ее, это очень просто и многократно
Христо Вригазов
6

Итак, после некоторого тестирования, я нашел быстрый способ с 1 строкой сделать эту работу. Похоже, tail -f выйдет, когда выйдет grep, но есть одна загвоздка. Кажется, он срабатывает только в том случае, если файл открыт и закрыт. Я сделал это, добавив пустую строку в файл, когда grep найдет совпадение.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> logfile \;

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

Причина, по которой он закрывается, посмотрите на флаг -F, а не на флаг -f.

Алекс Хофстиде
источник
1
Это работает, потому что добавление в лог-файл приводит tailк выводу другой строки, но к тому grepвремени завершилось (возможно, там есть условие гонки). Если grepвышел к тому времени, tailпишет другую строку, tailполучит SIGPIPE. Это приводит tailк немедленному выходу.
Ричард Хансен
1
Недостатки этого подхода: (1) есть условие гонки (оно не всегда может завершиться немедленно) (2) требуется доступ для записи в файл журнала (3) вы должны быть в порядке с изменением файла журнала (4) вы можете повредить log-файл (5), он работает только для tail(6), вы не можете легко настроить его поведение по-разному в зависимости от разных совпадений строк («сервер запущен» и «сервер не удалось запустить»), потому что вы не можете легко получить код возврата средней очереди газопровода. Существует альтернативный подход, который позволяет избежать всех этих проблем - см. Мой ответ.
Ричард Хансен
6

В настоящее время, как указано, все tail -fрешения, представленные здесь, рискуют подхватить ранее зарегистрированную строку «Сервер запущен» (что может или не может быть проблемой в вашем конкретном случае, в зависимости от количества зарегистрированных строк и ротации файла журнала / усечение).

Вместо того, чтобы чрезмерно усложнять вещи, просто используйте умнее tail, как показал bmike с фрагментом perl. Самое простое решение - это retailвстроенная поддержка регулярных выражений с шаблонами условий запуска и остановки :

retail -f -u "Server Started" server.log > /dev/null

Это будет следовать за файлом, как обычно, tail -fпока не появится первый новый экземпляр этой строки, а затем завершится. ( -uОпция не срабатывает на существующие строки в последних 10 строках файла в обычном режиме «Follow».)


Если вы используете GNU tail(из coreutils ), следующий простейший вариант - использовать --pidFIFO (именованный канал):

mkfifo ${FIFO:=serverlog.fifo.$$}
grep -q -m 1 "Server Started" ${FIFO}  &
tail -n 0 -f server.log  --pid $! >> ${FIFO}
rm ${FIFO}

FIFO используется, потому что процессы должны запускаться отдельно, чтобы получить и передать PID. ФИФО по- прежнему страдает от тех же проблем торчать для своевременной записи в дело tailполучить в SIGPIPE , используйте --pidопцию , так что tailвыходит , когда он замечает , что grepокончилось (обычно используется для контроля писателя процесса , а не читатель , но tailБезразлично» т действительно волнует). Опция -n 0используется для tailтого, чтобы старые строки не вызывали совпадения.


Наконец, вы могли бы использовать хвост с сохранением состояния , это сохранит текущее смещение файла, поэтому последующие вызовы будут показывать только новые строки (это также обрабатывает вращение файла). В этом примере используется старый FWTK retail*:

retail "${LOGFILE:=server.log}" > /dev/null   # skip over current content
while true; do
    [ "${LOGFILE}" -nt ".${LOGFILE}.off" ] && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
    sleep 2
done

* Примечание, то же имя, программа отличается от предыдущей опции.

Вместо того, чтобы использовать цикл с переключением процессора, сравните временную метку файла с состоянием file ( .${LOGFILE}.off) и sleep. Используйте « -T», чтобы указать местоположение файла состояния, если требуется, выше предполагается, что текущий каталог. Не стесняйтесь пропустить это условие, или в Linux вы можете использовать более эффективное inotifywait:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done
mr.spuratic
источник
Можно ли комбинировать retailс тайм-аутом, например: «Если прошло 120 секунд, а розничная сеть все еще не прочитала строку, введите код ошибки и выйдите из магазина»?
килтек
@kiltek использует GNU timeout(coreutils) для запуска retailи просто проверяет код завершения 124 по таймауту ( timeoutуничтожит любую команду, которую вы используете, чтобы запустить после установленного времени)
mr.spuratic
4

Это будет немного сложнее, так как вам придется войти в процесс управления и сигнализации. Больше kludgey было бы решением с двумя сценариями, использующим отслеживание PID. Лучше бы использовать именованные каналы, как это.

Какой сценарий оболочки вы используете?

Для быстрого и грязного, одно решение сценария - я сделал бы сценарий perl, используя File: Tail

use File::Tail;
$file=File::Tail->new(name=>$name, maxinterval=>300, adjustafter=>7);
while (defined($line=$file->read)) {
    last if $line =~ /Server started/;
}

Поэтому вместо того, чтобы печатать внутри цикла while, вы можете отфильтровать совпадения строк и выйти из цикла while, чтобы ваш сценарий продолжался.

Любой из них должен включать в себя лишь небольшое обучение для реализации контроля потока наблюдения, который вы ищете.

bmike
источник
используя Bash. мой перл-фу не так силен, но я попробую.
Алекс Хофстиде,
Используйте трубы - они любят Баш, а Баш любит их. (и ваше программное обеспечение для резервного копирования будет уважать вас, когда оно ударит по одному из ваших каналов)
bmike,
maxinterval=>300означает, что он будет проверять файл каждые пять минут. Поскольку я знаю, что моя строка появится в файле на мгновение, я использую гораздо более агрессивные опросы:maxinterval=>0.2, adjustafter=>10000
Стивен Остермиллер
2

дождитесь появления файла

while [ ! -f /path/to/the.file ] 
do sleep 2; done

дождитесь появления строки в файле

while ! grep "the line you're searching for" /path/to/the.file  
do sleep 10; done

https://superuser.com/a/743693/129669

Михаил Адамович
источник
2
Этот опрос имеет два основных недостатка: 1. Он тратит время вычислений, просматривая журнал снова и снова. Рассмотрим /path/to/the.fileразмер 1,4 ГБ; тогда понятно, что это проблема. 2. После появления записи в журнале он дольше, чем необходимо, в худшем случае - 10 с.
Alfe
2

Я не могу представить себе более чистого решения, чем это:

#!/usr/bin/env bash
# file : untail.sh
# usage: untail.sh logfile.log "Server Started"
(echo $BASHPID; tail -f $1) | while read LINE ; do
    if [ -z $TPID ]; then
        TPID=$LINE # the first line is used to store the previous subshell PID
    else
        echo "$LINE"; [[ "$LINE" == *"${*:2}"* ]] && kill -3 $TPID && break
    fi
done

хорошо, может быть имя может быть улучшено ...

Преимущества:

  • он не использует никаких специальных утилит
  • он не записывает на диск
  • он грациозно покидает хвост и закрывает трубу
  • это довольно коротко и легко понять
Джанкарло Спортелли
источник
2

Вам не нужен хвост, чтобы сделать это. Я думаю, что команда часов это то, что вы ищете. Команда watch контролирует вывод файла и может быть прервана с помощью опции -g при изменении вывода.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction
l1zard
источник
1
Поскольку он запускается каждые две секунды, он не завершается сразу после появления строки в файле журнала. Кроме того, это не работает, если файл журнала очень большой.
Ричард Хансен
см. superuser.com/questions/375223/…
Вольфганг Фаль
1

Алекс, я думаю, что это поможет тебе.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> /dev/null ;

эта команда никогда не даст запись в лог-файл, но будет молча grep ...

Мд. Мохсин Али
источник
1
Это не сработает - вы должны добавить, logfileиначе может пройти произвольно много времени, прежде чем tailвывести другую строку и обнаружить, что grepона умерла (через SIGPIPE).
Ричард Хансен
1

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

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

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

Сорин
источник
tail -n +0 -fначинается с начала файла. tail -n 0 -fначинается с конца файла.
Стивен Остермиллер
1
Еще один побочный эффект, который я получаю:myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Стивен Остермиллер
Я считаю, что «следующий список» должен быть «следующей строкой» в этом ответе.
Стивен Остермиллер
Это работает, но tailпроцесс продолжает работать в фоновом режиме.
cbaldan
1

Другие решения здесь имеют несколько проблем:

  • если процесс регистрации уже остановлен или остановлен во время цикла, они будут работать бесконечно
  • редактирование журнала, который должен быть просмотрен только
  • излишняя запись дополнительного файла
  • не учитывая дополнительную логику

Вот то, что я придумал, используя tomcat в качестве примера (удалите хэши, если вы хотите видеть журнал во время его запуска):

function startTomcat {
    loggingProcessStartCommand="${CATALINA_HOME}/bin/startup.sh"
    loggingProcessOwner="root"
    loggingProcessCommandLinePattern="${JAVA_HOME}"
    logSearchString="org.apache.catalina.startup.Catalina.start Server startup"
    logFile="${CATALINA_BASE}/log/catalina.out"

    lineNumber="$(( $(wc -l "${logFile}" | awk '{print $1}') + 1 ))"
    ${loggingProcessStartCommand}
    while [[ -z "$(sed -n "${lineNumber}p" "${logFile}" | grep "${logSearchString}")" ]]; do
        [[ -z "$(ps -ef | grep "^${loggingProcessOwner} .* ${loggingProcessCommandLinePattern}" | grep -v grep)" ]] && { echo "[ERROR] Tomcat failed to start"; return 1; }
        [[ $(wc -l "${logFile}" | awk '{print $1}') -lt ${lineNumber} ]] && continue
        #sed -n "${lineNumber}p" "${logFile}"
        let lineNumber++
    done
    #sed -n "${lineNumber}p" "${logFile}"
    echo "[INFO] Tomcat has started"
}
user503391
источник
1

Команда tailможет быть фоновой, и ее pid отражается в grepподоболочке. В grepподоболочке обработчик прерываний на EXIT может уничтожить tailкоманду.

( (sleep 1; exec tail -f logfile.log) & echo $! ; wait ) | 
     (trap 'kill "$pid"' EXIT; pid="$(head -1)"; grep -m 1 "Server Started")
phio
источник
1

Прочитайте их все. tldr: отделить окончание хвоста от grep.

Наиболее удобными являются две формы

( tail -f logfile.log & ) | grep -q "Server Started"

и если у вас есть Баш

grep -m 1 "Server Started" <(tail -f logfile.log)

Но если этот хвост, сидящий на заднем плане, беспокоит вас, есть более хороший способ, чем пятерка или любой другой ответ здесь. Требуется Баш.

coproc grep -m 1 "Server Started"
tail -F /tmp/x --pid $COPROC_PID >&${COPROC[1]}

Или, если это не хвост, который выводит вещи,

coproc command that outputs
grep -m 1 "Sever Started" ${COPROC[0]}
kill $COPROC_PID
Ян Келлинг
источник
0

Попробуйте использовать inotify (inotifywait)

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

Evengard
источник
Таким образом, весь файл должен быть перепроверен каждый раз, когда что-то записывается в него. Не работает для файлов журналов.
grawity
1
Другой способ - сделать два скрипта: 1. tail -f logfile.log | grep -m 1 "Сервер запущен"> / tmp / found 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / found; kill -KILL - $ MYPID
Evengard
Я хотел бы, чтобы вы отредактировали свой ответ, чтобы показать захват PID и затем использование inotifywait - элегантного решения, которое было бы легко понять для того, кто привык к grep, но нуждался в более сложном инструменте.
bmike
PID того, что вы хотели бы захватить? Я могу попытаться сделать это, если вы объясните немного больше, что вы хотите
Evengard
0

Вы хотите уйти, как только строка написана, но вы также хотите уйти после тайм-аута:

if (timeout 15s tail -F -n0 "stdout.log" &) | grep -q "The string that says the startup is successful" ; then
    echo "Application started with success."
else
    echo "Startup failed."
    tail stderr.log stdout.log
    exit 1
fi
Adrien
источник
-2

как насчет этого:

пока правда; делать, если [! -z $ (grep "myRegEx" myLog.log)]; затем сломаться; фи; сделанный

Ather
источник