Comando para mostrar las primeras y últimas líneas de un archivo

23

Tengo un archivo con muchas filas, y cada fila tiene una marca de tiempo al comienzo, como

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

Por lo tanto, con frecuencia verifico 2 cosas de este archivo de registro.

  1. Las primeras filas, que tienen las condiciones globales y la hora de inicio también se dan.
  2. Últimas filas, que tiene el estado de salida con alguna otra información.

¿Hay algún comando único rápido y útil que me permita mostrar solo las primeras y últimas líneas de un archivo?

mtk
fuente
2
¿Cuáles son las condiciones globales y no te head and tailfuncionan?
Margarita
Esa es la parte de mi archivo de registro. Intentaba ser elaborada. Puedes ignorar eso.
mtk
Tu solución me parece bien. Si desea más comodidad, conviértalo en una función de shell (incluso un alias podría funcionar).
vonbrand
@vonbrand El problema es que no lo séN
Bernhard
@Bernhard, no soy un sed(1)experto, pero hay formas de guardar cosas para usarlas más tarde. Quizás valga la pena mirar allí. OTOH, probablemente prepararía un script Perl (o lo que sea) para hacerlo si se usa con frecuencia, ya que estoy más familiarizado con eso.
vonbrand

Respuestas:

12

Puedes usarlo sedo awkhacerlo con un solo comando. Sin embargo perderás a la velocidad, causa sedy awktendrá que ejecutar a través de todo el archivo de todos modos. Desde el punto de vista de la velocidad, es mucho mejor hacer una función o cada vez una combinación de tail+ head. Esto tiene el inconveniente de no funcionar si la entrada es una tubería, sin embargo, puede usar la sustitución de proceso, en caso de que su shell lo admita (vea el ejemplo a continuación).

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"
}

y simplemente lanzarlo como

first_last "/path/to/file_to_process"

para proceder con la sustitución del proceso (bash, zsh, ksh como shells solamente):

first_last <( command )

PD. incluso puede agregar un greppara verificar si existen sus "condiciones globales".

prisa
fuente
-n 10es el predeterminado, no?
l0b0
@ l0b0 sí, es el valor predeterminado. -n 10No es necesario aquí.
prisa el
20

@rush tiene razón acerca de que usar head + tail es más eficiente para archivos grandes, pero para archivos pequeños (<20 líneas), algunas líneas pueden salir dos veces.

{ head; tail;} < /path/to/file

sería igualmente eficiente, pero no tendría el problema anterior.

Stéphane Chazelas
fuente
A diferencia de la solución rush, esto no funciona en un shell POSIX.
Marco
2
@Marco ¿Eh? Aquí solo se utilizan construcciones POSIX. ¿Qué ves que sale mal?
Gilles 'SO- deja de ser malvado'
2
@Gilles Perdí el espacio: {head; tail;} < filefunciona en zsh pero falla en sh. { head; tail;} < filesiempre funciona Perdón por el ruido.
Marco
@Marco, si hubiera problemas con eso, sería con head, no con el shell. POSIX requiere headdejar el cursor en el archivo justo después de esas 10 líneas para los archivos normales. Podría surgir un problema para headimplementaciones que no son POSIX (versiones muy antiguas de GNU head solían ser no conformes en ese caso, pero estamos hablando de décadas) o si el archivo no es buscable (como el nombre de pipe o socket, pero luego el otra solución tendría el mismo problema).
Stéphane Chazelas
1
@FCTW,sudo sh -c '{ head; tail;} < /path/to/file'
Stéphane Chazelas
9

La { head; tail; }solución no funcionaría en tuberías (o sockets u otros archivos no buscables) porque headpodría consumir demasiados datos a medida que se leen por bloques y no puede buscar en una tubería, dejando el cursor dentro del archivo más allá de lo tailque significa para seleccionar.

Por lo tanto, podría usar una herramienta que lea un carácter a la vez como el del shell read(aquí usando una función que toma el número de líneas de cabecera y cola como argumentos).

head_tail() {
  n=0
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  done
  tail -n "${2-$1}"
}
seq 100 | head_tail 5 10
seq 20 | head_tail 5

o implementar tailen awk por ejemplo como:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
    {l[NR%t]=$0}
    NR<=h
    END{
      n=NR-t+1
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]
    }'
}

Con sed:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'
}

(aunque tenga en cuenta que algunas sedimplementaciones tienen una limitación baja en el tamaño de su espacio de patrones, por lo que fallarían para valores grandes del número de líneas de cola).

Stéphane Chazelas
fuente
4

Usando la bashsustitución del proceso, puede hacer lo siguiente:

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

