Eu tenho um arquivo de texto e tenho um padrão que quero que o grep não corresponda. A coisa é, eu também quero que a linha antes não corresponda.
Meu arquivo:
line 1
line 2
pattern
line 4
E eu tentei cat file | grep -v pattern
, que gera:
line 1
line 2
line 4
Então eu tentei cat file | grep -B 1 pattern
, que resulta:
line 2
pattern
No entanto, quando uso os dois juntos cat file | grep -v -B 1 pattern
, recebo:
line 2
Como posso fazer isso para que a saída seja:
line 1
line 4
Costumo usar apenas ao
grep
extrair linhas únicas de arquivos, então quando preciso realizar edições mais complicadas em um texto, uso outras ferramentas.Todas as soluções aqui assumem que o padrão pode ocorrer várias vezes no texto e removerá as linhas em que ocorre e as linhas imediatamente anteriores a elas. As duas primeiras soluções terão problemas se o padrão corresponder em linhas consecutivas.
Você pode usar
sed
para corresponder a um padrão/pattern/
e permitir que isso acione os comandosN
ed
, que anexa a próxima linha ao buffer e descarta ambos:Como você deseja descartar a linha antes da correspondência do padrão, alimentamos os dados para trás em
sed
, começando com a última linha e avançando em direção ao início do arquivo. Em seguida, revertemos os dados novamente quandosed
terminar.O
tac
utilitário faz parte do GNU coreutils. A maioria dos sistemas não GNU pode usartail -r
no lugar detac
(verifique seutail(1)
manual).Se o padrão corresponder a duas linhas consecutivas, isso não removerá a linha anterior à primeira dessas linhas (já que a primeira linha seria excluída).
Usando o
ed
editor:Isso aplica o comando
g/pattern/ -1,. d
ao conteúdo do arquivo. Esse comando procura cada linha que corresponda apattern
e exclui essa linha e a linha anterior a ela.O comando final
,p
eQ
de edição imprime o arquivo inteiro e sai do editor sem salvar.Se o padrão corresponder a duas linhas consecutivas, isso removerá a linha que se torna anterior à segunda linha após remover a linha anterior à primeira linha.
(Essa última frase estava correta quando a escrevi, mas obviamente é uma frase somente para escrita.)
Também podemos usar
grep
e sua opção não padrão, mas comumente implementada-B
, para nos fornecer os números de linha que precisam ser excluídos. Esses números podem ser convertidos em umsed
script que executamos nos dados originais:O
grep
comando, dado o texto na pergunta, produziria... e o primeiro
sed
comando converte isso nosed
comando de edição2d
seguido por3d
("delete line 2 and 3"). O últimosed
comando no pipeline pega esse script de edição e o aplica ao texto original.Essa variante não tem problemas com linhas consecutivas que correspondem ao padrão, pois usa uma abordagem de 2 passos, primeiro encontrando todas as linhas que devem ser excluídas e, em seguida, excluindo-as (em vez de excluir linhas ao ler o texto pela primeira vez).
Usando qualquer awk com tac, você pode excluir qualquer número de linhas antes de um padrão correspondente:
Basta alterar
c=2
parac=5
ou qualquer número de linhas que você deseja excluir até e incluindo a linha correspondente, por exemplo, para excluir uma linha contendo o número 97 e as 94 linhas anteriores:Agora tente fazer isso com sed em vez de awk :-).
Veja imprimir-com-sed-ou-awk-a-line-following-a-matching-pattern para obter uma explicação deste e de outros idiomas relacionados.
Nota: Este código só funciona se
file
não existirem linhas ou substrings duplicadas de cada linha que correspondam à saída degrep -B1 pattern file
.Por exemplo, se
file
contém as seguintes linhas:E eu uso
grep -B1 pattern file | grep -v "$(cat)" file
a saída não será como você esperava:A melhor maneira de resolver isso é usando a resposta de Kusalananda
Solução (este se aplica apenas aos casos em que não há linhas ou substrings duplicadas como expliquei acima)
Isso
bash
funciona para mim (acho que existem maneiras melhores):Nos
zsh
comandos acima não vai funcionar. Eu não sei por quê. Mas você pode usar:PS Você não precisa usar
cat your_file | grep pattern
isso é redundante. Você deveria usargrep pattern your_file
As soluções de Kusalananda e Ed Morton são as mais simples e práticas, mas exigem a leitura do conteúdo duas vezes ou a leitura do conteúdo inteiro antes de começar. Pipes não podem ser relidos nem são sempre finitos. Uma solução que funciona com qualquer fluxo de texto pode ser algo assim:
Se você quiser fazer com que o número de linhas não impressas seja variável, isso fica um pouco mais complicado:
Nenhuma dessas soluções tem problemas com partidas consecutivas, btw.
Você pode usar
pcregrep
e seuM
modo ultiline:Observe que, se a primeira linha corresponder ao padrão, ela não será removida. Isso pode ser contornado usando:
(a
(...)
volta\n
aparentemente necessária, não sei porque não funciona com\n?.*pattern
ou[\n]?.*pattern
com a versão 8.39 aqui).Usando Raku (anteriormente conhecido como Perl_6)
Essas duas primeiras respostas (acima) basicamente detectam um padrão de duas linhas e o excluem. Assim, as ocorrências consecutivas da palavra
pattern
não são tratadas, nem a ocorrência depattern
na primeira linha. Para ambas as respostas,lines
são lidos de um arquivo e editadosjoin
novamente em\n
novas linhas (já quelines
autochomps por padrão). Em seguida, o regex de duas linhas desejado é procurado e 1).subst
itutado (sem nada, ou seja, deletado) ou 2).split
na regex de duas linhas ejoin
ed a saídaput
.As próximas duas respostas (abaixo) tratam da ocorrência de
pattern
na primeira linha, bem como tratam de ocorrências consecutivas da palavrapattern
. Eles usam o agrupamento[\N* \n]?
no início do Regex:Entrada de amostra:
Saída de amostra (os primeiros 2 exemplos que excluem um Regex de 2 linhas):
Saída de amostra (exemplos 3 e 4 que lidam adicionalmente
pattern
na primeira linha, bem como ocorrências consecutivas depattern
):FYI: A rotina de Raku
lines
é anunciada para agir preguiçosamente, então potencialmente um arquivo pode ser analisado sem ter que ler o arquivo inteiro primeiro. Para comentários sobre a rotina de Rakulines
, veja a URL abaixo.https://speakerdeck.com/util/reading-files-cant-be-this-simple
https://raku.org
Agradecimentos especiais ao usuário @JoL por uma crítica perspicaz dos regexes originais nesta resposta.
Você pode armazenar a linha que contém
pattern
e a linha acima dela em uma variável. Então você pode usar esta variável para grep novamente em seu arquivo.