unix - cabeza Y cola de archivo


131

Digamos que tiene un archivo txt, ¿cuál es el comando para ver las 10 líneas superiores y las 10 líneas inferiores del archivo simultáneamente?

es decir, si el archivo tiene 200 líneas de largo, visualice las líneas 1-10 y 190-200 de una vez.


¿Qué quieres decir "de una vez"?
cnicutar

@cnicutar es decir. no va el archivo head -10 mirando los datos y luego va por separado el archivo tail -10 y mirando los datos
toop

@toop Si desea un ejemplo real de trabajo, consulte stackoverflow.com/a/44849814/99834
sorin

Respuestas:


208

Puedes simplemente:

(head; tail) < file.txt

Y si necesita usar tuberías por alguna razón, entonces así:

cat file.txt | (head; tail)

Nota: imprimirá líneas duplicadas si el número de líneas en file.txt es menor que las líneas predeterminadas de encabezado + líneas predeterminadas de cola.


54
Estrictamente hablando, esto no le da la cola del archivo original, pero la cola de la secuencia después de headhaber consumido las primeras 10 líneas del archivo. (Compare esto con head < file.txt; tail < file.txtun archivo con menos de 20 líneas). Solo un punto muy pequeño a tener en cuenta. (Pero aún así +1.)
chepner

15
Agradable. Si desea un espacio entre las partes de la cabeza y la cola: (cabeza; eco; cola) <file.txt
Simon Hibbs

3
Curioso sobre por qué / cómo funciona esto. Lo
formulé

9
@nametal En realidad, es posible que ni siquiera obtengas tanto. Si bien headsolo muestra las primeras 10 líneas de su entrada, no se garantiza que no haya consumido más para encontrar el final de la décima línea, dejando menos de la entrada para lessmostrar.
chepner

20
Lamento decirlo, pero la respuesta solo funciona en algunos casos. seq 100 | (head; tail)me da solo los primeros 10 números. Solo en un tamaño de entrada mucho más grande (como seq 2000) la cola recibe algo de entrada.
modular

18

ed es el standard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt

2
¿Qué pasa si el archivo tiene más o menos de 200 líneas? ¿Y no sabes el número de líneas ab initio?
Paul

@Paul me he cambiado sedaed
kev

14

Para una secuencia pura (por ejemplo, salida de un comando), puede usar 'tee' para bifurcar la secuencia y enviar una secuencia a la cabeza y otra a la cola. Esto requiere el uso de la función '> (lista)' de bash (+ / dev / fd / N):

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

o usando / dev / fd / N (o / dev / stderr) más subcapas con redireccionamiento complicado:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(Ninguno de estos funcionará en csh o tcsh).

Para algo con un poco de mejor control, puede usar este comando perl:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'

1
+1 para soporte de transmisión. Puede reutilizar stderr:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs

2
por cierto, se rompe para archivos de mayor tamaño que el tamaño del búfer (8K en mi sistema). cat >/dev/nulllo corrige:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs

Me encantó la solución, pero después de jugar durante un tiempo, noté que en algunos casos la cola corría antes que la cabeza ... no hay un orden garantizado entre heady tailcomandos: \ ...
Jan

7
(sed -u 10q; echo ...; tail) < file.txt

Solo otra variación del (head;tail)tema, pero evitando el problema inicial de llenado del búfer para archivos pequeños.


4

head -10 file.txt; tail -10 file.txt

Aparte de eso, necesitarás escribir tu propio programa / script.


1
Agradable, siempre los he usado caty / heado tailentubado, ¡es bueno saber que puedo usarlos individualmente!
Paul

¿Cómo puedo canalizar estos primeros 10 + últimos 10 en otro comando?
toop

1
@Paul - con 'your_program' como wc -l devuelve 10 en lugar de 20
toop

3
o, sin tener que generar una subcapa: { head file; tail file; } | prog(espaciado dentro de las llaves, y el punto y coma final son obligatorios)
Glenn Jackman

1
Wow ... un voto negativo por tener una respuesta bastante similar a las demás (pero marcada con tiempo antes de ellos) después de casi dos años, de alguien que eligió no publicar por qué votaron negativamente. ¡Agradable!
mah

4

Basado en el comentario de JF Sebastian :

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

De esta manera, puede procesar la primera línea y el resto de manera diferente en una sola tubería, lo que es útil para trabajar con datos CSV:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N * 2
2
4 4
6 6

3

El problema aquí es que los programas orientados a secuencias no conocen de antemano la longitud del archivo (porque puede que no haya uno, si es una secuencia real).

herramientas como tailalmacenar las últimas n líneas vistas y esperar el final de la transmisión, luego imprimir.

si desea hacer esto en un solo comando (y hacer que funcione con cualquier desplazamiento, y no repita líneas si se superponen) tendrá que emular este comportamiento que mencioné.

prueba este awk:

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile

necesita más trabajo para evitar problemas cuando el desplazamiento es mayor que el archivo
Samus_

