unix - cabeça E cauda do arquivo


131

Digamos que você tenha um arquivo txt, qual é o comando para visualizar as 10 linhas superiores e as 10 linhas inferiores do arquivo simultaneamente?

ou seja, se o arquivo tiver 200 linhas, visualize as linhas 1-10 e 190-200 de uma só vez.


O que você quer dizer com "de uma só vez"?
Cknutar

@cnicutar ie. não indo para o arquivo -10 olhando os dados e, em seguida, indo separadamente para o arquivo -10 e olhando para os dados
acima de

@toop Se você quer um exemplo real de trabalho, consulte stackoverflow.com/a/44849814/99834
Sorin

Respostas:


208

Você pode simplesmente:

(head; tail) < file.txt

E se você precisar usar tubos por algum motivo, faça o seguinte:

cat file.txt | (head; tail)

Nota: imprimirá linhas duplicadas se o número de linhas no arquivo.txt for menor que as linhas padrão da cabeça + linhas padrão da cauda.


54
A rigor, isso não fornece a cauda do arquivo original, mas a cauda do fluxo depois headconsumiu as 10 primeiras linhas do arquivo. (Compare isso com head < file.txt; tail < file.txtum arquivo com menos de 20 linhas). Apenas um ponto muito menor a ser lembrado. (Mas ainda +1.)
chepner

15
Agradável. Se você quer uma lacuna entre as partes dianteira e traseira: (cabeça; eco; cauda) <file.txt
Simon Hibbs

3
Curioso sobre por que / como isso funciona. Foi perguntado como uma nova pergunta: stackoverflow.com/questions/13718242
zellyn:

9
@nametal Na verdade, você pode até não conseguir tanto. Embora headapenas exiba as 10 primeiras linhas de sua entrada, não há garantia de que ela não tenha consumido mais para encontrar a 10ª linha final, deixando menos da entrada para lessexibição.
chepner

20
Lamento dizer, mas a resposta só funciona em alguns casos. seq 100 | (head; tail)me dá apenas os primeiros 10 números. Somente em um tamanho de entrada muito maior (como seq 2000) a cauda recebe alguma entrada.
modular

18

ed é o standard text editor

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

2
E se o arquivo tiver mais ou menos de 200 linhas? E você não sabe o número de linhas ab initio?
Paul

@Paul Eu mudei sedparaed
kev

14

Para um fluxo puro (por exemplo, saída de um comando), você pode usar 'tee' para bifurcar o fluxo e enviar um fluxo para a cabeça e outro para a cauda. Isso requer o uso do recurso '> (list)' do bash (+ / dev / fd / N):

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

ou usando / dev / fd / N (ou / dev / stderr) mais subshells com redirecionamento 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

(Nenhum deles funcionará em csh ou tcsh.)

Para algo com um pouco de controle melhor, você pode 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 suporte a stream. Você pode reutilizar stderr:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs

2
Aliás, ele quebra para arquivos maiores que o tamanho do buffer (8K no meu sistema). cat >/dev/nullcorrige:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs

Adorei a solução, mas, depois de jogar pelo aa, notei que, em alguns casos, a cauda estava correndo diante da cabeça ... não há pedidos garantidos entre heade tailcomandos: \ ...
Jan

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

Apenas outra variação no (head;tail)tema, mas evitando o problema inicial de preenchimento do buffer para arquivos pequenos.


4

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

Fora isso, você precisará escrever seu próprio programa / script.


1
Bom, eu sempre usei cate headoutail canalizei, bom saber que posso usá-los individualmente!
Paul

Como posso então canalizar esses primeiros 10 + últimos 10 em outro comando?
toop

1
@Paul - com 'your_program' como wc -l, retorna 10 em vez de 20
até

3
ou, sem ter de criar um subshell: { head file; tail file; } | prog(espaçamento dentro das chaves, e o ponto e vírgula final são necessárias)
Glenn Jackman

1
Uau ... um voto negativo por ter uma resposta bastante semelhante a outras pessoas (ainda com carimbo de data e hora antes deles) depois de quase dois anos, de alguém que optou por não postar por que votou negativamente. Agradável!
mah

4

Baseado no comentário de JF Sebastian :

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

Dessa forma, você pode processar a primeira linha e o restante de maneira diferente em um pipe, o que é útil para trabalhar com dados 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
6

3

o problema aqui é que os programas orientados a fluxo não sabem o tamanho do arquivo com antecedência (porque pode não haver um, se for um fluxo real).

ferramentas como tail buffer das últimas n linhas vistas, aguarde o final do fluxo e depois imprima.

