Делайте хвост -F до совпадения с рисунком

Я хочу сделать хвост -F для файла до совпадения с шаблоном. Я нашел способ с помощью awk, но ИМХО моя команда не совсем чистая. Проблема в том, что мне нужно сделать это только в одной строке из-за некоторых ограничений.

tail -n +0 -F /tmp/foo | \
awk -W interactive '{if ($1 == "EOF") exit; print} END {system("echo EOF >> /tmp/foo")}'

Хвост будет блокироваться, пока EOF не появится в файле. Это работает довольно хорошо. Блок END является обязательным, потому что "выход" awk не выходит сразу. Это заставляет awk вычислить блок END перед выходом. Блок END зависает при вызове чтения (из-за tail), поэтому последнее, что мне нужно сделать, - это написать еще одну строку в файле, чтобы принудительно завершить выполнение tail.

Кто-нибудь знает лучший способ сделать это?

10 ответов

Решение

Попробуй это:

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

Вся командная строка завершится, как только строка "EOF" будет видна в /tmp/foo.

Есть один побочный эффект: хвостовой процесс будет запущен (в фоновом режиме), пока что-нибудь не будет записано в /tmp/foo.

Используйте опцию tail --pid, и tail остановится, когда оболочка умрет. Не нужно добавлять лишние к хвостовому файлу.

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

У меня нет результатов с решением:

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

Существует некоторая проблема, связанная с буфером, потому что, если к файлу не добавлено больше строк, sed не будет читать входные данные. Итак, проведя немного больше исследований, я придумал это:

sed '/EOF/q' <(tail -n 0 -f /tmp/foo)

Сценарий находится в https://gist.github.com/2377029

Это то, в чем Tcl довольно хорош. Если следующим является "tail_until.tcl",

#!/usr/bin/env tclsh

proc main {filename pattern} {
    set pipe [open "| tail -n +0 -F $filename"]
    set pid [pid $pipe]
    fileevent $pipe readable [list handler $pipe $pattern]
    vwait ::until_found
    catch {exec kill $pid}
}

proc handler {pipe pattern} {
    if {[gets $pipe line] == -1} {
        if {[eof $pipe]} {
            set ::until_found 1
        }
    } else {
        puts $line
        if {[string first $pattern $line] != -1} {
            set ::until_found 1
        }
    }
}

main {*}$argv

Тогда вы бы сделали tail_until.tcl /tmp/foo EOF

Это работает для вас?

tail -n +0 -F /tmp/foo | sed '/EOF/q'

Я предполагаю, что "EOF" - это шаблон, который вы ищете. sed команда завершается, когда находит его, а это означает, что tail должен выйти в следующий раз, когда он пишет.

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


Злоба о Баш

Как боялись, bash (по крайней мере, на MacOS X, но, вероятно, везде) - это оболочка, которая думает, что ей нужно зависать в ожидании tail закончить, хотя sed уволиться. Иногда - чаще, чем мне нравится - я предпочитаю поведение старой доброй оболочки Bourne, которая была не такой умной и потому реже угадывалась, чем Bash. dribbler это программа, которая выводит сообщения по одному в секунду (в данном примере '1: Hello' и т. д.), и вывод выводится на стандартный вывод. В Bash эта последовательность команд висит, пока я не сделал 'echo pqr >>/tmp/foo'в отдельном окне.

date
{ timeout -t 2m dribbler -t -m Hello; echo EOF; } >/tmp/foo &
echo Hi
sleep 1   # Ensure /tmp/foo is created
tail -n +0 -F /tmp/foo | sed '/EOF/q'
date

К сожалению, я не сразу вижу возможность контролировать это поведение. Я нашел shopt lithist, но это не связано с этой проблемой.

Ура для Korn Shell

Я отмечаю, что когда я запускаю этот скрипт с использованием оболочки Korn, он работает так, как я ожидал - оставляя tail таится вокруг, чтобы быть убитым как-то. То, что работает, есть 'echo pqr >> /tmp/fooпосле второй даты команда завершена.

Вот расширенная версия решения Джона, которая использует sed вместо grep, так что вывод tail переходит к stdout:

sed -r '/EOF/q' <( exec tail -n +0 -f /tmp/foo ); kill $! 2> /dev/null

Это работает, потому что sed создается перед tail, поэтому $! держит PID хвоста

Основное преимущество этого решения по сравнению с решениями sh -c заключается в том, что уничтожение sh, по-видимому, приводит к выводу чего-либо на вывод, например, "прекращено", что нежелательно.

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

Здесь главная проблема с $$, Если вы запустите команду как есть, $$ устанавливается не в sh, а в PID текущей оболочки, в которой выполняется команда.

Чтобы убить работу, вам нужно изменить kill $$ в kill \$$

После этого вы можете безопасно избавиться от --pid=$$ перешел к хвостовой команде.

Подводя итог, следующее будет работать просто отлично:

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

При желании вы можете пройти -n в sed молчать:)

Убить висячего tail процесс, а также вы можете выполнить tail команда в контексте замены процесса (Bash), которая впоследствии может быть уничтожена, как если бы это был фоновый процесс. (Код взят из Как прочитать одну строку из tail -f через конвейер, а затем завершить?).

: > /tmp/foo
grep -m 1 EOF <( exec tail -f /tmp/foo ); kill $! 2> /dev/null
echo EOF > /tmp/foo  # terminal window 2

В качестве альтернативы вы можете использовать именованный канал.

(
: > /tmp/foo
rm -f pidfifo
mkfifo pidfifo
sh -c '(tail -n +0 -f /tmp/foo & echo $! > pidfifo) | 
{ sed "/EOF/ q" && kill $(cat pidfifo) && kill $$ ;}'
)

echo EOF > /tmp/foo  # terminal window 2

Готов к использованию для tomcat =

sh -c 'tail -f --pid = $$ catalina.out | {grep -i -m 1 "Запуск сервера в" && kill $$;}'

для вышеуказанного сценария:

sh -c 'tail -f   --pid=$$ /tmp/foo | { grep -i -m 1 EOF && kill $$ ;}'
tail -f <filename> | grep -q "<pattern>"
Другие вопросы по тегам