Como manter apenas cada enésima linha de um arquivo

71

Eu tenho um arquivo CSV bastante considerável (75 MB). Estou apenas tentando produzir um gráfico, então realmente não preciso de todos os dados.

Reescrita: eu gostaria de excluir n linhas, manter uma linha, excluir n linhas e assim por diante.

Portanto, se o arquivo estiver assim:

Line 1
Line 2
Line 3
Line 4
Line 5
Line 6

e n = 2, a saída seria:

Line 3
Line 6

Parece que sedé capaz de fazer isso, mas não consegui descobrir como. Um comando bash seria ideal, mas estou aberto a qualquer solução.

Computerish
fonte
2
Deseja realmente as linhas 1, 3, 6, etc., em vez de 1, 4, 7, etc.?
Ilmari Karonen
2
Como é um arquivo CSV, presumo que a primeira linha contenha metadados (por exemplo, nomes de campos). Nesse caso, a pergunta deve ser "a cada enésima linha após a primeira".
31412 iglvzx
7
1, 3, 6 ainda não faz sentido!
Wim
11
Acho que deve ser de 1, 3, 5, a menos que n = 2, é um valor mágica para números triangulares (1, 3, 6, 10, 15, 21, etc)
rjmunro
4
Você pode atualizar sua pergunta para tornar consistente o que está solicitando ("cada enésima linha", "n = 2") e o resultado desejado (Linha 3, Linha 6)? Os futuros leitores ficarão confusos.
9788 Keith Thompson

Respostas:

121
~ $ awk 'NR == 1 || NR % 3 == 0' yourfile
Line 1
Line 3
Line 6

NRA variável (número de registros) é o número de linhas de registros porque o comportamento padrão é a nova linha de RS(separador de registros). padrão e ação são opcionais no formato padrão do awk 'pattern {actions}'. quando damos apenas parte do padrão, awkgrava todos os campos $0para as truecondições do nosso padrão .

Selman Ulug
fonte
8
Graças aos padrões, você não precisa de muito:awk 'NR == 1 || NR % 3 == 0'
Kevin
@selman: Se você gosta da solução de Kevin, considere atualizar sua resposta.
amigos estão dizendo sobre keith thompson
4
Gostaria de explicar por que isso acontece? Dessa forma, se alguém quiser ajustá-lo um pouco, então espero que a sua explicação irá ajudá-los a fazê-lo
Ivo Flipse
Eu descobri que essa abordagem me deixa as linhas 1 e 2 intocadas. Isso é confirmado com o awk 'NR == 1 || NR % 2 == 0' myfile.txt | wc -lresultado de um número ímpar, enquanto o arquivo original tinha um número par de linhas. A resposta do @kev funciona melhor no meu caso de teste.
Daniel Da Cunha
58

sed também pode fazer isso:

$ sed -n '1p;0~3p' input.txt
Line 1
Line 3
Line 6

man sedexplica ~como:

first ~ step Corresponde a cada passo da segunda linha, começando pela linha primeiro. Por exemplo, `` sed -n 1 ~ 2p '' imprimirá todas as linhas com números ímpares no fluxo de entrada, e o endereço 2 ~ 5 corresponderá a cada quinta linha, começando com a segunda. o primeiro pode ser zero; neste caso, sed opera como se fosse igual ao passo. (Esta é uma extensão.)

kev
fonte
6
Você poderia explicar esse comando?
QED
11
@ qed Explicação: 1pimprime a primeira linha, 0~3pimprime todas as terceiras linhas iniciando na linha 3 ( 1pportanto, é necessário imprimir a linha 1). Mas note que o 0~3não é padrão, mas uma extensão GNU sed.
Arkku 22/07/2015
"Esta é uma extensão." Qual versão você estava usando?
213 Victor Victor
Esta resposta me ajudou muito no Windows PowerShell. Ampliei assim: sed -n '1p;0~10p' '.\in.txt' > out.txtpara imprimir o arquivo reduzido em um arquivo de saída.
kimliv
22

O Perl também pode fazer isso:

while (<>) {
    print  if $. % 3 == 1;
}

Este programa imprimirá a primeira linha de sua entrada e a cada terceira linha posteriormente.

Para explicar um pouco, <>é o operador de entrada de linha, que itera sobre as linhas de entrada quando usado em um whileloop como este. A variável especial $.contém o número de linhas lidas até o momento e %é o operador do módulo.

Esse código pode ser escrito de forma ainda mais compacta como uma linha, usando as opções -ne -e:

perl -ne 'print if $. % 3 == 1'  < input.txt  > output.txt

O -eswitch usa um pedaço de código Perl para executar como um parâmetro de linha de comando, enquanto o -nswitch envolve implicitamente o código em um whileloop como o mostrado acima.


Edit: Para obter as linhas 1, 3, 6, 9, ... como no exemplo, em vez das linhas 1, 4, 7, 10, ... como eu assumi que você queria, substitua $. % 3 == 1por $. == 1 or $. % 3 == 0.

Ilmari Karonen
fonte
7

Se você quiser fazer isso com um script Bash, tente:

#!/bin/sh

echo Please enter the file name
read fname
echo Please enter the Nth lines that you want to keep
read n

exec<$fname
value=0
while read line
do
    if [ $(( $value % $n )) -eq 0 ] ; then
        echo -e "$line" >> new_file.txt
    fi
        let value=value+1 
done
echo "Check the 'new_file.txt' that has been created in this directory";

Salve-o como "read_lines.sh" e lembre-se de conceder + x permissões ao arquivo bash.

chmod +x ./read_lines.sh
akarpovsky
fonte
11
Se você fez isso apenas emitir na saída padrão, leia o número de linhas para pular dos argumentos e leia o arquivo da entrada padrão, seria mais simples e mais útil. Você ainda pode criar new_file.txt fazendo ./read_lines.sh > new_file.txt.
Rjmunro 07/07
4

Uma solução no bash puro, que não gera um processo, é:

{ for f in {1..2}; do read line; done;
  while read line; do
    echo $line;
    for f in {1..2}; do read line; done;
  done; } < file

A primeira linha pula 2 linhas no início do arquivo e whileimprime a próxima linha e pula 2 linhas novamente.

Se o seu arquivo for pequeno, é uma maneira muito eficiente de fazer o trabalho, pois não inicia um processo. Quando seu arquivo é grande, seddeve ser usado, pois é mais eficiente no manuseio io do que bash.

jfg956
fonte
1

Uma versão Python (ambos Python 2 e Python 3):

python2 -c "print(''.join(open('file.txt').readlines()[::3]))"

substitua [::3]pelos parâmetros de tamanho inicial, final e da etapa para obter mais controle. Por exemplo, [10:36:5]coloca as linhas 10,15, ..., 35.

Observe que, como readlines()mantém as terminações da linha, a saída dessa chamada pode terminar com uma última linha vazia, a menos que a última linha original seja eliminada pelo tamanho da etapa escolhida.

Também é possível uma versão de fluxo (aqui é emitida somente após o fluxo final):

python -c "import sys;print(''.join(list(sys.stdin)[::3]))" < file.txt
DomTomCat
fonte