linux - tutorial - shell script help




unix-cabeça E cauda do arquivo (13)

Digamos que você tenha um arquivo txt, qual é o comando para visualizar as 10 linhas principais 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.


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 canal, 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

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

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

E, mais tarde, invoque-o no prompt do shell: head_and_tail filename_foo .


Demorou muito tempo para acabar com esta solução que, parece ser a única que cobriu 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)
  • nenhum uso de arquivos externos
  • barra de progresso um ponto para cada linha após o MAX_LINES, muito útil para tarefas de longa duração.
  • barra de progresso no stderr, assegurando que os pontos de progresso são separados da cabeça + cauda (muito útil se você quiser canalizar stdout)
  • evita possível ordem de log incorreta devido ao buffering (stdbuf)
  • evite duplicar a saída quando o número total de linhas for menor que head + tail.

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

Veja como eu lidei com isso recentemente para vários arquivos CSV muito grandes que 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ê poderia simplesmente executar o seguinte para o mesmo efeito:

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

Eu tenho procurado por esta solução por um tempo. Tentei-me com sed, mas o problema de não saber o comprimento do arquivo / fluxo de antemão era intransponível. De todas as opções disponíveis acima, eu gosto da solução awk de Camille Goudeseune. Ele fez uma nota que sua solução deixou linhas vazias extras na saída com um conjunto de dados suficientemente pequeno. Aqui eu 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] }' ; }

O problema aqui é que programas orientados a fluxo não conhecem o tamanho do arquivo antecipadamente (porque pode não haver um, se for um fluxo real).

ferramentas como tail buffer as últimas n linhas vistas e aguardar o final do fluxo, em seguida, imprimir.

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 sobrepuserem), será necessário imitar 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

Para lidar com pipes (fluxos) e arquivos, adicione isso ao seu 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ê pode não só

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, ao contrário do antigo simples a.out | (head; tail) . Obrigado, respondentes anteriores.)

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


Para um fluxo puro (por exemplo, a 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 destes funcionará em csh ou tcsh.)

Para algo com um pouco melhor controle, 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;'

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

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

As últimas 10 linhas do arquivo, depois as 10 primeiras:

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

Você pode então canalizar a saída em outro lugar também:

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


Você pode simplesmente:

(head; tail) < file.txt

E se você precisar usar pipes por alguma razão, então:

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 de cauda padrão.


ed é o standard text editor

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

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

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


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

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







scripting