¿Comando rápido de Unix para mostrar líneas específicas en el medio de un archivo?


207

Intento depurar un problema con un servidor y mi único archivo de registro es un archivo de registro de 20 GB (¡sin marcas de tiempo incluso! ¿Por qué la gente usa System.out.println()como registro? ¿En producción?)

Usando grep, he encontrado un área del archivo que me gustaría ver, línea 347340107.

Aparte de hacer algo como

head -<$LINENUM + 10> filename | tail -20 

... lo que requeriría headleer las primeras 347 millones de líneas del archivo de registro, ¿hay un comando rápido y fácil que volcaría las líneas 347340100 - 347340200 (por ejemplo) a la consola?

actualización Olvidé por completo que grep puede imprimir el contexto alrededor de un partido ... esto funciona bien. ¡Gracias!


Me imagino que grep tiene que buscar en todo el archivo, debe haber una forma menos intensiva de CPU para hacer esto.
ojblass

Respuestas:


70

con GNU-grep podrías decir

grep --context = 10 ...

77
O más específicamente 10 líneas antes: grep -B 10 ... O 10 líneas después: grep -A 10 ...
Boy Baukema

17
Este comando no funciona, debajo de sed -n '<start>, <end> p' está funcionando
Basav

55
En realidad, esto no es lo que desea porque procesará todo el archivo incluso si la coincidencia está en el bit superior. En este punto, un combo cabeza / cola o cola / cabeza es mucho más efectivo.
Sklivvz

3
Esto no satisface la pregunta formulada ya que no ofrece una forma de generar una línea específica , como se le preguntó.
Chris Rasys

1
Esto no es realmente lo que se le preguntó. @matt b, ¿por qué no no acepta esta respuesta?
user1271772

391

Encontré otras dos soluciones si conoces el número de línea pero nada más (no es posible grep):

Suponiendo que necesita las líneas 20 a 40,

sed -n '20,40p;41q' file_name

o

awk 'FNR>=20 && FNR<=40' file_name

66
+1: aunque es posible que desee salir después de imprimir. Puede ofrecer algunos beneficios de rendimiento si el archivo es realmente enorme.
jaypal singh

awk 'NR> = 20 && NR <= 40' nombre_archivo
Sudipta Basak

2
sed -n '20, 40p; 41q 'nombre_archivo para salir entonces.
Snigdha Batra el

1
específicamente, esos son números de línea inicial y final. Si está en un archivo más grande, será '12345678,12345699p'
Code Abominator

1
Además del comentario de @ CodeAbominator, indique 41qque abandone la línea 41.
Brice

116
# print line number 52
sed -n '52p' # method 1
sed '52!d' # method 2
sed '52q;d' # method 3,  efficient on large files 

método 3 eficiente en archivos grandes

forma más rápida de mostrar líneas específicas


Estoy tratando de descubrir cómo adaptar el método 3 para usar un rango en lugar de una sola línea, pero me temo que mi sed-foo no está a la altura.
Xiong Chiamiov

9
@ XiongChiamiov ¿Qué tal sed -n '1,500p; 501q' para imprimir 1-500?
Sam

3
La razón por la que las dos primeras líneas / métodos son menos eficientes es que continúan procesando todas las líneas después de la Línea 52, hasta el final, mientras que el # 3 se detiene después de imprimir la Línea 52.
flow2k

1
Esta respuesta se beneficiaría de explicar lo que hacen todos los argumentos.
Bram Vanroy

25

No, no existe, los archivos no son direccionables en línea.

No hay una forma de tiempo constante para encontrar el inicio de la línea n en un archivo de texto. Debe transmitir a través del archivo y contar nuevas líneas.

Use la herramienta más simple / rápida que tiene para hacer el trabajo. Para mí, usar headtiene mucho más sentido que grep, ya que este último es mucho más complicado. No estoy diciendo " grepes lento", realmente no lo es, pero me sorprendería si fuera más rápido que headen este caso. Eso sería un error head, básicamente.