Yay, esto funciona con salida canalizada, no solo archivos: a.out | awk -v ...
Camille Goudeseune

de hecho :) pero ese es el comportamiento normal de awk, la mayoría de los programas de línea de comandos funcionan en stdin cuando se invocan sin argumentos.
Samus_

1
Muy cerca del comportamiento deseado, pero parece que para <10 líneas agrega líneas nuevas adicionales.
sorin

3

Me llevó mucho tiempo terminar con esta solución, que parece ser la única que cubrió todos los casos de uso (hasta ahora):

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

Lista de características:

  • Salida en vivo para la cabeza (obviamente, para la cola no es posible)
  • sin uso de archivos externos
  • barra de progreso un punto para cada línea después de MAX_LINES, muy útil para tareas de larga duración.
  • barra de progreso en stderr, asegurando que los puntos de progreso estén separados de la cabeza + cola (muy útil si desea canalizar stdout)
  • evita un posible orden de registro incorrecto debido al almacenamiento en búfer (stdbuf)
  • evite duplicar la salida cuando el número total de líneas es menor que head + tail.

2

He estado buscando esta solución por un tiempo. Lo intenté yo mismo con sed, pero el problema de no saber de antemano la longitud del archivo / flujo era insuperable. De todas las opciones disponibles anteriormente, me gusta la solución awk de Camille Goudeseune. Él hizo una nota de que su solución dejaba líneas en blanco adicionales en la salida con un conjunto de datos suficientemente pequeño. Aquí proporciono una modificación de su solución que elimina las líneas adicionales.

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }

1

Bueno, siempre puedes encadenarlos juntos. Me gusta así head fiename_foo && tail filename_foo. Si eso no es suficiente, puede escribir una función bash en su archivo .profile o en cualquier archivo de inicio de sesión que utilice:

head_and_tail() {
    head $1 && tail $1
}

Y, más tarde invocarla desde el shell de comandos: head_and_tail filename_foo.


1

Primeras 10 líneas de archivo.ext, luego sus últimas 10 líneas:

cat file.ext | head -10 && cat file.ext | tail -10

Últimas 10 líneas del archivo, luego las primeras 10:

cat file.ext | tail -10 && cat file.ext | head -10

Luego puede canalizar la salida en otro lugar también:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program


55
¿Por qué usar cat cuando solo puedes llamar a head -10 file.txt?
jstarek

¿Puede hacer que el número de líneas sea variable, de modo que la llamada sea algo así como: head_ tail (foo, m, n), devolviendo las primeras m y últimas n líneas de texto?
ricardo

@ricardo que implicaría escribir un script bash que toma 3 argumentos y los pasa a taily heado una función por alias-Ing ella.
Paul


1

aprovechando las ideas anteriores (probado bash y zsh)

pero usando un alias 'hat' Head and Tails

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql

0

¿Por qué no usar sedpara esta tarea?

sed -n -e 1,+9p -e 190,+9p textfile.txt


3
Esto funciona para archivos de longitud conocida, pero no para archivos cuya longitud es desconocida.
Kevin

0

Para manejar tuberías (streams) así como archivos, agregue esto a su archivo .bashrc o .profile:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

Entonces no solo puedes

headtail 10 < file.txt

pero también

a.out | headtail 10

(Esto todavía agrega líneas en blanco espurias cuando 10 excede la longitud de la entrada, a diferencia de la anterior a.out | (head; tail). Gracias, respondedores anteriores).

Nota: headtail 10no headtail -10.


0

Sobre la base de lo que @Samus_ explicó aquí sobre cómo funciona el comando de @Aleksandra Zalcman, esta variación es útil cuando no se puede detectar rápidamente dónde comienza la cola sin contar líneas.

{ head; echo "####################\n...\n####################"; tail; } < file.txt

O si comienza a trabajar con algo más que 20 líneas, un recuento de líneas podría incluso ayudar.

{ head -n 18; tail -n 14; } < file.txt | cat -n

0

Para imprimir las primeras 10 y últimas 10 líneas de un archivo, puede intentar esto:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less


0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

NOTA : La variable aFile contiene la ruta completa del archivo .


0

Yo diría que dependiendo del tamaño del archivo, leer activamente en su contenido puede no ser deseable. En esa circunstancia, creo que deberían bastar algunas secuencias de comandos de shell simples.

Así es como recientemente manejé esto para varios archivos CSV muy grandes que estaba analizando:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

Esto imprime las primeras 10 líneas y las últimas 10 líneas de cada archivo, al tiempo que imprime el nombre de archivo y algunos puntos suspensivos antes y después.

Para un solo archivo grande, simplemente puede ejecutar lo siguiente para obtener el mismo efecto:

$ head somefile.csv && echo ... && tail somefile.csv

0

Consume stdin, pero simple y funciona para el 99% de los casos de uso

cabeza y cola

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

ejemplo

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
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.