A razão porque
tac file | grep foo | head -n 1
não para na primeira partida é por causa do buffer.
Normalmente, head -n 1sai depois de ler uma linha. Portanto, grepdeve obter um SIGPIPE e sair assim que gravar sua segunda linha.
Mas o que acontece é que, como sua saída não está indo para um terminal, grepele é armazenado em buffer. Ou seja, ele não está gravando até que tenha acumulado o suficiente (4096 bytes no meu teste com o GNU grep).
O que isso significa é que grepnão será encerrado antes de gravar 8192 bytes de dados, portanto, provavelmente algumas linhas.
Com o GNU grep, você pode fazê-lo sair mais cedo usando o --line-bufferedque diz para escrever linhas assim que forem encontradas, independentemente de ir para um terminal ou não. Então, grepsairia na segunda linha que encontrar.
Mas com o GNU de grepqualquer maneira, você pode usar -m 1como o @terdon mostrou, o que é melhor quando ele sai na primeira partida.
Se você grepnão é o GNU grep, então você pode usar sedou awkpreferir. Mas, tac sendo um comando GNU, duvido que você encontre um sistema em tacque grepnão seja o GNU grep.
tac file | sed "/$pattern/!d;q" # BRE
tac file | P=$pattern awk '$0 ~ ENVIRON["P"] {print; exit}' # ERE
Alguns sistemas precisam tail -rfazer a mesma coisa que o GNU tac.
Observe que, para arquivos regulares (que podem ser procurados), tace tail -rsão eficientes porque eles lêem os arquivos para trás, eles não estão apenas lendo o arquivo completamente na memória antes de imprimi-lo para trás (como faria a abordagem sed do @ slm ou tacem arquivos não regulares) .
Em sistemas onde tacnem tail -rexistem nem estão disponíveis, as únicas opções são implementar a leitura reversa manualmente com linguagens de programação como perlou usar:
grep -e "$pattern" file | tail -n1
Ou:
sed "/$pattern/h;$!d;g" file
Mas isso significa encontrar todas as correspondências e imprimir apenas a última.