2
A menos que las líneas tengan un ancho fijo en bytes, no sabe dónde mover el puntero del archivo sin contar nuevos caracteres de línea desde el inicio del archivo.
Joseph Lust

Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación.
exhuma

@exhuma Tienes razón. Reescribí Hace siete años me molesté. :)
Relájese

20

Qué pasa:

tail -n +347340107 filename | head -n 100

No lo probé, pero creo que funcionaría.


No, generalmente la cola tiene un límite de 256 últimos kilobytes o similar, según la versión y el sistema operativo.
Antti Rytsölä

💪 yessire miller
dctremblay

13

Prefiero solo entrar lessy

  • escribiendo 50%para ir a la mitad del archivo,
  • 43210G para ir a la línea 43210
  • :43210 hacer lo mismo

Y cosas como esa.

Aún mejor: presione vpara comenzar a editar (¡en vim, por supuesto!), En esa ubicación. ¡Ahora, tenga en cuenta que vimtiene las mismas combinaciones de teclas!


12

Primero dividí el archivo en unos pocos más pequeños como este

$ split --lines=50000 /path/to/large/file /path/to/output/file/prefix

y luego grep en los archivos resultantes.


acordado, rompa ese registro y cree un trabajo cron para hacerlo correctamente. use logrotate o algo similar para evitar que se vuelvan tan grandes.
Tanj

9

Puede usar el excomando, un editor estándar de Unix (parte de Vim ahora), por ejemplo

  • mostrar una sola línea (por ejemplo, la segunda):

    ex +2p -scq file.txt

    sintaxis de sed correspondiente: sed -n '2p' file.txt

  • rango de líneas (por ejemplo, 2-5 líneas):

    ex +2,5p -scq file.txt

    sintaxis sed: sed -n '2,5p' file.txt

  • desde la línea dada hasta el final (por ejemplo, 5º al final del archivo):

    ex +5,p -scq file.txt

    sintaxis sed: sed -n '2,$p' file.txt

  • múltiples rangos de línea (por ejemplo, 2-4 y 6-8 líneas):

    ex +2,4p +6,8p -scq file.txt

    sintaxis sed: sed -n '2,4p;6,8p' file.txt

Los comandos anteriores se pueden probar con el siguiente archivo de prueba:

seq 1 20 > file.txt

Explicación:

  • +o -cseguido del comando: ejecute el comando (vi / vim) después de leer el archivo,
  • -s - modo silencioso, también utiliza el terminal actual como salida predeterminada,
  • qseguido de -ces el comando para salir del editor (agregar !para hacer forzar el cierre, por ejemplo -scq!).

7

Si su número de línea es 100 para leer

head -100 filename | tail -1

6

Obtener ack

Instalación de Ubuntu / Debian:

$ sudo apt-get install ack-grep

Entonces corre:

$ ack --lines=$START-$END filename

Ejemplo:

$ ack --lines=10-20 filename

De $ man ack:

--lines=NUM
    Only print line NUM of each file. Multiple lines can be given with multiple --lines options or as a comma separated list (--lines=3,5,7). --lines=4-7 also works. 
    The lines are always output in ascending order, no matter the order given on the command line.

1
Esto, para mí, parece el comando con la sintaxis más intuitiva de todas las respuestas aquí.
nzn

Desde la versión 2.999_06 del 10 de enero de 2019, el --linesparámetro se ha eliminado.
Burny

4

sed también necesitará leer los datos para contar las líneas. La única forma en que sería posible un acceso directo sería que hubiera un contexto / orden en el archivo para operar. Por ejemplo, si hubiera líneas de registro antepuestas con una fecha / hora de ancho fijo, etc., podría usar la utilidad look unix para la búsqueda binaria a través de los archivos para fechas / horas particulares


4

Utilizar

x=`cat -n <file> | grep <match> | awk '{print $1}'`

