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.

toop
fuente
¿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.

Aleksandra Zalcman
fuente
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
kev
fuente
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;'
Despotricando
fuente
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.

invitado
fuente
4

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

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

mah
fuente
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
modular
fuente
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
Samus_
fuente
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.
Sorin
fuente
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] }' ; }
Michael Blahay
fuente
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.

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

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

Escribí una aplicación simple de Python para hacer esto: https://gist.github.com/garyvdm/9970522

Maneja tuberías (flujos) y archivos.

Gary van der Merwe
fuente
2
Sería mejor publicar las partes relevantes del código.
fedorqui 'así que deja de dañar'
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
zzapper
fuente
0

¿Por qué no usar sedpara esta tarea?

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

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

Camille Goudeseune
fuente
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
Script Wolf
fuente
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

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

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

mark_infinite
fuente
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
Jitsusama
fuente
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
Brad Parks
fuente