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