Exclui linhas com base na longa lista de números de linha

3

Eu tenho uma longa lista de números de linha (35389208) que eu não quero no meu arquivo. Por número de linha, quero dizer a linha no meu arquivo (por exemplo, linha 277). Minha lista de números de linha que eu não quero se parece com:

277
278
279
280
289
290
291
292
321
322
....

Qual é a melhor maneira de excluir esses números de linha do meu arquivo? Solução em perl sed ou awk (ou qualquer outra coisa).

    
por bdeonovic 11.06.2014 / 19:59

5 respostas

1

Você pode tentar usar uma regex com sed :

sed '/^[0-9]*$/d' filename.txt

Isso removerá as linhas que contêm apenas números do seu arquivo.

O script Perl a seguir removerá a linha n -th do arquivo input.txt e gerará o restante para stdout . Números de linha podem ser especificados em line_numbers.txt :

#!/usr/bin/perl

my @lines_to_exclude;

open(my $fh_line_numbers, "<", "line_numbers.txt") or die "Failed to open file: $!\n";
while(<$fh_line_numbers>) { 
  chomp; 
  push @lines_to_exclude, $_;
} 
close $fh_line_numbers;

my $linecounter = 1;

open (my $fh_datafile, '<', 'input.txt') or die "Cannot open $filename: $!";

while ( my $line = <$fh_datafile> ) {

  if ( ! ( $linecounter ~~ @lines_to_exclude ) ) {
    print $line;
  }

  $linecounter++;
}

close($fh_datafile);

(o operador ~~ está disponível apenas em perl > = 5.10)

    
por 11.06.2014 / 20:04
1

Se a leitura de todos os números de linha na memória for uma opção, você pode fazer isso com awk :

awk 'FNR == NR { h[$1]; next } !(FNR in h)' line-numbers.txt input.txt

Se você tem memória limitada disponível e seu arquivo line-numbers.txt está numericamente ordenado, você pode fazer assim:

delete-lines.awk

BEGIN {  
  lines_file = "line-numbers.txt"
  if(!(getline n < lines_file)) { 
    print "Unable to open lines file " lines_file > "/dev/stderr" 
    exit 
  } 
} 

FNR != n

FNR == n {
  getline n < lines_file
}

Execute assim:

awk -f delete-lines.awk input.txt

Testando onde line-numbers.txt contém:

277
278
279
280
289
290
291
292
321
322

e input.txt são representados por seq 325 .

Primeiro, com os números de linha na memória:

seq 325 | awk 'FNR == NR { h[$1]; next } !(FNR in h)' line-numbers.txt -

depois, com os números de linha sendo lidos um por vez:

seq 325 | awk -f delete-lines.awk -

Saída nos dois casos (linhas 1 a 274 são omitidas):

.
.
.
275
276
281
282
283
284
285
286
287
288
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
323
324
325
    
por 12.06.2014 / 13:54
0

Para excluir o número no começo da linha

sed 's/^[0-9]*//g' filename.txt
    
por 11.06.2014 / 20:21
0

Isso pode funcionar para você (GNU sed):

sed 's/.*/&d/' line-numbers-to-delete-file | sed -f - file-to-be-shortened

Gere um script do arquivo que contém as linhas a serem excluídas e alimente-o a uma instância de sed usando o arquivo que você deseja encurtar como entrada.

    
por 12.06.2014 / 23:09
0

Observe que, exceto pelo código adicional na tentativa 2, todo o código realmente faz o oposto do que o OP fez. Como você pode ver na tentativa 2, é fácil adaptar os comandos.

Eu tinha um arquivo de texto com 1.108.752 linhas, com cerca de 83 MB de tamanho. Eu queria obter 46.744 linhas, variando entre a 15ª e a 1.108.716ª linha, que é em média a cada 24 linhas.

tl; dr;

A segunda tentativa é mais rápida que a primeira. Terceiro só funciona para menos linhas.

Primeira tentativa (ruim)

Para cada linha que eu quero, sed lê linhas do início do arquivo de texto, mas não as imprime ( -n ). Quando atingir a linha que quero, imprima-a ( p ), depois feche ( q ) em vez de ler até o final do arquivo. Então faça isso novamente para o próximo linenumber.

Obviamente, isso leva um pouco mais de tempo a cada execução, porque sed precisa passar por mais linhas do que antes de cada vez.

Se eu calculei isso direito, no meu caso, isso levaria cerca de 307332472188 passa pelo arquivo de texto geral. Oh meu.

Observe que, para essa abordagem, a ordem das linhas é irrelevante no arquivo linenumbers:

while read line; do
    sed -n "${line}{p;q}" "${INFILE}"
done

Resultados da temporização: 2568.80s user 256.10s system 92% cpu 51:00.37 total . Não é bom.

Segunda tentativa (melhor)

Isso lê os linenumbers do arquivo e acrescenta um p (novamente, para imprimir esta linha). Essa cadeia é canalizada para o próximo sed , que lê de um arquivo ( -f ), que aqui é STDIN escrito como - , que é a saída do primeiro sed , que é realmente o linenumber a ser impresso:

sed 's/$/p/' "${LINENUMS}" | sed -n -f - "${INFILE}"

Resultados da temporização: 146.54s user 0.18s system 100% cpu 2:26.70 total . Muito bom!

Se você quiser não imprimir as linhas do linefile (como OP queria fazer), altere ligeiramente o comando para que os linenumbers sejam d eletizados em vez de < em> p , e imprime todas as outras linhas em vez de excluí-las (-n):

sed 's/$/d/' "${LINENUMS}" | sed -f - "${INFILE}"

Terceira tentativa (mais ruim)

Isso não funcionou para mim porque eu tinha muitas linhas que queria extrair. Deve funcionar para (muito) menos linhas, mas não sei o limite para isso.

Eu tentei criar uma longa string para sed, o que eu esperava que levasse a sed passando pelo arquivo apenas uma vez (!), não imprimindo nada exceto os linenumbers da string:

sed -n "12p;15p;24p;345p;...;12345;" ${INFILE}"

mas isso resultaria em uma string com cerca de 420076 caracteres, que após bombear para sed simplesmente levou a sed: Argument list is too long . O que é compreensível.

    
por 06.04.2016 / 00:22