Моніторинг файлу, поки не буде знайдено рядок


60

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

В даний час я використовую:

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

Коли рядок буде знайдено, grep закривається, як очікувалося, але мені потрібно знайти спосіб змусити команду хвоста вийти занадто, щоб сценарій міг продовжуватись.


Цікаво, на якій операційній системі працював оригінальний плакат. У системі Linux RHEL5 я здивовано виявив, що команда хвоста просто вмирає, як тільки команда grep знайде збіг та закінчиться.
ZaSter

4
@ZaSter: Вмирає tailлише в наступному рядку. Спробуйте це: date > log; tail -f log | grep -m 1 triggerа потім в іншій оболонці: echo trigger >> logі ви побачите вихід triggerу першій оболонці, але не припинення команди. Потім спробуйте: date >> logу другій оболонці і команда в першій оболонці закінчиться. Але іноді це вже пізно; ми хочемо припинити, як тільки з’явилася лінія тригера, а не тоді, коли рядок після лінії тригера завершена.
Альфе

Це відмінне пояснення та приклад, @Alfe.
ZaSter

1
елегантне однолінійне надійне рішення - використовувати tail+ grep -qяк відповідь 00прометей
Тревор Бойд Сміт

Відповіді:


41

Простий одношаровий POSIX

Ось простий однолінійний. Для цього не потрібні хитросмугові чи не 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 - це не так.

3
Цей підхід залишить tailроботу у фоновому режимі назавжди. Як би ви захопили tailPID у фоновому режимі під оболонки та викрили його головною оболонкою? Я можу придумати допоміжний варіант вирішення, вбивши всі tailпроцеси, що додаються до сеансу , використовуючи pkill -s 0 tail.
Rick van der Zwet

1
У більшості випадків використання це не повинно бути проблемою. Причина, по якій ви робите це в першу чергу, полягає в тому, що ви очікуєте, що в файл журналу буде записано більше рядків. tailприпиняється, як тільки він спробує записати в розбиту трубу. Труба розірветься, як тільки grepвона завершиться, тому після grepїї завершення tailбуде припинено після того, як файл журналу отримає ще один рядок у ньому.
00прометей

коли я використав це рішення, я не тло tail -f.
Тревор Бойд Сміт

2
@Trevor Бойд Сміт, так, це працює в більшості ситуацій, але проблема ОП полягала в тому, що grep не завершиться, поки хвіст не закриється, а хвіст не вийде, поки інший рядок не з’явиться у файлі журналу після виходу grep (коли хвіст намагається подати трубопровід, який був розірваний греп-ендом). Тому, якщо ви не перетворите хвіст, ваш сценарій не буде продовжувати виконання, доки в файлі журналу не з’явиться зайвий рядок, а не саме в тій лінії, що знімається grep.
00прометей

Знову "trail не вийде, поки не з'явиться інший рядок після [потрібного рядкового рядка]": Це дуже тонко, і я повністю пропустив це. Я не помітив, тому що шаблон, який я шукав, був посередині, і все це швидко роздрукувалося. (Знову поведінка, яку ви описуєте, дуже тонка)
Тревор Бойд Сміт

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.
l1zard

@ l1zard Ти можеш це м'ясо? Як би ви спостерігали за хвостом файлу журналу, поки не з’явився певний рядок? (Менш важливо, але мені також цікаво, коли було додано watch -g; у мене є новіший сервер Debian з цією опцією та ще один старий на базі RHEL без нього).
Роб Уілан

Мені не зовсім зрозуміло, навіщо хвіст тут навіть потрібен. Наскільки я розумію це правильно, користувач хоче виконати певну команду, коли з’явиться певне ключове слово у файлі журналу. Команда, подана нижче за допомогою watch, виконує саме цю задачу.
l1zard

Не зовсім - це перевірка, коли в файл журналу додається заданий рядок . Я використовую це для перевірки, коли Tomcat або JBoss повністю запущені; вони пишуть "Сервер запущений" (або подібне) кожного разу, коли це відбувається.
Роб Уїлан

16

Якщо ви використовуєте Bash (принаймні, але, здається, це не визначено POSIX, тому він може бути відсутнім у деяких оболонках), ви можете використовувати синтаксис

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

Це працює майже як уже згадувані рішення FIFO, але набагато простіше написати.


