Imprime tudo entre dois padrões, depois apaga a primeira e última linha da saída resultante [duplicada]

2
otherdata
otherdata
start_data
one
two
three
four
end_data
otherdata
otherdata

A saída resultante deve ser apenas:

one
two
three
four

Isso pareceu um trabalho para sed para mim:

sed -n '/start_data/,/end_data/{1d;$d;p}' myfile

Não funcionou. A primeira linha foi excluída, mas não a última linha! (por nenhuma razão que eu pudesse explicar pela lógica até agora)

OK, então vamos tentar o jeito feio:

sed -n '/start_data/,/end_data/{/start_data\|end_data/!p}' myfile

Justo, isso funciona. Mas eu gostaria de fazer o método mais curto funcionar também, já que a saída resultante irá sempre conter os dois padrões na primeira e última linha, já que estamos apenas extraindo os dados entre.

Por que sed sufoca com a tentativa de combinar as instruções 1d e $d nas chaves?

    
por syntaxerror 18.06.2015 / 02:31

5 respostas

4

Você pode reverter a lógica:

sed '1,/start_data/d;/end_data/,$d'

Isso pressupõe que start_data não esteja na primeira linha. Para contornar isso, se você tiver o GNU sed , poderá fazê-lo:

sed '0,/start_data/d;/end_data/Q'

Esse 0 e Q são específicos do GNU. Q fecha sed sem imprimir o espaço de padrão, de modo que também tornaria mais eficiente, pois não manteria a leitura e descartaria o restante do arquivo como na primeira solução.

    
por 18.06.2015 / 06:37
3

awk parece ser uma boa opção para este problema:

$ awk '/end_data/{f=0;};f{print;};/start_data/{f=1;}' myfile
one
two
three
four

O texto acima usa o sinalizador f para decidir se uma linha deve ser impressa. Quando start_data , o sinalizador é definido como verdadeiro (1). Quando end_data é encontrado, o sinalizador é definido como falso (0). Quando f é verdadeiro, a linha é impressa.

Why does sed choke at the attempt of combining the 1d and $d statements in curly braces?

Não é "sufocante". É só que 1d e $d referem-se à primeira e à última linha do arquivo, não à primeira e à última linha do padrão.

    
por 18.06.2015 / 02:49
3

Bem, isso funciona:

sed -ne/start_data/!d\;:n -e'n;/end_data/q;p;bn' <in

Nem sequer tenta p rint até encontrar /start_pattern/ e a partir desse endereço até à última linha, irá substituir a linha atual com n ext, q it input totalmente se a nova linha extraída corresponder a /end_data/ , ou então p rint. E isso é tudo. A saída é, dados seus dados de amostra:

one
two
three
four

Ele não reconhecerá uma linha como uma correspondência end_data se também corresponder à primeira linha start_data que ocorre na entrada.

    
por 18.06.2015 / 06:52
1

Você já tem uma resposta para sua pergunta; Vou colocar outra maneira de fazer isso usando Perl.

< inputfile perl -0777 -pe 's/^(.*\n)*?start_data.*\n((.*\n)*?)end_data(.*\n)*/$2/'
  • -0777 : faz o slurps do arquivo inteiro de uma só vez, em vez de uma linha no momento
  • -p : coloca um loop while (<>) {[...]} ao redor do script e imprime o arquivo processado
  • -e : lê o script dos argumentos

Divisão do comando Perl:

  • s : afirma para executar uma substituição
  • / : inicia o padrão
  • ^ : corresponde ao início do arquivo
  • (.*\n)*? : corresponde qualquer número de qualquer caractere avidamente dentro da linha atual e uma nova linha, zero ou mais vezes preguiçosamente dentro do arquivo atual (ou seja, corresponde ao menor número de vezes possível, parando quando o seguinte padrão começa a corresponder)
  • start_data.*\n : corresponde a uma string start_data , qualquer número de qualquer caractere avidamente na linha atual e uma nova linha
  • ((.*\n)*?) : agrupa e combina qualquer número de qualquer caractere avidamente dentro da linha atual e uma nova linha, zero ou mais vezes preguiçosamente dentro do arquivo atual (isto é, corresponde ao menor número de vezes possível, parando quando o seguinte padrão começa a corresponder) / li>
  • end_data : corresponde a uma end_data string
  • (.*\n)* : corresponde qualquer número de qualquer caractere avidamente dentro da linha atual e uma nova linha, zero ou mais vezes avidamente dentro do arquivo atual (ou seja, corresponde ao máximo de vezes possível)
  • / : interrompe o padrão / inicia a sequência de substituição
  • $2 : substitui pelo segundo grupo capturado
  • / : interrompe a string de substituição / inicia os modificadores
por 18.06.2015 / 03:46
1

Aqui, deixe-me fazer uma modificação cosmética trivial para o arquivo de entrada fornecido na pergunta:

% cat myfile
red
orange
start_data
one
two
three
four
end_data
yellow
green

Eu simplesmente substituí as linhas otherdata por outros dados distintos, então podemos nos referir a cada linha no arquivo de entrada de forma única, por conteúdo, sem ter que dizer "a primeira linha", desde que isso é aparentemente sujeito a má interpretação, ou "o primeiro otherdata line", que é um pouco detalhado (e, pelo que sei, também pode estar sujeito a interpretações erradas).

Agora, provavelmente a coisa mais próxima que você encontrará em sua primeira tentativa é

% sed -n '/start_data/,/end_data/p' myfile | sed '1d;$d'
one
two
three
four

Sua primeira tentativa ( sed -n '/start_data/,/end_data/{1d;$d;p}' myfile ) "engasga" porque (como John1024 disse) a linha 1 é a red line * e linha $ é a green linha ** . O 1d;$d; não tem efeito porque essas linhas (junto com, de fato, todos das linhas otherdata / colordata) já estão excluídos pelo intervalo /start_data/,/end_data/ .

por 18.06.2015 / 13:32