Cómo mantener solo cada enésima línea de un archivo

71

Tengo un archivo CSV bastante considerable (75 MB). Solo estoy tratando de producir un gráfico, así que realmente no necesito todos los datos.

Reescritura: me gustaría eliminar n líneas, luego mantener una línea, luego eliminar n líneas, y así sucesivamente.

Entonces, si el archivo se ve así:

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

yn = 2, entonces la salida sería:

Line 3
Line 6

Parece que sedpodría hacer esto, pero no he podido entender cómo. Un comando bash sería ideal, pero estoy abierto a cualquier solución.

Computarizado
fuente
2
¿Realmente quiere líneas 1, 3, 6, etc., en lugar de 1, 4, 7, etc.?
Ilmari Karonen
2
Como se trata de un archivo CSV, supongo que la primera línea contiene metadatos (es decir, nombres de campo). Si es así, la pregunta debería ser "cada enésima línea después de la primera".
iglvzx
77
1, 3, 6 todavía no tiene sentido!
wim
1
Supongo que debería ser 1, 3, 5 a menos que n = 2 sea un valor mágico para números triangulares (1, 3, 6, 10, 15, 21, etc.)
rjmunro
44
¿Puede actualizar su pregunta para que lo que está pidiendo ("cada enésima línea", "n = 2") y su salida deseada (Línea 3, Línea 6) sean coherentes? Los futuros lectores se confundirán.
Keith Thompson

Respuestas:

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

NRLa variable (número de registros) es el número de registros de líneas porque el comportamiento predeterminado es una nueva línea para RS(separador de registros). El patrón y la acción son opcionales en el formato predeterminado de awk 'pattern {actions}'. cuando damos solo una parte del patrón, awkescribe todos los campos $0para las truecondiciones de nuestro patrón .

Selman Ulug
fuente
8
Gracias a los valores predeterminados, ni siquiera necesita tanto:awk 'NR == 1 || NR % 3 == 0'
Kevin
@selman: Si le gusta la solución de Kevin, puede considerar actualizar su respuesta.
Keith Thompson
44
¿Te importaría explicar por qué lo hace? De esa manera, si alguien quiere modificarlo un poco, entonces espero que su explicación lo ayude a hacerlo
Ivo Flipse
Descubrí que este enfoque me deja sin tocar las líneas 1 y 2. Esto se confirma con el awk 'NR == 1 || NR % 2 == 0' myfile.txt | wc -lresultado de un número impar mientras que el archivo original tenía un número par de líneas. La respuesta @kev funciona mejor en mi caso de prueba.
Daniel Da Cunha
58

sed También puede hacer esto:

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

man sedexplica ~como:

primer ~ paso Haga coincidir cada línea de paso comenzando con la línea primero. Por ejemplo, `` sed -n 1 ~ 2p '' imprimirá todas las líneas impares en la secuencia de entrada, y la dirección 2 ~ 5 coincidirá con cada quinta línea, comenzando con la segunda. primero puede ser cero; en este caso, sed opera como si fuera igual al paso. (Esta es una extensión).

kev
fuente
66
¿Podría explicar este comando?
qed
1
@qed Explicación: 1pimprime la primera línea, 0~3pimprime cada tercera línea a partir de la línea 3 (por lo 1ptanto, se requiere para imprimir la línea 1). Pero tenga en cuenta que 0~3no es estándar sino una extensión sed de GNU.
Arkku
"Esta es una extensión". ¿Qué versión estás / estabas usando?
Victor
Esta respuesta me ayudó mucho para Windows PowerShell. Lo amplié así: sed -n '1p;0~10p' '.\in.txt' > out.txtpara imprimir el archivo reducido en un archivo de salida.
kimliv
22

Perl también puede hacer esto:

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

Este programa imprimirá la primera línea de su entrada, y cada tercera línea después.

Para explicarlo un poco, <>es el operador de entrada de línea, que itera sobre las líneas de entrada cuando se usa en un whilebucle como este. La variable especial $.contiene el número de líneas leídas hasta ahora, y %es el operador de módulo.

Este código se puede escribir de manera aún más compacta como una línea, utilizando los interruptores -ny -e:

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

El -econmutador toma una parte del código Perl para ejecutarse como un parámetro de línea de comando, mientras que el -nconmutador envuelve implícitamente el código en un whilebucle como el que se muestra arriba.


Editar: para obtener realmente las líneas 1, 3, 6, 9, ... como en el ejemplo, en lugar de las líneas 1, 4, 7, 10, ... como supuse por primera vez que quería, reemplace $. % 3 == 1con $. == 1 or $. % 3 == 0.

Ilmari Karonen
fuente
7

Si quieres hacerlo con un script Bash puedes probar:

#!/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";

Guárdelo como "read_lines.sh" y recuerde dar permisos + x al archivo bash.

chmod +x ./read_lines.sh
akarpovsky
fuente
1
Si hiciera esto simplemente emitir en salida estándar, lea el no de líneas para omitir los argumentos y lea el archivo desde la entrada estándar, sería más simple y más útil. Todavía podría hacer new_file.txt haciendo ./read_lines.sh > new_file.txt.
rjmunro
4

Una solución en bash puro, que no genera un proceso es:

{ 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

La primera línea omite 2 líneas al comienzo del archivo, y luego whileimprime la siguiente línea y omite 2 líneas nuevamente.

Si su archivo es pequeño, esta es una forma muy eficiente de hacer el trabajo, ya que no inicia un proceso. Cuando su archivo es grande, seddebe usarse ya que es más eficiente en el manejo de io que bash.

jfg956
fuente
1

Una versión de Python (tanto Python 2 como Python 3):

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

reemplace [::3]con parámetros de inicio, finalización y tamaño de paso para obtener más control. Por ejemplo, [10:36:5]pone las líneas 10,15, ..., 35.

Tenga en cuenta que, dado que readlines()mantiene las terminaciones de línea, la salida de esta llamada puede terminar con una última línea vacía, a menos que la última línea original se elimine por el tamaño de paso elegido.

También es posible una versión de transmisión (aquí solo se muestra después de la transmisión terminada):

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