Imprime a primeira e última linha de todos os arquivos na pasta

3

Eu tenho um monte de arquivos de log que são sobrescritos ( file.log.1 , file.log.2 etc). Quando os copio do dispositivo, fazendo-os na minha máquina local, perco os carimbos de hora originais. Então eu gostaria de colocá-los em ordem cronológica. O problema é que eu não sei necessariamente qual é o mais novo e qual é o mais antigo.

O que eu gostaria de poder fazer é, se todos os registros estiverem em um diretório, imprimir algo assim:

file: file.log.1
first line: [first line that isn't whitespace]
last line: [last line that isn't whitespace]

Eu posso apenas escrever um script python para fazer isso, mas prefiro fazê-lo com os built-ins do linux, se possível. Isso é um trabalho para o awk / sed? Ou isso seria realmente melhor para uma linguagem de script? Se sim para o awk / sed, como você faria isso?

Eu encontrei este comando awk pesquisando, mas ele só aceita um nome de arquivo e imprime qualquer que seja a última linha (e pode haver um número variável de linhas vazias no final)

awk 'NR == 1 { print }END{ print }' filename
    
por Mitch 23.12.2014 / 18:59

9 respostas

6

Então eu gosto de sed a resposta pode ser

for file in file.log.*
do
   echo "file: $file"
   echo -n "first line: "
   cat "$file" | sed -n '/^\s*$/!{p;q}'
   echo -n "last line: "
   tac "$file" | sed -n '/^\s*$/!{p;q}'
done
    
por 23.12.2014 / 19:24
5

um comando do awk:

awk -v OFS=: '
    FNR==1 {
        # the last non-blank line from the previous file
        if (line) {print filename, fnr, line}
        filename=FILENAME
        line=""
        p=0
    } 
    /^[[:blank:]]*$/ {next} 
    !p {
        # the first non-blank line
        print FILENAME, FNR, $0; p=1
    }
    {fnr=FNR; line=$0} 
    END {print filename, fnr, line}
' *

para cada arquivo, imprima o nome do arquivo, o número da linha e a linha, separados por dois pontos.

O GNU awk v4 tem padrões BEGINFILE e ENDFILE que simplificam bastante as coisas:

gawk -v OFS=: '
    BEGINFILE {p=0} 
    /^[[:blank:]]*$/ {next} 
    !p {print FILENAME, FNR, $0; p=1}
    {fnr=FNR; line=$0} 
    ENDFILE {print FILENAME, fnr, line}
' *
    
por 23.12.2014 / 19:59
2

Tente:

awk -F'\n' -vRS="" '
  {
    print "file: " FILENAME;
    gsub(/\n[[:blank:]]+|[[:blank:]]+\n/,"");
    print "first line: " $1;
    print "last line: " $NF;
  }
' file.log.*
    
por 23.12.2014 / 19:22
2

Outra abordagem seria usar head e tail :

EDITAR (Obrigado pela sugestão @don_crissti!)

for file in file.log.*
do
   echo "file: $file"
   echo -n "first line: "
   grep -v '^\s*$' "$file" | head -n1
   echo -n "last line: "
   grep -v '^\s*$' "$file" | tail -n1
done
    
por 24.12.2014 / 00:07
2

O que? Não Perl?

for file in file.log.*; do 
    echo "FILE: $file"; 
    perl -ne 'if(/\S/){$k++; $l=$_}; 
              print "First line: $_" if $k==1; 
              END{print "Last line: $l\n"}' "$file";  
done

Explicação

  • for file in file.log.* : itera todos os arquivos cujos nomes começam com file.log. no diretório atual e salvam cada um deles como $file .
  • echo "FILE: $file"; : imprime o nome do arquivo.
  • perl -ne : leia o arquivo de entrada atual linha por linha ( -n ), salvando cada linha como a variável especial Perl $_ e execute o script fornecido por -e .
  • if(/\S/){$k++; $l=$_} : se a linha atual corresponder a um caractere que não seja espaço em branco ( \S ), salve a linha como $l e incremente o contador $k em um.
  • print "First line: $_" if $k==1; : imprime a linha atual ( $_ ) se $k for 1 . Isto irá imprimir a primeira linha não branca.
  • END{print "Last line: $l\n"} : isso é executado depois que todas as linhas de entrada foram lidas. Como salvamos cada linha que não é espaço em branco como $l , no final do arquivo, $l será a última linha que não é espaço em branco. Portanto, isso imprimirá a última linha.

Outra abordagem:

for file in file.log.*; do 
    printf "FILE: %s\nFirst line: %s\nLast line: %s\n\n" \
        "$file" \
        "$(grep -Em 1 '\S' "$file")" \
        "$(tac "$file" | grep -Em1 '\S' )"; 
done

Explicação

Este é o mesmo loop for somente aqui estamos usando printf para imprimir três strings. O nome do arquivo e a saída desses dois comandos:

  • grep -Pm 1 '\S' "$file" : O -E ativa expressões regulares estendidas que nos permitem usar \S para "espaços não brancos". O -m1 significa "sair após a primeira correspondência encontrada".
  • tac "$file" | grep -Em1 '\S' : tac é o inverso de cat . Ele irá imprimir o conteúdo de um arquivo, mas da última linha até a primeira linha. Portanto, este comando irá imprimir a última linha que não seja espaço em branco.
por 24.12.2014 / 09:36
1

Para ficar completo, aqui está uma resposta sed que só lê cada arquivo uma vez (esperamos que seja mais rápido que vários sed , cat e tac invocações, se os arquivos forem grandes):

for file in file.log.*; do
    echo "file: $file"
    sed -n "
/[^[:space:]]/ {                # Match first non-whitespace line
    h                           # Copy to hold buffer
    s/^/first line: / p         # Add prefix and print
:loop
    $ {                         # Match last line
        g                       # Get contents of hold buffer
        s/^/last line: / p      # Add prefix and print
    }
    n                           # Load next line
    /[^[:space:]]/ h            # Copy non-whitespace line to hold buffer
    b loop                      # Jump back to process next line
}" "$file"
done
    
por 23.12.2014 / 23:27
0
strm_1st_last() ( unset l i c n
l=1 i='i\\' c='c\\' n='\
';  eval "echo |sed -n \":b
    /[^[:blank:]]/h;$( wc -l "$@" | 
    sed -e '${1!d' -e "};s/[\"\/]/\\&/g
    s/^[ 0]*\([^ 0-9]\)/1$i$n empty: /;/\n/b
    s|^ *\([0-9]\{1,\}\) *\(.*\)|\
    \$((1+(l+=\1)-\1)){$i$n\2$n :l\
         s/.*[^[:blank:]]/    \&/p;//h;t\
         n;\$((l-1)){ //bb$n//!$c$n\ALL BLANK$n};bl$n}\
    \${l}{ x;s/^/    /p;s/.*//;x;d$n}|")"'" - "$@"'
)

Essa é uma função que depende de um processo sed para criar um script viável por um segundo. Bem, isso não depende apenas de um sed sub-descascado, mas também de wc para obter primeiro os números de linha totais para cada um de seus argumentos, e o próprio shell para calcular usualmente a aritmética expressões sed constrói como referências de linha para o segundo sed .

Basicamente, ele trata todos os argumentos como um único fluxo. Por exemplo, se você fizer isso:

seq 10 > file1; seq 5 > file2; sed -n \$= file[12]

sed será impresso ...

15

... porque concatena automaticamente todos os argumentos de nome de arquivo em um único fluxo de dados. Então, nós apenas trabalhamos com isso.

Primeiro wc é solicitado para um relatório em todos os seus comprimentos e, em seguida, sed vai trabalhar moldando isso em um script de entrada viável para um segundo sed .

Se wc informar que algum dos arquivos tem 0 linhas, sed imprimirá vazio: nome_do_arquivo para ele e todos os outros antes de qualquer outra coisa. (provavelmente apenas 0 linhas seria mais preciso, no entanto).

Se, ao verificar uma seção de linhas de entrada que compreende um arquivo inteiro sed não encontrar nenhum caractere não em branco, sed informará ALL BLANK (que é o único tipo de relatório de linha que não é recuado)

Para todos os outros casos, sed imprime a primeira e a última linha não vazia para cada um dos seus argumentos e precede cada par recuado com o nome do arquivo não recuado. Isso não lida com nomes de arquivos contendo novas linhas - embora provavelmente não seja necessário muito mais para isso.

Então, se eu fizer ...

strm_1st_last file[12]

file1
    1
    10
file2
    1
    5

ou ...

: >file2; strm_1st_last file[12]

empty: file2
file1
    1
    10

... ou ...

strm_1st_last ~/*.sh


empty: /home/mikeserv/alleq.sh
empty: /home/mikeserv/mansed5.sh
/home/mikeserv/chrome.sh
    #!/bin/bash
    ) &
/home/mikeserv/crap.sh
ALL BLANK

/home/mikeserv/getopts.sh
    c=$# t=\     l='
    done; printf '%s\n' "'${str#?}'"; done
/home/mikeserv/mansedmaybe.sh
    mansed () {
    );}
/home/mikeserv/mansed.sh
    mansed () {
    );}
/home/mikeserv/pr_after.sh
    pr_after() if       _i= _o=D OPTIND=1 _n='\
        fi
/home/mikeserv/script.sh
    #!/usr/bin/sh
    echo done
/home/mikeserv/serial.sh
    sq() ( IFS=\' set -f
    #   a               b
/home/mikeserv/your_config.sh
    zifs() { eval "shift ${3+3}
    )
/home/mikeserv/ytplay.sh
    ytplay() ( 
    )
/home/mikeserv/zifs.sh
    zifs() { eval "shift ${3+3}
    )
    
por 25.12.2014 / 19:46
-1

Tente isto:

for file in $(ls) ; do cat $file | sed -n '1p;$p' ; done
    
por 12.10.2016 / 12:55
-2

Usando a localização, você pode tentar:

find . -type f -exec sed -n '1p;$p' {} \; -print
    
por 02.10.2017 / 23:39

Tags