¿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!

mate b
fuente
Me imagino que grep tiene que buscar en todo el archivo, debe haber una forma menos intensiva de CPU para hacer esto.
ojblass
Consulte también stackoverflow.com/questions/6022384/…
flow2k

Respuestas:

70

con GNU-grep podrías decir

grep --context = 10 ...

fuente
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
Sklivvz
fuente
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

CMI
fuente
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.

relajarse
fuente
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.

itsmatt
fuente
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!

sehe
fuente
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.

Luka Marinko
fuente
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!).
kenorb
fuente
7

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

head -100 filename | tail -1
Roopa
fuente
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.
Odeyin
fuente
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

pixelbeat
fuente
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>
Ramana Reddy
fuente
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.

mweerden
fuente
-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
}
Keithel
fuente
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.

osirisgothra
fuente
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>"
Fritz Dodoo
fuente
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
Dagelf
fuente
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:]]"; }
anguila ghEEz
fuente
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