Я хочу проследить несколько файлов журнала и вывести входящие строки в один канал. Однако, делая это без особого размышления (например, tail -F), можно получить ломаные линии: например, строки ABC\n и XYZ\n из двух журналов могут смешаться и стать ABXYZ\nC\n .

Вот пример:

$ >a >b
$ (echo -n a >>a; sleep 2; echo A >>a) &
$ (echo -n b >>b; sleep 2; echo B >>b) &
$ tail -Fq a b

В идеале это производит aA\n и bB\n . В действительности получается что-то вроде abA\nB\n .

Как вывести эти строки, чтобы они не перепутались?

Вот некоторые вещи, которые я пробовал

  • Вместо того, чтобы использовать один tail -Fq , я попробовал отдельные экземпляры tail для каждого файла:

    $ (trap 'kill 0' EXIT; tail -F a & tail -F b & wait)
    

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

  • Используйте отдельные экземпляры и используйте grep для буферизации каждой строки.

    $ (trap 'kill 0' EXIT; tail -F a | grep -F '' & tail -F b | grep -F '' & wait)
    

    Это похоже на работу. Однако я не уверен, насколько это долговечно. Я думаю, что он имеет те же ограничения, которые обсуждались в этом вопросе: является ли эхо атомарным при написании отдельных строк

    (Кроме того, есть ли лучший способ сделать то, что здесь делает grep -F '' ?)

1 ответ1

0

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

Это очень похоже на второй пример вопроса, но используется команда grep -F '' --line-buffered . grep -F здесь можно заменить на fgrep для краткости. Что касается grep -F / fgrep и обычного регулярного выражения grep, использование фиксированной строки grep немного быстрее, чем что-то вроде grep ^ --line-buffered для той же цели.

Собирая это вместе, многострочная версия:

(
trap 'kill 0' EXIT
tail -F a | fgrep '' --line-buffered &
tail -F b | fgrep '' --line-buffered &
wait
)

Subshell ( ) может не потребоваться, если это входит в сценарий оболочки. Чтобы превратить его в однострочник, избавьтесь от разрывов строк и поставьте точки с запятой (;) в конце строк, которые не заканчиваются амперсандом (&).

Решение в глубине

На самом деле есть две проблемы, которые это решает:

Во-первых, tail -F будет потреблять и выводить байты в файлах так, как они их видят, не дожидаясь конца строки. Так оно и есть, и tail настоящее время не может изменить это. Поэтому мы не можем выполнить tail -Fq a b и вместо этого должны использовать отдельные процессы для каждого файла.

Во-вторых, после выполнения tail -F для каждого файла проблема остается в том, что выходные данные могут быть перепутаны в буфере канала. Поскольку tail очищается в произвольных байтах, а не в целых строках, для этого есть достаточные причины. Указание stdbuf -oL для строки буфера tail не меняет этого, так как tail, кажется, перезаписывает это.

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

Разнообразный

Чтобы объяснить, что делает trap 'kill 0' EXIT ... wait , нужно предотвратить процессы tail -F которые могут остаться позади при использовании чего-то вроде Ctrl- C .

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