Aquí obtendrá el número de línea donde ocurrió la coincidencia.

Ahora puede usar el siguiente comando para imprimir 100 líneas

awk -v var="$x" 'NR>=var && NR<=var+100{print}' <file>

o puedes usar "sed" también

sed -n "${x},${x+100}p" <file>

Si tiene más de una coincidencia, use: "awk 'NR == 1 {print $ 1}" para la primera coincidencia y así sucesivamente
Ramana Reddy

2

Con esto sed -e '1,N d; M q', imprimirá las líneas N + 1 a M. Esto probablemente sea un poco mejor, grep -Cya que no intenta hacer coincidir las líneas con un patrón.


-eEs opcional aquí.
flow2k

2

Sobre la base de la respuesta de Sklivvz, aquí hay una buena función que uno puede poner en un .bash_aliasesarchivo. Es eficiente en archivos grandes cuando se imprimen cosas desde el frente del archivo.

function middle()
{
    startidx=$1
    len=$2
    endidx=$(($startidx+$len))
    filename=$3

    awk "FNR>=${startidx} && FNR<=${endidx} { print NR\" \"\$0 }; FNR>${endidx} { print \"END HERE\"; exit }" $filename
}

1

Para mostrar una línea desde a <textfile>por su <line#>, simplemente haga esto:

perl -wne 'print if $. == <line#>' <textfile>

Si desea una forma más poderosa de mostrar un rango de líneas con expresiones regulares, no diré por qué grep es una mala idea para hacer esto, debería ser bastante obvio: esta simple expresión le mostrará su rango en un pase único, que es lo que desea cuando se trata de archivos de texto de ~ 20 GB:

perl -wne 'print if m/<regex1>/ .. m/<regex2>/' <filename>

(consejo: si tu expresión regular tiene /, usa algo como en su m!<regex>!lugar)

Esto se imprimiría <filename>comenzando con la línea que coincide <regex1>hasta (e incluyendo) la línea que coincide <regex2>.

No hace falta un asistente para ver cómo algunos ajustes pueden hacerlo aún más poderoso.

Lo último: perl, ya que es un lenguaje maduro, tiene muchas mejoras ocultas para favorecer la velocidad y el rendimiento. Con esto en mente, lo convierte en la opción obvia para una operación de este tipo, ya que se desarrolló originalmente para manejar grandes archivos de registro, texto, bases de datos, etc.


Realmente, no me parece así, ya que cuando se ejecuta un comando perl más complicado que decir, se ejecutan 2+ programas juntos (más abajo en la página), y, creo que realmente lo dices porque escribí más de una explicación que requería que LEÍAS, ya que hay páginas igualmente complejas (o más) en la página que no salieron del agua ...
sheesh

Tenga en cuenta que el usuario solicitó un rango de líneas; sin embargo, su ejemplo puede adaptarse trivialmente.
Sklivvz

0

Puedes probar este comando:

egrep -n "*" <filename> | egrep "<line number>"

0

Fácil con perl! Si desea obtener las líneas 1, 3 y 5 de un archivo, diga / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,3,5]){print}}' < /etc/passwd

1
¿Dices que es fácil con awk, pero lo hiciste en perl?
Prisionero 13 de

0

Me sorprende que solo otra respuesta (de Ramana Reddy) sugiera agregar números de línea a la salida. Lo siguiente busca el número de línea requerido y colorea la salida.

file=FILE
lineno=LINENO
wb="107"; bf="30;1"; rb="101"; yb="103"
cat -n ${file} | { GREP_COLORS="se=${wb};${bf}:cx=${wb};${bf}:ms=${rb};${bf}:sl=${yb};${bf}" grep --color -C 10 "^[[:space:]]\\+${lineno}[[:space:]]"; }

Las respuestas con código solo tienden a marcarse para su eliminación. ¿Podría agregar algún comentario sobre cómo esto resuelve el problema?
Graham
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.