1
Це працює, але хвіст все ще працює, поки ви не надішлете SIGTERM(Ctrl + C, команду виходу, або вб'єте)
пам’яті

3
@mems, будь-який додатковий рядок у файлі журналу зробить. tailПрочитає, спробуйте вивести його , а потім отримати SIGPIPE , який буде припинити його дію . Отже, в принципі ви праві; tailможе працювати нескінченно довго , якщо нічого не буде записано в лог - файл коли - або знову. На практиці це може бути дуже акуратним рішенням для багатьох людей.
Альфе

14

Є кілька способів дістатися tailдо виходу:

Поганий підхід: змусити tailнаписати ще один рядок

Ви можете змусити tailзаписати інший рядок виводу відразу після того, grepяк знайшли збіг та вийшли. Це призведе tailдо отримання значка SIGPIPE, що призведе до його виходу. Один із способів зробити це - змінити файл, який контролюється tailпісля grepвиходів.

Ось приклад коду:

tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }

У цьому прикладі catне вийде, поки grepне закриє свій stdout, тому tail, швидше за все, не зможе записати на трубу до grepтого, як змогла закрити stdin. catвикористовується для розповсюдження стандартного виходу grepнемодифікованого.

Цей підхід відносно простий, але є кілька недоліків:

  • Якщо grepзакриття stdout перед закриттям stdin, завжди буде умова перегону: grepзакриває stdout, запускаючи catвихід, запускаючи echo, викликаючи tailвиведення рядка. Якщо цей рядок буде надісланий grepраніше, grepвін мав можливість закрити stdin, tailвін не отримає, SIGPIPEпоки не напише інший рядок.
  • Він вимагає доступу для запису до файлу журналу.
  • Ви повинні бути в порядку, змінюючи файл журналу.
  • Ви можете пошкодити файл журналу, якщо трапляєтеся писати одночасно з іншим процесом (записи можуть бути переплетеними, що спричинить появу нового рядка в середині повідомлення журналу).
  • Цей підхід специфічний tail- він не працюватиме з іншими програмами.
  • Третій етап конвеєра ускладнює доступ до коду повернення другого етапу конвеєра (якщо ви не використовуєте розширення POSIX, наприклад масив bashs 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. Завдання надійно знає дві речі в одному і тому ж місці в коді: tail'PID' і те, чи не grepвийшло.

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

Другий етап можна використати pgrepдля отримання tailPID, але це було б ненадійним (ви можете відповідати неправильному процесу) та непереносимим ( 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, можливо, доведеться зробити щось особливе, щоб відключити або регулярно промивати буфери.


Ви, рішення (як і інші, я не буду вас звинувачувати), пропустите речі, які вже були записані у лог-файл до початку моніторингу. Запис tail -fвидасть лише останні десять рядків, а потім усі наступні. Щоб покращити це, ви можете додати параметр -n 10000у хвіст, щоб також було видано останні 10000 рядків.
Альфе

Інша ідея: Ваше ФІФО рішення може бути випрямлено, я думаю, пропускання виходу з tail -fчерез ФІФО і на ньому змісту: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Альфе

@Alfe: я можу помилитися, але я вважаю, що tail -f logзапис у FIFO призведе до того, що деякі системи (наприклад, GNU / Linux) використовують блокування на основі блоку замість буферування на основі рядків, що означає, що воно grepможе не бачити відповідну лінію, коли вона з'являється в журналі. Система може надавати утиліту для зміни буферизації, наприклад, stdbufз GNU coreutils. Однак така утиліта була б не портативною.
Річард Хансен

1
@Alfe: Насправді, схоже, POSIX нічого не говорить про буферизацію, крім взаємодії з терміналом, тому, з точки зору стандартів, я вважаю, що ваше просте рішення є таким же хорошим, як і моє складне. Однак я не на 100% впевнений у тому, як реально поводяться різні реалізації у кожному конкретному випадку.
Річард Хансен

Насправді я зараз переживаю ще простіше, grep -q -m 1 trigger <(tail -f log)запропоноване в іншому місці, і живу з тим, що tailпроходить на одну лінію довше у фоновому режимі, ніж потрібно.
Альфе

9

Дозвольте розкрити відповідь на @ 00prometheus (яка найкраща).

Можливо, вам слід використовувати тайм-аут, а не чекати нескінченно.

Функція 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"

Отже, де timeoutкоманда?
аянаміст

Насправді, використання timeout- це єдиний надійний спосіб не вішати безстроково очікування сервера, який не може запуститися і вже вийшов.
gluk47

1
Ця відповідь найкраща. Просто скопіюйте функцію та зателефонуйте їй, це дуже легко та багаторазово
Христо Вригазов

6

Отож, зробивши тестування, я знайшов швидкий 1-рядовий спосіб зробити цю роботу. Здається, хвіст -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), ви можете пошкодити файл журналу (5) він працює лише для tail(6), ви не можете легко налаштувати його так, щоб він поводився по-різному, залежно від різних збігів рядків ("сервер запущений" проти "сервер не вдався"), тому що ви не можете легко отримати код повернення середньої стадії трубопроводу. Існує альтернативний підхід, який дозволяє уникнути всіх цих проблем - дивіться мою відповідь.
Річард Хансен

6

В даний час, як дано, всі tail -fрішення тут ризикують отримати раніше зареєстрований рядок "Початок роботи з сервером" (що може бути, а може, і не бути проблемою у вашому конкретному випадку, залежно від кількості зареєстрованих рядків та обертання файлів журналу / усічення).

Замість того, щоб надмірно ускладнювати речі, просто використовуйте розумніші tail, як показав bmike за допомогою perl snippit. Найпростішим рішенням є це, retailяке має інтегровану підтримку регулярних виразів з моделями умов запуску та зупинки :

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

Це буде слідувати файлу як звичайне, tail -fпоки не з’явиться перший новий екземпляр цього рядка, а потім вийти. (Ця -uопція не спрацьовує на існуючих рядках в останніх 10 рядках файлу, коли в звичайному режимі "слідувати".)


Якщо ви використовуєте GNU tail(від coreutils ), наступним найпростішим варіантом є використання --pidта FIFO (названа труба):

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

* Примітка, те саме ім'я, інша програма до попереднього параметра.

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

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done

Чи можна поєднувати retailтайм-аут, наприклад: "Якщо минуло 120 секунд, а роздрібний продаж все ще не прочитав рядок, то введіть код помилки та вийдіть з роздрібної торгівлі"?
kiltek

@kiltek використовуйте GNU timeout(coreutils) для запуску retailта просто перевіряйте код виходу 124 на час очікування ( timeoutзнищить будь-яку команду, яку ви використовуєте для запуску після встановленого часу)
mr.spuratic

4

Це буде дещо складно, оскільки вам доведеться вступити в управління процесами та сигналізацією. Більш незрозумілим було б рішення з двома сценаріями з використанням відстеження 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, щоб ваш сценарій продовжувався.

Будь-яке з них повинно включати лише невелике навчання для впровадження контролюючого потоку, який ви шукаєте.


використовуючи 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. Він чекає довше, ніж потрібно, коли з'явився запис журналу, в гіршому випадку - 10s.
Альфе

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 . Команда watch відстежує вихід файлу і може бути припинена за допомогою параметра -g, коли вихід змінився.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction

1
Оскільки це працює раз на дві секунди, воно не одразу виходить, як тільки рядок з'являється у файлі журналу. Крім того, він не працює добре, якщо файл журналу дуже великий.
Річард Хансен


1

Алекс, я думаю, що ця допоможе тобі багато.

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

ця команда ніколи не запише в файл реєстрації, але буде мовчати ніжно ...


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"
}

1

tailКоманда може бути і фоновий ідентифікатор процесу огласился 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")

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, якщо знайшли вихід із циклу ...


Таким чином, весь файл потрібно було б переглядати кожен раз, коли щось записується на нього. Не працює добре для файлів журналів.
grawity

1
Ще один спосіб - зробити два сценарії: 1. хвост -f logfile.log | grep -m 1 "Початок роботи сервера"> / tmp / знайдено 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / знайдено; вбити -KILL - $ MYPID
Evengard

Я б хотів, щоб ви відредагували свою відповідь, щоб показати захоплення PID, а потім застосували inotifywait - елегантне рішення, яке легко зрозуміти для тих, хто звик грепати, але потребує більш складного інструменту.
bmike

ПІД того, що ви хотіли б захопити? Я можу спробувати зробити це, якщо ви поясніть трохи більше, що ви хочете
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

-2

як щодо цього:

поки правда; робити, якщо [! -z $ (grep "myRegEx" myLog.log)]; потім розірвати; fi; зроблено

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.