Substitua várias linhas por uma string quando os números de linha são armazenados em um arquivo

4

Eu tenho um arquivo com alguns milhões de linhas, tudo a mesma coisa. apenas por um exemplo:

Known
Known
Known
Known
Known
Known
...

Eu tenho outro arquivo com alguns milhares de números de linha, por exemplo:

3
5
6
...

Gostaria de saber se existe uma maneira rápida de usar o comando bash para substitua essas linhas por outra string, por exemplo, UnKnown. Com base no exemplo que desejo gerar:

Known
Known
UnKnown
Known
UnKnown
UnKnown
...
    
por Sagi 02.04.2016 / 15:14

3 respostas

3

Uma solução awk :

$ awk 'NR==FNR{a[$1]++;next}
       { 
        if(FNR in a){
            print "UnKnown"
        }
        else{
            print
        }
       }' nums file
Known
Known
UnKnown
Known
UnKnown
UnKnown

Explicação

  • NR==FNR{a[$1]++;next} : NR é o número da linha atual da entrada e FNR do número da linha atual do arquivo atual. Os dois serão iguais apenas enquanto o primeiro arquivo estiver sendo lido. Portanto, essa expressão salvará cada número de linha (o primeiro campo, $1 , do primeiro arquivo) como uma chave na matriz a e, em seguida, passará para a próxima linha.
  • if(FNR in a){ print "UnKnown"} : se o número da linha do arquivo atual estava no primeiro arquivo, imprima "Desconhecido".
  • else {print} : se não, imprima a linha atual.
por 02.04.2016 / 15:29
3

Uma possibilidade é filtrar as linhas pelo awk. Se a lista de linhas a alterar for pequena, passe-a para awk na linha de comando.

awk <original.txt >modified.txt -v lines="$(cat lines-to-change.txt)" '
    BEGIN {split(lines, a); for (i in a) change[a[i]]=1}
    NR in change {$0 = "Un" $0} # or $0 = "UnKnown"
    1
'

Se o número de linhas a alterar for muito pequeno e o arquivo a ser modificado for muito grande, sed poderá ser mais rápido. Com sed, você precisa criar um script contendo a substituição para aplicar a cada linha.

sed "$(<lines-to-change.txt sed 's/$/s:^:Un:/')" <original.txt >modified.txt

Se uma fração significativa de linhas precisar ser alterada, as duas abordagens anteriores serão executadas no limite de comprimento da linha de comando. Aqui está uma abordagem modificada com o awk que lê os dois arquivos em paralelo. Se lines-to-change.txt já estiver classificado, você poderá usar getline n <"lines-to-change.txt" em vez de "sort -n lines-to-change.txt" | getline n .

awk <original.txt >modified.txt '
    BEGIN {"sort -n lines-to-change.txt" | getline n}
    NR==n {$0 = "Un" $0; n = 0; "sort -n lines-to-change.txt" | getline n}
    1
'
    
por 03.04.2016 / 01:10
1

Esta é uma variação da resposta de Gilles para o cenário "se o número de linhas a mudar for pequeno". Em vez de criar uma expressão inline sed, ele cria um script sed enviado por meio do pipeline stdout / stdin para ser lido com -f -. Isso evita problemas com um limite de comprimento da linha de comando. Você poderia, alternativamente, salvar o script sed em um arquivo "temporário" e, em seguida, apontar sed para ele.

A outra variação que estou trazendo é o comando "c" do sed, que diz para substituir a linha selecionada pelo texto fornecido. A sintaxe do comando "c" é um pouco incomum, pois quer uma barra invertida, nova linha e, em seguida, o novo texto.

sed 's/$/c\\nNew String/' line-number-file | sed -f - input-file > output-file

O primeiro comando sed cria um script sed intermediário como entrada para o segundo sed "substituindo" o final da linha ( $ ) pela sequência "c, barra invertida, nova linha, Nova sequência":

3c\
New String
5c\
New String
6c\
New String

Para alterar o texto que está sendo usado como substituto, vá para a primeira seção sed e substitua "New String" pelo que você quiser.

Se você quiser substituir o texto no arquivo de entrada original e seu sed suportar o sinal -i , você poderá alterar o comando para:

sed 's/$/c\\nNew String/' line-number-file | sed -f - -i input-file
    
por 03.04.2016 / 05:18