¿Cómo puedo extraer un rango predeterminado de líneas de un archivo de texto en Unix?


532

Tengo un volcado de SQL de ~ 23000 líneas que contiene varias bases de datos de datos. Necesito extraer una determinada sección de este archivo (es decir, los datos de una sola base de datos) y colocarla en un nuevo archivo. Sé los números de línea de inicio y fin de los datos que quiero.

¿Alguien conoce un comando de Unix (o una serie de comandos) para extraer todas las líneas de un archivo entre la línea 16224 y 16482 y luego redirigirlas a un nuevo archivo?


Como menciona archivos grandes, le sugiero que compruebe el comentario stackoverflow.com/questions/83329/…
sancho.s ReinstateMonicaCellio

Respuestas:


793
sed -n '16224,16482p;16483q' filename > newfile

Del manual de sed :

p : imprime el espacio del patrón (en la salida estándar). Este comando generalmente solo se usa junto con la opción de línea de comandos -n.

n - Si la impresión automática no está desactivada, imprima el espacio del patrón, luego, independientemente, reemplace el espacio del patrón con la siguiente línea de entrada. Si no hay más entradas, sed sale sin procesar más comandos.

q - Salga sedsin procesar más comandos o entradas. Tenga en cuenta que el espacio de patrón actual se imprime si la impresión automática no está desactivada con la opción -n.

y

Las direcciones en un script sed pueden tener cualquiera de las siguientes formas:

número Especificar un número de línea coincidirá solo con esa línea en la entrada.

Se puede especificar un rango de direcciones especificando dos direcciones separadas por una coma (,). Un rango de direcciones coincide con las líneas que comienzan desde donde coincide la primera dirección y continúa hasta que la segunda dirección coincide (inclusive).


3
Tenía curiosidad si esto modifica el archivo original. Hice una copia de seguridad por si acaso y parece que esto NO modificó el original, como se esperaba.
Andy Groff el

@AndyGroff. Para modificar el archivo en su lugar, use el parámetro "-i". De lo contrario, no modificará el archivo.
youri

175
Si, como yo, necesita hacer esto en un archivo MUY grande, ayuda si agrega un comando para salir en la siguiente línea. Entonces es sed -n '16224,16482p;16483q' filename. De lo contrario, sed seguirá escaneando hasta el final (o al menos mi versión lo hace).
wds 01 de

77
La gente de @MilesRout parece preguntarse "¿por qué el voto negativo?" bastante a menudo, tal vez te refieres a "no me importa" en lugar de "a nadie le importa"
Mark

1
@wds: tu comentario bien merece una respuesta que suba a la cima. Puede marcar la diferencia entre el día y la noche.
sancho.s ReinstateMonicaCellio

203
sed -n '16224,16482 p' orig-data-file > new-file

Donde 16224,16482 son el número de línea inicial y el número de línea final, inclusive. Esto es 1 indexado. -nsuprime el eco de la entrada como salida, que claramente no desea; los números indican el rango de líneas para operar el siguiente comando; El comando pimprime las líneas relevantes.


77
En archivos grandes, el comando anterior continuará recorriendo todo el archivo una vez que se haya encontrado el rango deseado. ¿Hay alguna manera de dejar de procesar el archivo una vez que se ha emitido el rango?
Gary

39
Pues bien, a partir de la respuesta en este caso , parece que detenerse en el extremo superior del rango podría lograrse con: sed -n '16224,16482p;16482q' orig-data-file > new-file.
Gary

55
¿Por qué pondrías en un espacio innecesario y luego tienes que citar? (Por supuesto, hacer problemas innecesarios y resolverlos es la esencia de la mitad de la informática, pero quiero decir, además de esa razón ...)
Kaz

92

Muy simple usando cabeza / cola:

head -16482 in.sql | tail -258 > out.sql

usando sed:

sed -n '16482,16482p' in.sql > out.sql

usando awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql

1
La segunda y la tercera opción están bien, pero la primera es más lenta que muchas alternativas porque usa 2 comandos donde 1 es suficiente. También requiere cálculo para obtener el argumento correcto tail.
Jonathan Leffler

3
Vale la pena señalar que para mantener los mismos números de línea que la pregunta, el comando sed debería ser sed -n 16224,16482p' in.sql >out.sqly el comando awk debería serawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz

3
También vale la pena saber que, en el caso del primer ejemplo, head -16482 in.sql | tail -$((16482-16224)) >out.sqlel cálculo se reduce a bash
sibaz

1
El primero con cabeza y cola MUCHO más rápido en archivos grandes que la versión sed, incluso con la opción q agregada. versión head y versión instantánea y sed I Ctrl-C después de un minuto ... Gracias
Miyagi

2
También podría usarse tail -n +16224para reducir la computación
SOFe

35

Puede usar 'vi' y luego el siguiente comando:

:16224,16482w!/tmp/some-file

Alternativamente:

cat file | head -n 16482 | tail -n 258

EDITAR: - Solo para agregar una explicación, usa head -n 16482 para mostrar las primeras 16482 líneas y luego usa tail -n 258 para obtener las últimas 258 líneas de la primera salida.


2
Y en lugar de vi, podría usar ex, eso es vi menos cosas de consola interactiva.
Tadeusz A. Kadłubowski

1
No necesitas el catcomando; headPuede leer un archivo directamente. Esto es más lento que muchas alternativas porque usa 2 comandos (3 como se muestra) donde 1 es suficiente.
Jonathan Leffler

1
@ JonathanLeffler Estás bastante equivocado. Es increíblemente rápido. Extraigo 200k líneas, aproximadamente 1G, de un archivo 2G con 500k líneas, en unos segundos (sin el cat). Otras soluciones necesitan al menos unos minutos. También parece ser la variación más rápida en GNU tail -n +XXX filename | head XXX.
Antonis Christofides

28

Hay otro enfoque con awk:

awk 'NR==16224, NR==16482' file

Si el archivo es enorme, puede ser bueno exitdespués de leer la última línea deseada. De esta manera, no leerá las siguientes líneas innecesariamente:

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file

2
1+ para ahorrar tiempo de ejecución y recursos mediante el uso print; exit. Gracias !
Bernie Reiter

Ligera simplificación del segundo ejemplo:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade

Eso es brillante, gracias @ RobinA.Meade! Edité tu idea en la publicación
fedorqui 'SO deja de dañar'

17
perl -ne 'print if 16224..16482' file.txt > new_file.txt

9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2

6
cat dump.txt | head -16224 | tail -258

debería hacer el truco. La desventaja de este enfoque es que necesita hacer la aritmética para determinar el argumento de la cola y tener en cuenta si desea que el 'intermedio' incluya o no la línea final.


44
No necesitas el catcomando; headPuede leer un archivo directamente. Esto es más lento que muchas alternativas porque usa 2 comandos (3 como se muestra) donde 1 es suficiente.
Jonathan Leffler

@JonathanLeffler Esta respuesta es la más fácil de leer y recordar. Si realmente te importara el rendimiento, no habrías estado usando un shell en primer lugar. Es una buena práctica dejar que herramientas específicas se dediquen a una determinada tarea. Además, la "aritmética" se puede resolver usando | tail -$((16482 - 16224)).
Yeti

6

De pie sobre los hombros de boxxar, me gusta esto:

sed -n '<first line>,$p;<last line>q' input

p.ej

sed -n '16224,$p;16482q' input

Los $medios "última línea", por lo que el primer comando hace que sedimprimir todas las líneas que comienzan con la línea 16224y la segunda marcas comando seddejar de fumar después de imprimir una línea 16428. ( No parece necesario agregar 1para el qrango en la solución de boxxar).

Me gusta esta variante porque no necesito especificar el número de línea final dos veces. Y medí que el uso $no tiene efectos perjudiciales en el rendimiento.



3

Rápido y sucio:

head -16428 < file.in | tail -259 > file.out

Probablemente no sea la mejor manera de hacerlo, pero debería funcionar.

Por cierto: 259 = 16482-16224 + 1.


Esto es más lento que muchas alternativas porque usa 2 comandos donde 1 es suficiente.
Jonathan Leffler

3

Escribí un programa de Haskell llamado splitter que hace exactamente esto: leer mi publicación de blog de lanzamiento .

Puede usar el programa de la siguiente manera:

$ cat somefile | splitter 16224-16482

Y eso es todo lo que hay que hacer. Necesitarás Haskell para instalarlo. Sólo:

