Remova as linhas baseadas no padrão, mas mantendo as primeiras n linhas correspondentes

6

Eu preciso remover linhas de um arquivo de texto com base no padrão, mas preciso manter as primeiras n linhas que correspondem ao padrão.

Entrada

% 1 
% 2
% 3
% 4
% 5
text1
text2
text3

saída

%1
%2
text1
text2
text3

Eu usei o arquivo sed /^%/d , mas ele exclui todas as linhas que começam com%, sed 3,/^%/d também não funciona. Eu preciso manter as primeiras n linhas do padrão e excluir o resto

    
por Jana 17.01.2013 / 22:51

5 respostas

10

Se você quiser excluir todas as linhas que começam com% put preservando as duas primeiras linhas de entrada, faça o seguinte:

sed -e 1,2b -e '/^%/d'

Embora o mesmo seja mais legível com awk :

awk 'NR <= 2 || !/^%/'

Ou, se você estiver após o desempenho:

{ head -n 2; grep -v '^%'; } < input-file

Se você deseja preservar as duas primeiras linhas que correspondem ao padrão, embora elas possam não ser as primeiras da entrada, awk certamente seria a melhor opção:

awk '!/^%/ || ++n <= 2'

Com sed , você pode usar truques como:

sed -e '/^%/!b' -e 'x;/xx/{h;d;}' -e 's/^/x/;x'

Ou seja, use o espaço de espera para contar o número de ocorrências dos padrões correspondidos até o momento. Não é muito eficiente ou legível.

    
por 18.01.2013 / 01:00
3

Eu tenho medo sed sozinho é um pouco simples demais para isso (não que isso seria impossível, um pouco complicado - veja por exemplo sed sokoban para o que pode ser feito).

Que tal awk ?

#!/bin/awk -f
BEGIN { c = 0; }
{
    if (/^%/) {
        if (c++ < 3) {
            print;
        }
    } else {
        print;
    }
}

Se você pode confiar em usar BASH (que suporta expressões regulares), o awk acima pode ser traduzido para:

#!/bin/bash -
c=0
while IFS= read -r line; do
    if [[ $line =~ ^% ]]; then
        if ((c++ < 3)); then
            printf '%s\n' "$line"
        fi
    else
        printf '%s\n' "$line"
    fi
done

Você também pode usar sed ou grep para fazer a correspondência de padrões em vez do operador =~ .

    
por 17.01.2013 / 23:15
2

Uma solução Perl one-liners :

# in-place editing
perl -i -pe '$.>2 && s/^%.*//s' filename.txt

# print to the standard output
perl -ne '$.>2 && /^%/ || print' filename.txt
    
por 22.01.2013 / 20:40
1
tr '\n' ';' < input | sed 's/% /##/3g' | tr ';' '\n' | sed '/##/d'

Substitui caracteres de nova linha por ';' para obter uma string de linha única, então todas as primeiras ocorrências do padrão para ## marcando com sed / s / pattern / ## / 3g '(substituir da terceira para a última ocorrência do padrão na linha), foram alteradas de volta'; ' para '\ n' e finalmente removeu as linhas marcadas.

    
por 18.01.2013 / 00:19
1
sed '/^%/{
3,$d}' '% 1 
% 2
% 3
% 4
% 5
text1
text2
text3'

Uma maneira de remover as linhas extras.

Editar: minha resposta funciona nas mesmas condições que Stephane Chazelas se as% linhas não ocorrerem primeiro, não funcionará.

Sniper nerd.

sed -n '/^% [^12]*$/!{
/^% [12][[:digit:]]\{1,\}/n
p}' file.txt

Funcionará independentemente de onde a string % number é encontrada no fluxo. Qualquer linha que comece com % e termine com qualquer número de caracteres além de 1 ou 2 , que negamos. Esse endereço corresponde a qualquer coisa além de /% [A-Za-z3-9]*/ deixando um ponto cego. Números entre 10 e 29 serão impressos ainda. Então, aninhamos um segundo endereço para corresponder a esse intervalo e pular a linha.

Mas o awk ainda seria melhor.

    
por 18.01.2013 / 01:02