Substituir o intervalo de linhas com o intervalo de linhas (sed ou outros)

3

Eu tenho dois arquivos de texto: file1 e file2 , ambos com várias linhas.

$ cat file1
line one
line two
line three
line four
line five

$ cat file2
line A
line B
line C
line D
line E
line F

Eu gostaria de substituir um intervalo de linhas de file1 (da linha 1_start para a linha 1_end ) com um intervalo de linhas de file2 (da linha 2_start para a linha 2_end ).

Por exemplo, substitua as linhas 2,4 em file1 pelas linhas 3,5 de file2 .

O que eu só poderia fazer até agora é extrair as linhas necessárias de file2 com

$ sed -n 3,5p file2

Mas não ajuda colocá-los em file1 . É possível com sed ? Se não, é possível com uma ferramenta semelhante?

    
por BowPark 06.12.2017 / 11:57

4 respostas

7

sed pode imprimir um determinado intervalo de linhas com algo parecido com isto:

sed -n 'X,Yp' filename

Em que X é a primeira linha de um intervalo e Y é a última linha, ambas inclusivas. -n informa a sed para não imprimir nada, a menos que seja explicitamente instruído a fazê-lo e é isso que o p após o intervalo faz.

Para que você possa chamar isso três vezes, anexando a um arquivo temporário, mova esse arquivo para onde quiser. Você também pode combiná-los usando cat e substituição de processos como neste exemplo mostra (estou usando números de linha que acabei de retirar do ar; $ é a última linha em um arquivo):

cat <(sed -n '1,5p' file1) <(sed -n '10,12p' file2) <(sed -n '9,$p' file1) > file1.tmp && mv file1.tmp file1

Aqui, substituiríamos as linhas 6, 7 e 8 em file1 pelas linhas 10, 11 e 12 de file2 .

Atualização: graças a @MiniMax para apontar que cat e a substituição do processo podem ser evitadas fazendo o seguinte:

{ sed -n '1,5p' file1; sed -n '10,12p' file2; sed -n '9,$p' file1; } > file1.tmp && mv file1.tmp file1

KISS, afinal. :)

    
por 06.12.2017 / 12:07
4

Outra maneira de fazer com sed é usar o comando r , útil se a opção -i inplace tiver que ser usada também

$ sed -n '3,5p; 5q;' f2 | sed -e '2r /dev/stdin' -e '2,4d' f1
line one
line C
line D
line E
line five

$ # if /dev/stdin is not supported
$ sed -n '3,5p; 5q;' f2 > t1
$ sed -e '2r t1' -e '2,4d' f1

Obrigado a don_crissti por lembrar que poderíamos sair assim que as linhas necessárias forem obtidas do arquivo 2.

    
por 06.12.2017 / 12:57
2

Com enormes arquivos de entrada, isso pode ser mais rápido:

# replacing lines m1,m2 from file1 with lines n1,n2 from file2
m1=2; m2=4; n1=3; n2=5
{ head -n $((m1-1)); { head -n $((n1-1)) >/dev/null; head -n $((n2-n1+1));
} <file2; head -n $((m2-m1+1)) >/dev/null; cat; } <file1

explicado aqui , a única diferença são os intervalos de uma linha nesse caso específico.

    
por 06.12.2017 / 15:05
1

Eu comecei a fazer tudo com o Python ultimamente, então aqui está um programa em Python que faz o que você quer:

#!/usr/bin/env python2
# -*- coding: ascii  -*-
"""replace_range.py"""

import sys
import argparse

parser = argparse.ArgumentParser()

parser.add_argument(
    "matchfile",
    help="File in which to replace lines",
)
parser.add_argument(
    "matchrange",
    help="Comma-separated range of Lines to match and replace",
)
parser.add_argument(
    "replacementfile",
    help="File from which to get replacement lines"
)
parser.add_argument(
    "replacementrange",
    help="Comma-separated range of lines from which to get replacement"
)

if __name__=="__main__":

    # Parse the command-line arguments
    args = parser.parse_args()

    # Open the files
    with \
    open(args.matchfile, 'r') as matchfile, \
    open(args.replacementfile, 'r') as replacementfile:

        # Get the input from the match file as a list of strings 
        matchlines = matchfile.readlines()

        # Get the match range (NOTE: shitf by -1 to convert to zero-indexed list)
        mstart = int(args.matchrange.strip().split(',')[0]) - 1
        mend = int(args.matchrange.strip().split(',')[1]) - 1

        # Get the input from the replacement file as a list of strings 
        replacementlines = replacementfile.readlines()

        # Get the replacement range (NOTE: shitf by -1 to convert to zero-indexed list)
        rstart = int(args.replacementrange.strip().split(',')[0]) -1
        rend = int(args.replacementrange.strip().split(',')[1]) - 1

        # Replace the match text with the replacement text
        outputlines = matchlines[0:mstart] + replacementlines[rstart:rend+1] + matchlines[mend+1:]

        # Output the result
        sys.stdout.write(''.join(outputlines))

E aqui está o que parece em ação:

user@host:~$ python replace_range.py file1 2,3 file2 2,4

line one
line B
line C
line D
line four
line five
    
por 06.12.2017 / 13:10