se você quiser fazer isso em um único comando (e fazê-lo funcionar com qualquer deslocamento, e não repetir as linhas se elas se sobreporem), será necessário emular esse comportamento que mencionei.

tente 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

ele precisa de mais trabalho, a fim de evitar problemas quando o deslocamento é maior que o arquivo
Samus_

Yay, isso funciona com saída canalizada, não apenas arquivos: a.out | awk -v ...
Camille Goudeseune

de fato :) mas esse é o comportamento normal do awk, a maioria dos programas de linha de comando funciona no stdin quando invocada sem argumentos.
Samus_

1
Muito perto do comportamento desejado, mas parece que para <10 linhas, ele adiciona novas linhas extras.
Sorin

3

Demorou muito tempo para terminar com esta solução, que parece ser a única que abrangeu todos os casos de uso (até agora):

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 recursos:

  • saída ao vivo para a cabeça (obviamente que para a cauda não é possível)
  • sem uso de arquivos externos
  • barra de progresso um ponto para cada linha após o MAX_LINES, muito útil para tarefas de longa execução.
  • barra de progresso no stderr, garantindo que os pontos de progresso sejam separados da cabeça e da cauda (muito útil se você quiser canalizar stdout)
  • evita possível ordem de log incorreta devido ao buffer (stdbuf)
  • evite duplicar a saída quando o número total de linhas for menor que a cabeça + cauda.

2

Eu tenho procurado por esta solução por um tempo. Tentei eu mesmo com o sed, mas o problema de não saber o tamanho do arquivo / fluxo de antemão era insuperável. De todas as opções disponíveis acima, eu gosto da solução awk de Camille Goudeseune. Ele observou que sua solução deixou linhas em branco extras na saída com um conjunto de dados suficientemente pequeno. Aqui, forneço uma modificação de sua solução que remove as linhas extras.

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

Bem, você sempre pode amarrá-los juntos. Assim head fiename_foo && tail filename_foo. Se isso não for suficiente, você poderá escrever uma função bash em seu arquivo .profile ou em qualquer arquivo de logon usado:

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

E, mais tarde, invocá-lo a partir do seu shell prompt: head_and_tail filename_foo.


1

Primeiras 10 linhas de file.ext, depois as últimas 10 linhas:

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

As últimas 10 linhas do arquivo e as 10 primeiras:

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

Você também pode canalizar a saída para outro local:

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


5
Por que usar cat quando você pode chamar head -10 file.txt?
Jstarek

Você pode tornar o número de linhas variável, para que a chamada seja algo como: head_ tail (foo, m, n) - retornando a primeira e a última n linhas de texto?
ricardo

@ricardo que envolveria a escrita de um script bash que leva 3 args e os passa para taile / headou para uma função usando o alias.
Paul


1

usando as idéias acima (testado bash & zsh)

mas usando um apelido 'hat' Head and Tails

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


hat large.sql

0

Por que não usar sedpara esta tarefa?

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


3
Isso funciona para arquivos de tamanho conhecido, mas não para arquivos cujo tamanho é desconhecido.
Kevin

0

Para manipular pipes (fluxos) e arquivos, adicione-o ao arquivo .bashrc ou .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] }' ; }

Então você não pode apenas

headtail 10 < file.txt

mas também

a.out | headtail 10

(Isso ainda acrescenta linhas em branco espúrias quando 10 excede o comprimento da entrada, diferente da antiga simples a.out | (head; tail). Obrigado, respondedores anteriores.)

Nota:, headtail 10não headtail -10.


0

Com base no que @Samus_ explicou aqui sobre como o comando de @Aleksandra Zalcman funciona, essa variação é útil quando você não consegue identificar rapidamente onde a cauda começa sem contar as linhas.

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

Ou, se você começar a trabalhar com algo diferente de 20 linhas, uma contagem de linhas pode até ajudar.

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

0

Para imprimir as 10 e as 10 primeiras linhas de um arquivo, tente o seguinte:

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 : A variável aFile contém o caminho completo do arquivo .


0

Eu diria que, dependendo do tamanho do arquivo, a leitura ativa de seu conteúdo pode não ser desejável. Nessa circunstância, acho que alguns scripts simples de shell devem ser suficientes.

Aqui está como eu lidei com isso recentemente para vários arquivos CSV muito grandes que eu estava analisando:

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

Isso imprime as primeiras 10 linhas e as últimas 10 linhas de cada arquivo, além de imprimir o nome do arquivo e algumas reticências antes e depois.

Para um único arquivo grande, você pode simplesmente executar o seguinte para o mesmo efeito:

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

0

Consome stdin, mas simples e funciona para 99% dos casos de uso

head_and_tail

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

exemplo

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.