$ cabal install splitter

Y ya terminaste. Espero que este programa te sea útil.


¿ splitterSolo lee desde la entrada estándar? En cierto sentido, no importa; el catcomando es superfluo si lo hace o no. Utilice splitter 16224-16482 < somefileo (si toma argumentos de nombre de archivo) splitter 16224-16482 somefile.
Jonathan Leffler

3

Incluso podemos hacer esto para verificar en la línea de comando:

cat filename|sed 'n1,n2!d' > abc.txt

Por ejemplo:

cat foo.pl|sed '100,200!d' > abc.txt

66
No necesita el catcomando en ninguno de estos; sedes perfectamente capaz de leer archivos por sí solo, o puede redirigir la entrada estándar de un archivo.
Jonathan Leffler

3

Usando ruby:

ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf

2

Estaba a punto de publicar el truco de cabeza / cola, pero en realidad probablemente solo dispararía emacs. ;-)

  1. esc- xgoto-line ret16224
  2. marca ( ctrl- space)
  3. esc- xgoto-line ret16482
  4. esc-w

abra el nuevo archivo de salida, ctl-y guardar

A ver qué pasa.


44
Emacs no funciona muy bien en archivos muy grandes en mi experiencia.
Greg Mattes

¿Puedes ejecutar eso como una acción programada, o es solo una opción interactiva?
Jonathan Leffler

2

Yo usaría:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR contiene el número de registro (línea) de la línea que se lee desde el archivo.


2

Quería hacer lo mismo desde un script usando una variable y lo logré poniendo comillas alrededor de la variable $ para separar el nombre de la variable de la p:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

Quería dividir una lista en carpetas separadas y encontré la pregunta inicial y respondí un paso útil. (el comando dividido no es una opción en el sistema operativo anterior al que tengo que transferir el código).


1

Escribí un pequeño script bash que puede ejecutar desde su línea de comando, siempre que actualice su RUTA para incluir su directorio (o puede colocarlo en un directorio que ya está contenido en la RUTA).

Uso: $ pinch nombre_archivo inicio-línea final-línea

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0

1
Esto es más lento que muchas alternativas porque usa 2 comandos donde 1 es suficiente. De hecho, lee el archivo dos veces debido al wccomando, que desperdicia el ancho de banda del disco, especialmente en archivos de gigabytes. En todo tipo de formas, esto está bien documentado, pero también es un exceso de ingeniería.
Jonathan Leffler

1

Esto podría funcionar para usted (GNU sed):

sed -ne '16224,16482w newfile' -e '16482q' file

o aprovechando bash:

sed -n $'16224,16482w newfile\n16482q' file

1

Usando ed:

ed -s infile <<<'16224,16482p'

-ssuprime la salida de diagnóstico; Los comandos reales están en una cadena aquí. Específicamente, 16224,16482pejecuta el pcomando (imprimir) en el rango de dirección de línea deseado.


0

El -n en las respuestas aceptadas funciona. Aquí hay otra forma en caso de que esté inclinado.

cat $filename | sed "${linenum}p;d";

Esto hace lo siguiente:

  1. canalice el contenido de un archivo (o alimente el texto como desee).
  2. sed selecciona la línea dada, la imprime
  3. d es necesario para eliminar líneas, de lo contrario sed asumirá que todas las líneas eventualmente se imprimirán. es decir, sin la d, obtendrá todas las líneas impresas por la línea seleccionada impresa dos veces porque tiene la parte $ {linenum} p solicitando que se imprima. Estoy bastante seguro de que -n básicamente está haciendo lo mismo que d aquí.

3
la nota cat file | sedestá mejor escrita comosed file
fedorqui 'SO deja de dañar'

Además, esto solo imprime una línea, mientras que la pregunta es sobre un rango de ellos.
fedorqui 'SO deja de dañar'

0

Como estamos hablando de extraer líneas de texto de un archivo de texto, le daré un caso especial en el que desea extraer todas las líneas que coincidan con un patrón determinado.

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

Imprimirá la línea [Datos] y el resto. Si desea el texto de la línea 1 al patrón, escriba: sed -n '1, / Data / p' myfile. Además, si conoce dos patrones (es mejor que sea único en su texto), tanto la línea inicial como la final del rango se pueden especificar con coincidencias.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.