Tenga en cuenta que no se garantiza que las líneas estén en orden, aunque para archivos de más de 8 KB, es muy probable que lo estén. Este límite de 8kB es el tamaño típico del búfer de lectura, y está relacionado con la razón por la | {head; tail;}que no funciona para archivos pequeños.

El cat >/dev/nulles necesario para mantener headvivo el oleoducto. De teelo contrario, se cerrará antes, y aunque obtendrá la salida tail, será desde algún lugar en el medio de la entrada, en lugar del final.

Finalmente, ¿por qué, en >/dev/nulllugar de, digamos, mudarse taila otro |? En el siguiente caso:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

headLa salida estándar se alimenta a la tubería en taillugar de a la consola, que no es lo que queremos en absoluto.

Jander
fuente
Cuando head o tail terminan de escribir la salida que desean, cierran su stdin y salen. De ahí viene el SIGPIPE. Normalmente esto es algo bueno, están descartando el resto de la producción, por lo que no hay razón para que el otro lado de la tubería continúe gastando tiempo en generarlo.
derobert
¿Qué hace que la orden sea confirmada? Probablemente será para un archivo grande, porque tailtiene que trabajar más tiempo, pero espero (y veo) que falle aproximadamente la mitad del tiempo para entradas cortas.
Gilles 'SO- deja de ser malvado'
Obtendrá el SIGPIPE tee >(head) >(tail)por las mismas razones ( >(...)que por cierto es una función ksh que ahora es compatible con zsh y bash también) también usa tuberías. Podría hacerlo, ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null)pero aún verá algunos mensajes de error de tuberías rotastee .
Stéphane Chazelas
En mi sistema (bash 4.2.37, coreutils 8.13), tailes el que SIGPIPE está matando, no tee, y tailno está escribiendo en una tubería. Entonces debe ser de un kill(), ¿no ?. Y esto solo sucede cuando estoy usando la |sintaxis. stracedice que teeno se llama kill()... así que tal vez bash?
Jander
1
@Jander, intenta alimentar más de 8k comoseq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
Stéphane Chazelas
3

Usando ed(que leerá todo el archivo en RAM):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file
curx
fuente
Más corto:ed -s file <<< $'11,$-10d\n,p\nq\n'
don_crissti
2

La primera solución de Stephane en una función para que pueda usar argumentos (funciona en cualquier shell similar a Bourne o POSIX):

head_tail() {
    head "$@";
    tail "$@";
}

Ahora puedes hacer esto:

head_tail -n 5 < /path/to/file

Por supuesto, esto supone que solo está viendo un archivo y, al igual que la solución de Stephane, funciona (confiablemente) solo en archivos normales (buscables).

l0b0
fuente
2

Con la opción -u( --unbuffered) de GNU sed, puede usarla sed -u 2qcomo una alternativa sin búfer para head -n2:

$ seq 100|(sed -u 2q;tail -n2)
1
2
99
100

(head -n2;tail -n2)falla cuando las últimas líneas son parte del bloque de la entrada que es consumida por head:

$ seq 1000|(head -n2;tail -n2)
1
2
999
1000
$ seq 100|(head -n2;tail -n2)
1
2
nisetama
fuente
¡Esta debería ser la mejor respuesta! ¡Funciona de maravilla!
Ben Usman
1

Hoy me topé con algo así, donde solo necesitaba la última línea y algunas líneas desde el frente de una corriente y se me ocurrió lo siguiente.

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

Leí esto como: inicialice el espacio de espera con el contenido de la primera línea, agregue las líneas 2-3 en el espacio de espera, en EOF agregue la última línea al espacio de espera, intercambie el espacio de espera y patrón e imprima el patrón espacio.

Tal vez alguien con más sed-fu que yo pueda encontrar la manera de generalizar esta opción para imprimir las últimas pocas líneas de la corriente que se indican en esta pregunta, pero yo no lo necesitaba y no podía encontrar una manera fácil de hacer matemáticas en base a la $dirección de en sedo tal vez administrando el espacio de espera de modo que solo las últimas líneas estén en él cuando EOFse alcance.

diáconos
fuente
1

Puede probar Perl, si lo tiene instalado:

perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

Esto funcionará para la mayoría de los archivos, pero lee todo el archivo en la memoria antes de procesarlo. Si no está familiarizado con las secciones de Perl, "0" entre corchetes significa "tomar la primera línea" y "-3 ...- 1" significa "tomar las últimas tres líneas". Puede adaptar ambos a sus necesidades. Si necesita procesar archivos realmente grandes (lo que es 'grande' puede depender de su RAM y quizás de los tamaños de intercambio), puede optar por:

perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

puede ser algo más lento, porque hace un corte en cada iteración, pero es independiente del tamaño del archivo.

Ambos comandos deberían funcionar tanto en tuberías como con archivos normales.

Jasio
fuente